Home > Aggregates

Aggregates

What is an aggregate?

A larger unit of encapsulation than just a class. Every transaction is scoped to a single aggregate. The lifetimes of the components of an aggregate are bounded by the lifetime of the entire aggregate.

Concretely, an aggregate will handle commands, apply events, and have a state model encapsulated within it that allows it to implement the required command validation, thus upholding the invariants (business rules) of the aggregate.

What is the difference between an aggregate and an aggregate root?

The aggregate forms a tree or graph of object relations. The aggregate root is the "top" one, which speaks for the whole and may delegates down to the rest. It is important because it is the one that the rest of the world communicates with.

I know aggregates are transaction boundaries, but I really need to transactionally update two aggregates in the same transaction. What should I do?

You should re-think the following:

  • Your aggregate boundaries.
  • The responsibilities of each aggregate.
  • What you can get away with doing in a read side or in a saga.
  • The actual non-functional requirements of your domain.

If you write a solution where two or more aggregates are transactionally coupled, you have not understood aggregates.

Why is the use of GUID as IDs a good practice?

Because they are (reasonably) globally unique, and can be generated either by the server or by the client.

How can I get the ID for newly created aggregates?

It's an important insight that the client can generate its own IDs.

If the client generates a GUID and places it in the create-the-aggregate command, this is a non-issue. Otherwise, you have to poll from the appropriate read side, where the ID will appear in an eventually consistent time frame. Clearly this is much more fragile than just generating it in the first place.

Should I allow references between aggregates?

In the sense of an actual "memory reference", absolutely not.

On the write side, an actual memory reference from one aggregate to another is forbidden and wrong, since aggregates by definition are not allowed to reach outside of themselves. (Allowing this would mean an aggregate is no longer a transaction boundary, meaning we can no longer sanely reason about its ability to uphold its invariants; it would also preclude sharding of aggregates.)

Referring to another aggregate using a string identifier is fine. It is useless on the write side (since the identifier must be treated as an opaque value, since aggregates can not reach outside of themselves). Read sides may freely use such information, however, to do interesting correlations.

How can I validate a command across a group of aggregates?

This is a common reaction to not being able to query across aggregates anymore. There are several answers:

  • Do client-side validation.
  • Use a read side.
  • Use a saga.
  • If those are all completely impractical, then it's time to consider if you got your aggregate boundaries correct.

How can I guarantee referential integrity across aggregates?

You're still thinking in terms of foreign relations, not aggregates. See last question. Also, remember that just because something would be a two tables in a relational design does not in any way suggest it should be two aggregates. Designing in aggregates is different.

How can I make sure a newly created user has a unique user name?

This is a commonly occurring question since we're explicitly not performing cross-aggregate operations on the write side. We do, however, have a number of options:

  • Create a read-side of already allocated user names. Make the client query the read-side interactively as the user types in a name.
  • Create a reactive saga to flag down and inactivate accounts that were nevertheless created with a duplicate user name. (Whether by extreme coincidence or maliciously or because of a faulty client.)
  • If eventual consistency is not fast enough for you, consider adding a table on the write side, a small local read-side as it were, of already allocated names. Make the aggregate transaction include inserting into that table.

How can I verify that a customer ID really exists when I place an order?

Assuming customer and order are aggregates here, it's clear that the order aggregate cannot really validate this, since that would mean reaching out of the aggregate.

Checking up on it after the fact, in a saga or just in a read side that records "broken" orders, is one option. After all, the most important thing about an order is actually recording it, and presumably any interesting data about the recipient of the order is being copied into the order aggregate (referring to the customer to find the address is bad design; the order was always made to be deliverd to a particular address, whether or not that customer changes their address in the future).

Being able to use what data was recorded in this broken order means you've a chance to rescue it and rectify the situation - which makes a good bit more business sense than dropping the order on the floor because a foreign key constraint was violated!

How can I update a set of aggregates with a single command?

A single command can't act on a set of aggregates. It just can't.

First off, ask yourself whether you really need to update several aggregtes using just one command. What in the situation makes this a requirement?

However, here's what you could do. Allow a new kind of "bulk command", conceptually containing the command you want to issue, and a set of aggregates (specified either explicitly or implicitly) that you want to issue it on. The write side isn't powerful enough to make the bulk action, but it's able to create a corresponding "bulk event". A saga captures the event, and issues the command on each of the specified aggregates. The saga can do rollback or send an email, as appropriate, if some of the commands fail.

There are some advantages to this approach: we store the intent of the bulk action in the event store. The saga automates rollback or equivalent.

Still, having to resort to this solution is a strong indication that your aggregate boundaries are not drawn correctly. You might want to consider changing your aggregate boundaries rather than building a saga for this.

What is sharding?

A way to distribute large amounts of aggregates on several write-side nodes. We can shard aggregates easily because they are completely self-reliant.

We can shard aggregates easily because they don't have any external references.

Can an aggregate send an event to another aggregate?

No.

The factoring of your aggregates and command handlers will typically already make this idea impossible to express in code. But there's a deeper philosophical reason: go back and re-read the first sentence in the answer to "What is an aggregate?". If you manage to circumvent command handlers and just push events into another aggregate somehow, you will have taken away that aggregate's chance to participate in validation of changes. That's ultimately why we only allow events to be created as a result of commands validated by a command handler on an aggregate.

Can I call a read side from my aggregate?

No.

How do I send e-mail in a CQRS system?

In an event handler outside of the aggregate. Do not do it in the command handler, as if the events are not persisted due to losing a race with another command then the email will have been sent on a false premise.

   Copyright © 2014 Edument AB Contact Us   Edument