Assuming that one event stream is a transation boundary and aggregate is a write-model to enforce invariants, can i have two or more aggregates dedicated to one event stream? If one big aggregate is not an option by perfomance or overcomplicated design reasons?
For example i have following domain model represented by one event stream:
{
"Products": [{
"Id": 1,
"Name": "Call-Centre",
"Services": [
{
"Id": 10,
"Name": "CloudStorage",
"Price": 250,
"Discount": 100
}
]
}]
I need to enforce two invariants. First: services per product must be unique (by name). Second: service discount value can't be more than actual price. Now i consider to use two aggregates: ProductAggregate which contains Services collection for enforcing first invariant; ServiceAggregate which contains only Service info for enforcing second one. Are there some pitfalls in my solution?
In general you can't nest aggregates within other aggregates. They can refer via the root to the other aggregate. You can make all access to a given aggregate be through another aggregate.
For instance the product aggregate might be like:
{
"Id": 1,
"Name": "Call-Centre",
"Services": {
"CloudStorage": 10
}
}
And a service aggregate might be:
{
"Id": 10,
"Name": "CloudStorage",
"Price": 250,
"Discount": 100
}
If services are actually children of a product (CloudStorage
for product 1 is different from CloudStorage
for product 2), then you can encode that by having a Product
field in service (and very likely have a domain constraint that once a service is associated with with a product, the Product
field in the service cannot be changed). Operations involving the relationship may turn into sagas (e.g. adding a service to a product or updating a service name). It may also be worth encoding that relationship into the service ID, though that might be easier if the ID can be a string or something else.
Because they're different aggregates, they're conceptually different event streams (because aggregates define transaction boundaries at least as much as event streams do). It may be useful to project the 2 event streams into one: that projection is likely able to properly order events between the two source streams. The logic for such ordering is going to be dependent on the domain (e.g. knowing which operations could be interleaved and which can be assumed to not be (e.g. because they're coordinated by a saga)).