Occasionally Connected Systems Architecture: Concurrency
The problem of concurrency is a familiar one in the development of multi-user systems. Most of the time, optimistic locking is preferred because it results in better throughput. However, pessimistic locking has its place as well for those cases where only one user should be allowed to work on the data. Both of these solutions break down once users start working offline and try to synchronize their updates with the server later.
The kind of conflicts that occur during synchronization include:
- Trying to add a child row to a parent row that doesn't exist
- Trying to update a row that no longer exists
- Adding a row that violates a unique constraint
In other words, various kinds of database-level referential integrity violations. There are other kinds of conflicts that occur but are much more difficult to detect. These "business integrity violations" include:
- Requesting to ship an order that has already been cancelled
- Submitting an order for a customer who has been delinquent in their payments
- Updating information on a document that has already been authorized
When performing synchronization between the smart client and the server databases, although it is possible to detect referential integrity violations, it is almost impossible to handle them. Should all changes from that client be rolled back? Only some changes? If so, which ones? The answer to all of these questions hinges on one thing—business level transaction scope.
Business-Level Transaction Scope
If the transactions that were performed against the smart clients database (from the time of going offline until it reconnected again) were "replayed" against the server database, you'd have the appropriate roll-back behavior in case of a violation. One problem with this is that many databases don't support this. Another is that this solution does not entirely take care of the "business integrity violations" mentioned above. A full solution would have to include running the business logic as a part of the transaction against the server. If all logic that was in the database were implemented as stored procedures and/or triggers, this solution might work. Yet, it is clear that much business logic is not represented directly in the database, and this development style is also often described as an anti-pattern.
It appears that you're at an impasse. Database-level synchronization will not be able to identify or handle the various kinds of conflicts prevalent in offline scenarios.
N-Tier Messaging to the Rescue
If you re-examine the solution described, you see that you need the ability to group together all the data the client changed in each transaction along with the business-level reason for that transaction. Call this grouping a Message. It's important for the client to be able to serialize and store these messages so that they can be sent to the server later.
If you were to call the "business-level reason" something else, like "action" and represent it as a string, these messages begin to look similar to SOAP packets and the overall solution moves closer to Web Services.
On the server side, when a message is received the server performs all the logic involved in that action. This includes opening a transaction, performing the appropriate validation, and performing all the data changes requested by the message. Should any kind of violation occur, that message could be routed to a human operator for resolution.
When the client is offline, it would behave similarly to the server: UI elements would capture the user's actions (effectively done with the Command pattern), those actions would be translated to messages, the logic for handling those messages would run against the local database, and the messages would be saved so that they could later be sent to the server. When the client is online, those same messages would be sent to the server instead. The process of going offline would not need to be any different than what is currently done today—you simply copy a subset of the data from the server database to the local one.
The use of the simple abstraction of a "message" enables you to solve the thorny issues of conflict identification and resolution in both the technical and business domains. In the next article of this series, you'll get into the ramifications of using messaging on the design of smart clients as well as the practicality of running server code on the client.
- 8 Fallacies of Distributed Computing: http://en.wikipedia.org/wiki/Fallacies_of_Distributed_Computing
- The Command Pattern: http://www.patterndepot.com/put/8/command.pdf
- Disconnected Problems and Solutions—a blog post: http://udidahan.weblogs.us/2007/03/30/entity-framework-disconnected-problems-solutions/
About the Author
Udi Dahan is The Software Simplist, a Microsoft Solutions Architect MVP, recognized .NET expert, and a member of both the Microsoft Architects and Technologists Councils. Udi provides clients all over the world with training, mentoring, and high-end architecture consulting services, specializing in Service-Oriented, scalable, and secure .NET architecture design, and Web Services.
He is a member of the International Speakers Bureau of INETA, an associate member of the International Association of Software Architects (IASA), a frequent conference presenter, a Dr. Dobb's sponsored expert on Web Services, SOA, & XML, and a regularly published author.
Udi can be contacted via his blog www.UdiDahan.com.