I am going to propose a mechanism from the Functional Domain modeling world. And I am going to offer an example usecase, although it would have been easier if you had given a concrete example. I apologize in advance for putting the code in Python if you are not familiar with the language, but in my defense, it almost looks like pseudo-code :)
- Aggregate root has a state
- Messages from RabbitMQ contain state and become commands
- Order of commands is not guaranteed
- Domain rules dictate whether a command should be accepted or not
- The reason to control order is to produce a consistent, repeatable state of the aggregate root (Otherwise, it will end up in a different state even with the same set of messages)
Let's consider a Loan Aggregate. Assume that a user
applies for a loan, the loan might be
enriched with additional documentation, and then either
Applying your problem statement, a loan can be
enriched only if it has been
approved only if it has been either
enriched, and so on.
Typically, you would model the state as an attribute of the loan, like so:
state = STATUS.APPLIED.
Then you would have to write rules like:
if command.state == COMMAND.ENRICH and loan.state == STATUS.APPLIED:
Instead, you could model the state as a concrete type, like below:
_state_ = STATUS.NONE
# Lot more attributes
_state_ = STATUS.NEW
_state_ = STATUS.APPLIED
_state_ = STATUS.ENRICHED
_state_ = STATUS.APPROVED
_state_ = STATUS.REJECTED
_state_ class variable is redefined in each child class to the appropriate status. You would then have a factory method that reconstitutes the loan object from datastore:
record = find_loan(application_number)
if record['state'] == 'applied':
elif record['state'] == 'enriched':
The command is then passed to the appropriate process method for execution:
loan_object = construct_loan(command['application_number'])
if command['state'] == 'enrich':
elif command['state'] == 'approve':
Now, system throws a runtime error "NotImplementedError" whenever a command cannot be processed because of state inconsistencies.
You can either ignore them, in which case, you will have a
no-op if the wrong command is applied. Or you can bubble up an error, in case this is a situation that is not expected to happen.
You can even store the command in a data store for later processing, in case you expect to receive messages jumbled up, but coherent as a whole.