In our current legacy system, we have something called a Specification. It is composed of various parts (eg. hazards, shelf-life, analyticals, microbiologicals, etc…). While they are all part of a single Spec, I think each “area” could be it’s own aggregate (eg. SpecificationShelfLifes, SpecificationHazards, etc…) So, if I have an actual Specification aggregate which handles the CreateSpecification command and generates a SpecificationCreated event, is there anything inherently wrong with having these other aggregates handle the Created event as well? Basically, Specification has both CommandHandler/EventSourcingHandler while the others only have the EventSourcingHandler.
Can you explain your case more, please?
I presume that you have one aggregate root (Specification) with members/entities (hazards, shelf-life, analyticals, microbiologicals) at the moment and that you want to break this down to smaller aggregates. Is this correct?
Can you elaborate relation between these entities? Is it more HAS association or IS (inheritance)? I’m trying to understand why would other aggregates subscribe to Created event.
Sure. A Specification can be like a recipe. It has an identifier, name, category, owner, etc… It could also have various Shelf Lifes. Each Shelf Life entry under the Spec specifies the label (eg. Days Since Packing, Total Shelf Life, etc…), the duration, whether it’s frozen, refrigerated, etc… and the temperatures. Entries may be added/removed/changed. But, the labels must be unique. So, the scope of a transaction is really around all shelf life entries, and not necessarily the Spec or each individual entry. Commands cannot be issued against a non-existent spec. So, I guess I’m trying to keep the boundaries small for something that might not be so small. If 2 commands were issued to change Shelf Life entries at the same time, I think they should be consecutive. But, if 1 command is for shelf life, and another for hazards are issued at the same time, then I think they should be able to be handled concurrently. In any case, their ability to be carried out depends on the existence of the spec. Does this make sense?
The question is how small/big our aggregates should be This is not easy, I admit.
The aggregate root is a transaction boundary. Having said that:
- bigger aggregates have more chances to reach the conflict, as you already mentioned (shelf life, and another for hazards are issued at the same time, then I think they should be able to be handled concurrently). The conflict resolution feature of Axon Framework could be used to relax this. There are some examples of conflict resolution here - smaller aggregates do not have this problem
- invariants between entities (1.shelf life can be created for existing Spec only; 2.label has to be unique) is easier to manage in bigger aggregates (under the same aggregate root) - invariants between entities/aggregates is harder to manage once your aggregates are small. In this case, you have to implement Saga pattern to keep these small aggregates eventually consistent (this can be tricky sometimes as you have to think about compensation logic if something goes wrong).
Your case justifies the usage of a bigger aggregate as you have clear invariants between your entities in my opinion. Nevertheless, I would suggest trying to break it into smaller aggregates as an experiment of yours. This will give you a better feeling if the introducing Sagas to keep your invariants between different aggregates (that are eventually consistent) worth the effort. To implement Saga pattern between two aggregates you also have to explicitly or implicitly introduce PENDING state on your small aggregates (eg. adding_hazard_to_spec) in order to implement Isolation (ACID).