Can we really achieve eventual consistency?

Hi there,

I want to embrace the eventual consistency, but I worry that it may not be possible (without introducing too much complexity ).

Using Axon as CQRS framework, the inconsistency may occur : (1). between Query and Command , or (2) occur in Command side only when multiple commands need to collaborate with each other and one of the command get failed .

For case (1), the eventual consistency is possible by using RetrySchdule or replaying the events and keep the event handler idempotent.

For case (2), it seems hardly possible . For example, a process needs the collaboration of aggregrate A,B and C. The business sequence is(not a long business) : commandA->commandB->commandC , what if commandC get failed ? In this case only commandC get rollback and commandA and commandB won’t as command is the transaction boundary in Axon. To solve the inconsistency, commandC can send compensation commands to A and B, but what if the version of A or B is NOT the same version as before ? the compensation command cannot continue in the case. Unless Axon keeps lock on A & B even after command A & B get finished , but I think it’s not true. For simple command bus maybe true, I’m talking about asynchronous command bus here. Whatever use Saga or not, it’s the same situation I guess.

Keeping both of the commandA and commandB are readonly command , or introduing certain distributed lock for the aggregrates’ collaboration can make sure the consistent.

I’m just starting to use Axon , probably there’s somthing I miss. What’s the axon’s suggested way to handling the case(2) ?

Regards,
Sean

Hi Sean,

I believe that the case (1) is already clear to you.

The number (2) is more difficult:

2.1 You can consider merging these three aggregates into one under the same transaction. This will make things immediately consistent. But I guess this is not your case.
2.2 You can use Saga pattern to connect these aggregates in BASE transaction. In this case, you should treat your aggregates as individual state machines and introduce continuing states (for example enum.PAYING). This will enable you to implement Isolation (ACID) on the aggregate level. In this case A and B will be locked in this state (enum.PAYING) noting that the process is still on. Once the C finishes the process it will publish event notifying A and B to enter the enum.PAID state for example.

This way your aggregates can handle commands only in a specific order. While you A and B are in this continuing states they will not be able to handle just any command from the client side. If this happens you will get an exception (or event) from which you should recover from the client-side.

Best,
Ivan

Hi Ivan,

Thank you for the quick reply. I was also thinking it over last night after the post and got that putting ‘reservation’ on A and B before finish the process can help to be consistent. But your reply is more clear , the ‘state machine’ is the clear concept to solve the problem.

One more thing, since compensation command of C may also fail because of non-business reason, like network error. The PAYING state may always remain in the system. So once introduce ‘state machine’ , there could be orphan states in the system, Saga’s DeadlineHandling could be the automatic approach to solve these state, but It’s better to make the state visible to the end user I think , so that end user can decide how to handle these orphan state manually as well.

Regards,
Sean

I guess this depends on your use case. Make sense to inform the client/user. You can also have both options included, so Deadline expires if the user does not fix the error on time.

Best,
Ivan