5
votes

Hi I have the following senario which I dont understand how to get eventual consistency with:

  1. User 1 uses Task based ui to change customer name
  2. App Service calls operation on the aggregate
  3. Aggregate fires event on customername changed
  4. bus sends message using nservicebus
  5. NServicebus service dies
  6. User 2 gets aggregate and calls change address
  7. Aggregate operation called
  8. Domain event fired
  9. Message put on bus
  10. Bus restarts
  11. Message 2 picked up first
  12. Message 2 processed and other Bounded context updated with new address
  13. Message 1 picked up now which is wrong order
  14. What happens now

In 13 would there be an optimistic concurrency error if we pass the version of the aggregate in the event?

If so Message 1 new gets applied to object in the other context. How do we even maintain consistency?

This is the issue that is preventing me applying events in my domain. All help welcome.

The essential idea is to update another aggregate in another context. I am just stuck on the concurrency technicalities of this.

We are not using event sourcing or CQRS in the sense of commandhandler and commands push on the bus. It is only the event processing we want to happen asynchronously as we have an existing design which we do not wish to change.

Blair

4

4 Answers

3
votes

Generally you would be queuing the messages. If they are going into a queue you will get proper ordering. If you want to use something that does not support ordering with your servicebus then add a sequence number to your events so the other side can properly reorder them. TCP has been doing this since 1981 http://www.ietf.org/rfc/rfc793.txt :)

0
votes

Oops: I just noticed that you actually used 2 different tasks in your example (customer name and then customer address). Of course this would be a non-issue since the order really should not matter. But I'll leave my answer just in case your intention was to have two of the same type of change.

As always a couple of questions/issues come to mind:

For one thing your example does not require an endpoint to fail since 2 users could update the address at the same time and the order of the processing can be random. So the question becomes which one is the correct address. So resolving that requires quite a bit more analysis. For one thing I guess we could assume that a customer would not move so frequently that 2 users are updating the address at the same time (or even near it) --- :)

Even so, maybe the first address is the correct one.

I think for such a scenario you want to identify whether concurrency, or even some other tolerance, is an issue. So maybe an address may only be changed once a day and any other change requires some other interaction. So some exception handling would be an option.

You really should not be taking this down to a technical level to try and resolve it but rather look at the process/business implications.

For a simple solution I would go with matching message send date to the last operation date for a specific type. So for ChangeAddressCommand when processing a message you could compare to the current LastAddressChange and if the message was sent before the last change date then reject it.

0
votes

A similar issuee with NServiceBus is discussed here. The OP proposes the use of IBus.HandleCurrentMessageLater() to spin until the other message arrives. This can work, but can be problematic since you never know how long you have to wait.

A more involved option would be to use a saga which would wait until all messages leading up to a particular version have arrived. In this case, sequencing is done based on version and is only possible if all changes in aggregate version are published to the other BC. Suppose Message 1 operated on version 2 of the aggregate. It then it increments the version of the aggregate and publishes an event stating it has operated on version 2. Message 2 operates on version 3 of the aggregate and publishes an event stating it has operated on version 3. When the NServiceBus endpoint in the other BC receives Message 2 before Message 1, it knows that the last received message operated on version 1 of the aggregate, so it needs one that has operated on version 2. It will start a saga which is waiting for the the next message. Once it receives Message 1, the saga will apply Message 1 and then Message 2 and release the saga state. If using version for sequencing isn't acceptable, another sequencing strategy can be used.

0
votes

According to this you should ask yourself:

What is the business impact of having a failure?

In your current case, you have this problem once by one million requests. I don't think it would have a huge impact on the business if you'd accept both of the requests as valid.