1

I have an offline application and the data is 2-way synchronised between server and the app using pouchdb-couchdb. One requirement is both the server and the app must able to mutate the application data.

Now I have the problem to maintain large application logic on the server side, as there will be multiple version of the app (multiple application logic) running in production, but there's only 1 version of the server.

Example:

Offline Application:
v1.0: increase({ data: 1 }) => ({ data: 1 })
v2.0: increase({ data: { value: 0, percision: 2 }) => ({ data: { value: 1, precision: 2 } })

Server:
# only 1 version allowed, how to maintain?

To resolve this problem, there are 2 possible solutions:

  1. version my data, and the server will keep track all logic for each version, example
Server:
increase('1.0', { data: 0 }) => ({ data: 1 })
increase('2.0', { data: { value: 0, percision: 2 }) => ({ data: { value: 1, precision: 2 } })

The problem of this approach the server codebase will growth drastically (eg: 100+ function), and we still have two source of truth to maintain.

  1. store the business logic in the database. In the offline app, we store increase().prototype.toString() in db, let it sync with server. Whenever server need the function, it'll just perform eval(fn). Since only the app will able to mutate the function in the database, we only need to maintain 1 codebase

I personally prefer second approach but I not sure is there any pitfall on this approach. Or is there any better approach to this issue?

kingwei
  • 147

1 Answers1

1

If the data model of the server is extended, and there is a client app (which is able to mutate the data) which sticks to an older version and does not know about the server extensions, sooner or later you will come to the point where this will introduce the risk if data loss or inconsistencies, since the client may write back some data without the extended fields or the new data constraints.

A possible solution to this which is pretty safe is to make sure when an older client version connects to the server, it asks the server for the version number of the underlying data model or the API. When the server then returns a newer version number than the client will able to handle, the app switches into read-only mode and will forbid to synchronize any data until the client is updated to a newer version. When the client then is updated, it will run a defined migration process for its local data.

This works best when the installation of a client upgrade works almost automatically in the background, with no special user rights required, only a modest network connection.

Of course, there is also the possibility of providing different versions of the server API for different client versions. This may lead to a lot of functions on the server side, but not necessarily to "two sources of truth to maintain" - the outer side of those APIs should be freezed once they are published, and the "inner side" can be refactored to make functions "1.0", "2.0" and "3.0" of the API just delegate to some common function.

Both of these approaches can be combined, so you may shut down API "1.0" once "4.0" is released and force the clients of version 1.0 to update, but allow clients still to use version 2.0 and 3.0 in parallel to 4.0. This keeps down the number of API versions you have to maintain to a fixed number instead of letting it grow to infinity.

Doc Brown
  • 218,378