I'm looking for some guidance on implementing a process manager.
It should manage an action on a batch of aggregates, where those actions may be a long running process themselves.
I'll use a representative example to try and provide some detail and clarity.
Example:
Imagine a system with a Customer aggregate, and many Contract aggregates. When a customer's credit rating changes, the customers contracts are all changed to pending (eventually consistent long running batch process), at which point they will be re-signed and re-confirmed with the customer (a long running process). Let's add the assumption that a contract references a customer by identity, and this relationship can never change.
1) Question on the Batch Update:
Typically in a CQRS system, we only retrieve an aggregate via its identifier (GUID). In fact, this is necessary if we switch to event sourcing. However, when the customer's credit rating changes, we need to act on all their contracts, so we need someway to make that connection.
Everything begins with a ChangeCustomerCreditRating command. Then how do we go about this?
Option One:
The customer entity would raise a CustomerCreditRatingChanged event. Then a CreditChangeProcessor would listen for that event.
It could then invoke a RevokeCustomerContracts command, including the customer identifier. However, now my contract repository would need a method to retrieve all contracts based on their customer ID. This is likely safe, as a contract's customer ID can't change, but flies in the face of the typical guidance in CQRS to retrieve aggregates (contracts in this case) by their ID only.
This command would now also be responsible triggering the process for setting the pending / revoked state on all those contracts. Violating the guidance of updating one aggregate per command. In "Implementing Domain-Driven Design Vaughn Vernon" does indicate this may be acceptable in this use case in the Aggregates chapter under Reasons to Break the Rules (Reason One: User Interface Convenience).
Option Two:
On changing a customer's credit rating, the UI would display the Customer's contracts, at which point the user would start a RevokeCustomerContacts command, that includes the identifiers of all the customer's contracts.
Retrieving the contracts for a customer has now be offloaded to the read model.
Essentially we don't really have a process manager anymore, just a Splitter, creating individual RevokeCustomerContract commands from the monolithic RevokeCustomerContacts command. Each of these commands would result in the contract raising a CustomerContractRevoked event. I'm not a fan of this as it's just making the process more manual.
Option Three:
On receiving the CustomerCreditRatingChanged event, the process manager either queries the read model via a service to get the identifiers of all the Customer's contracts, or the process manager maintains an internal mapping of customers -> contracts that it privately stores (built from previous events). It then calls a RevokeCustomerContract command for each contract. Eventual consistency should not be a concern in this use case.
Any other guidance on a batch update situation like the above?
2) Question on the Long Running Process CustomerContractRevoked event starts:
As mentioned, when a contract goes into a pending state due to being revoked, there is a long running process where the contract is re-signed and re-confirmed. Would you advise a single process manager throughout the full lifecycle (credit rating change -> batch change of contract state to pending -> monitor contract state through to re-confirmation for each contract):
CreditChangeProcessor
Or two process managers, one that handles the batch update of the contracts into a pending state, and another that, upon a contract entering a pending state, begins the process to re-confirm the contract:
CreditChangeProcessor
ContractConfirmationProcessor
Where the ContractConfirmationProcessor acts upon a single contract. And would be started by receiving one of the many CustomerContractRevoked events raised.
I lean towards the second, as they both have different reasons for existing (one to enable a batch of changes, the other because of a temporally long running process). The CreditChangeProcessor could always maintain an internal state of all the individual contracts, and listen for a final CustomerContractReconfirmed for each contract. It would then be monitoring the overall process, but not the individual steps within each contract reconfirmation.
Would appreciate any experienced guidance? Any challenges when one process manager results in another beginning.
I think it's the batch aspect that is causing the most significant challenges to my understanding. Any proposed approaches would be appreciated.