Occasionally connected CQRS system
If I understand your design picture correctly, then the occasionally connected users enqueue commands, i.e., change requests, and when the user reconnects the queued commands are sent together; there is only one database authority (that the command handlers query to load the most recent versions of their aggretates); only the view model is synced to the clients.
In this setup, Scenario 2 is trivially auto-merged by your design, if you choose your commands wisely, read: make them fine-grained: For every possible change, choose one command. Then, on re-connection of the client, the commands are processed in any order, but since they only affect disjunct fields, there is no problem:
- Customer is at v20.
- A is offline, edits changes against stale model of v20.
- B is offline, edits changes against stale model of v20.
- A comes online, batch sends an queued
ChangeName
command, the Customer of v20 is loaded and persisted as v21. - B comes online, batch sends an queued
ChangeAddress
command, the Customer of v21 is loaded and persisted as v22. - The database contains the user with their correct name and address, as expected.
In Scenario 1, with this setup, both employees will overwrite the other employees' changes:
- Customer is at v20.
- A is offline, edits changes against stale model of v20.
- B is offline, edits changes against stale model of v20.
- A comes online, batch sends an queued
ChangeName
command to "John Doe", the Customer of v20 is loaded and persisted as v21 with name "John Doe" - B comes online, batch sends an queued
ChangeName
command to "Joan d'Arc", the Customer of v21 (named "John Doe") is loaded and persisted as v22 (with name "Joan d'Arc'). - Database contains a user with name "Joan d'Arc".
If B comes online before A, then it's vice versa:
- Customer is at v20.
- A is offline, edits changes against stale model of v20.
- B is offline, edits changes against stale model of v20.
- B comes online, batch sends an queued
ChangeName
command to "Joan d'Arc", the Customer of v20 is loaded and persisted as v21 (with name "Joan d'Arc'). - A comes online, batch sends an queued
ChangeName
command to "John Doe", the Customer of v21 is loaded and persisted as v22 with name "John Doe". - Database contains a user with name "John Doe".
There are two ways to enable conflict detection:
- Check whether the command's creation date (i.e., the time of the employees modification) is after the last modification date of the
Customer
. This will disable the auto-merge feature of Scenario 2, but will give you full conflict detection against concurrent edits. - Check whether the command's creation date (i.e., the time of the employees modification) is after the last modification date of the individual field of the
Customer
it is going to change. This will leave the auto-merge of Scenario 2 intact, but will give you auto-conflict-detection in Scenario 1.
Both are easy to implement with event sourcing (since the timestamps of the individual events in the event stream are probably known).
As for your question "Who's changes do we keep in scenario 1?" -- this depends on your business domain and its requirements.
EDIT-1: To answer on the clarification question:
Yes, you'll need one command for each field (or group of fields, respectively) that can be changed individually.
Regarding your mockup: What you are showing is a typical "CRUD" UI, i.e., multiple form fields and, e.g., one "Save" button. CQRS is usually and naturally combined with a "task based" UI, where there would be, say, the Status
field be displayed (read-only), and if a user wants to change the status, one clicks, say, a "Change Status" button, which opens a dialog/new window or other UI element, where one can change the status (in web based systems, in-place-editing is also common). If you are doing a "task based" UI, where each task only affects a small subset of all fields, then finely grained commands for ChangeName, ChangeSupplier etc are a natural fit.
Here's a generic overview of some solutions:
Scenario 1
Someone has to decide, preferably a human. You should ask the user or show that there is a conflict.
Dropbox solves this by picking the later file and keeping a file.conflict file in the same directory for the user to delete or use.
Scenario 2
Keep the original data around and see which fields actually changed. Then you can apply employee 1's changes and then employee 2's changes without stepping on any toes.
Scenario 3 (Only when the changes come online at different times)
Let the second user know that there were changes while they were offline. Attempt Scenario 2 and show the second user the new result (because this might change his inputs). Then ask him if he wants to save his changes, modify them first, or throw them out.