1

Our subdomain has two primary aggregate types: Locations and pallets. Each location keeps track of how many pallets it holds, and each pallet can only be placed on one location at a time. Assume that there are many instances of each type and their associations change often, so it is not feasible to put all of them into one common parent aggregate.

Consider this solution (only domain model shown):

public class Pallet : Aggregate
{
  public int LocationId { get; private set; }

  public void PlaceOnLocation(int locationId)
  {
    this.LocationId = locationId;

    this.emitDomainEvent(
      new PalletPlacedOnLocationDomainEvent(this.Id, locationId)
    );
  }
}

public class Location : Aggregate
{
  public int Load { get; private set; } = 0;

  public void OnPalletPlaced(PalletPlacedOnLocationDomainEvent event)
  {
    if (event.LocationId != this.Id) throw ArgumentException(); // Precondition

    this.Load++;
  }
}

Not shown here are the application layer handlers that handle transactions as well as load and save the aggregates.

Note that instead of OnPalletPlaced(...) I could have used an IncreaseLoad() method on the location. In this case, however, I would have introduced two issues:

  • Leaked the relationship between the domain event and its effect from the domain layer to the application layer.

  • Opened up the possibility for another (illegal) use case that increases the load without actually placing a pallet on that location.

Is it a valid approach to handle domain events directly in the aggregate like this?

Arnold Schrijver
  • 3,588
  • 3
  • 36
  • 65
domin
  • 1,192
  • 1
  • 7
  • 28
  • Are Pallet and Location actually 2 separate aggregates if their so tightly related? Usually, I would put a process manager in between 2 aggregates that would consume an event from one aggregate and raise a command towards the other one. – hgulyan Apr 28 '21 at 20:33
  • There are many locations and pallets in the system, and their associations change often. So putting all of them into one common aggregate is not really feasible. Regarding your approach: How could you prevent the direct usage of the second command then (assuming this command only makes sense in response to an event)? – domin Apr 28 '21 at 20:37
  • The same way as you can't prevent raising PalletPlacedOnLocationDomainEvent event from another part of application :) Location aggregate should be able to verify it's consitency internally. Question is which aggregate is responsible for keeping track of number of pallets in a certain location? It looks to me in your scenario both of them are. – hgulyan Apr 28 '21 at 20:41
  • At least I can only raise the event from within the domain layer which assumes the author „knows what he‘s doing“. On the other hand, a command is callable from anyone outside the domain layer, leading to an invalid usage. I would say Location is eventually consistent since it will eventually receive the event. However, if I also would need to check a max capacity on a Location, I‘d be in trouble. Which, to be honest, is a likely requirement… ;) – domin Apr 28 '21 at 20:49
  • If you need to check for max capacity, I'd turn responsibilities around -> 1st allocate a spot (increase load) on Location aggregate -> then place it on Pallet aggregate. Is your question about how to design aggregates or how to secure them? Answering your question from the post, no handling events is usually not a good approach, as you can't reject them, a command on the other hand is a request for a change, which can be rejected in case some of the validation rules fail. – hgulyan Apr 28 '21 at 20:56
  • The question is more about the validity of the practice of handling events directly on aggregates in order to preserve the entire domain knowledge in only the domain layer. I‘ve rarely seen this in the literature about DDD, that‘s why I wonder what the issue with it might be. – domin Apr 28 '21 at 21:06
  • I'd start with exploring the definition of aggregate. It's a unit of consistency around some set of business rules. If all it does is storing a counter, it doesn't look like an aggregate to me. – hgulyan Apr 28 '21 at 21:17
  • You are right. I tried to simplify the example in order to highlight the event handling aspect of it. It seems to be some general guideline to not handle events like this, and I can‘t see the abstract/general reasoning that goes against it. – domin Apr 28 '21 at 21:39
  • 1
    You might find this resource helpful http://cqrs.nu/Faq/aggregates – hgulyan Apr 28 '21 at 22:07
  • 1
    Cool link, thanks. Quote "an aggregate will handle commands, **apply events**, and have a state model encapsulated within it ...". But later: "Can an aggregate send an event to another aggregate? **No.**". Probably, the latter means directly dispatching an event from one aggregate to another (violates the boundary), the former would allow it if the event takes the usual route through the application layer or if it is event-sourced. – domin Apr 29 '21 at 07:18
  • 1
    Exactly! Usually commands and events would be grouped under one aggregate id, especially in event sourced scenario (to be able to apply to load current state of the aggregate). You'd need to have a translation layer between 2 aggregates. event -> command -> event – hgulyan Apr 29 '21 at 11:14
  • The thing I still struggle with is this: If the translation happens only outside the domain layer, essential domain knowledge is lost. If, on the other hand, one would be allowed to forward a domain event from the application handler into an aggregate method, this method would then internally call one or many other **commanding methods** on itself (also private ones maybe), stating the fact that we are handling a domain event explicitly in the domain model. – domin Apr 29 '21 at 11:27

2 Answers2

1

An aggregate has the responsibility of maintaining the invariants required by the business logic (e.g. limits on how many pallets are in a given location or a prohibition on pallets containing certain items from being placed in a given location).

However, a domain event represents a fact about the world which the aggregate cannot deny (an aggregate could ignore events if they have no meaning to it (which is not a question of validation, but of the type of event: the aggregate's current state cannot enter into it)). If an aggregate handles domain events, it therefore should do so unconditionally: if the business rule the location aggregate enforces is that there cannot be more than 20 pallets in a location, then a domain event which effectively leads to there being 20 thousand pallets in a location means that you have 20 thousand pallets in that location. In short, this means that the only domain events which should be handled as domain events by an aggregate are those domain events which were validated against the aggregate before they were emitted. It's only really in event-sourcing or event-sourcing-adjacent approaches where you would see domain events being processed in the context of an aggregate.

This doesn't preclude recognizing that one component's event can be another component's command. The domain event could be treated as a command and be rejected or itself result in more domain events (e.g. "there are too many pallets in location XYZ!").

Levi Ramsey
  • 18,884
  • 1
  • 16
  • 30
  • Thank you! Let‘s say one aggregate‘s event is another’s command. Is it correct to place this translation outside the domain layer? At the very least, I would introduce a domain event handler (in the domain layer) that does this translation. This wouldn’t eliminate the need for a wrapping application handler that simply delegates the event and loads/saves the aggregates though. – domin Apr 29 '21 at 05:22
  • Depending on the use case and domain model, an aggregate can easily deal with an event that happened somewhere else. The handler method (on the aggregate) can perform whatever validation and decision-taking-logic it needs to handle the event properly. It could very well just do nothing with if it decides so. The crucial observation is simply that in the ubiquitous language, some aggregates know about the event, whereas in the event-command-translation happening in the app layer, it doesn't, which leads to an *incomplete domain model*. – domin Apr 29 '21 at 07:39
  • 1
    I would tend to say that mapping domain events to commands for an aggregate in the same domain can be part of the domain layer (though to be fair, my perspective is heavily influenced by implementing DDD/CQRS/ES in actor-model systems, where the application layer is so thin that the domain layer is nearly the entire system). For events from other domains, that translation is outside of the domain. – Levi Ramsey Apr 29 '21 at 13:52
1

I don't know the domain, so I'll just mention some considerations. Many different choices may be valid depending on your use case and architectural design.

Assume that there are many instances of each type and their associations change often, so it is not feasible to put all of them into one common parent aggregate.

Having two separate aggregates in this subdomain may be warranted, but it also depends on how you sliced the subdomains in your Context Map. If you are in the transportation sector, your core domain may be 'Inventory Management' and your aggregate root is then the total Inventory, while Pallets and Locations are just domain entities. The inventory thus becomes the coordinator you can use.

There may be other subdomains involved in the Context Map where these entities also play a role, but in their own Bounded Contxt, e.g. Order Shipping (tracking the Pallets of an Order as they move to the Customer) and Warehouse Management (capacity planning of a single Location).

Is it a valid approach to handle domain events directly in the aggregate like this?

I would in general avoid it. In my own DDD architecture I use a Ports & Adaptors design and CQRS (and considering an actor model, where the actors reside in the Application layer). I don't use Event Sourcing or Microservices, but want to retain the option to easily add them later, if need arises (i.e. they are optional).

Now, why would I avoid this design? First of all you have created a tight coupling between two aggregates. What if my warehouse facilitates many different types of storage besides pallets? It may be okay if that's certainly not the case, and both aggregates really belong in the same subdomain. But what if you need to handle events from different subdomains?

In my design I set things up more or less like this:

  • An Adapter endpoint (e.g. REST API) creates a Command or Saga and invokes the Application Layer
  • Application layer handles Commands and Sagas. The handler contains the plumbing needed to invoke the Domain appropriately (validates command, loads aggregate from DB, etc.)
  • Domain layer contains Aggregate (usually 1 per subdomain), Entities, Value Objects and Events.
  • Aggregate, of course, is where stuff happens: the single point of entry. I just invoke methods and publish events here.
  • Event handling is not part of the domain layer. I have a Publish/Subscribe mechanism in the Application layer, and it is used in the Adapters (e.g. to trigger a Saga and issue a follow-up Command).
  • The aggregate methods either return nothing in most cases, or may return a value / raise an exception if this can be easily dealt with in the command handler and is not directly domain-related (in which case a domain event is more appropriate).

From @levi-ramsey answer:

If an aggregate handles domain events, it therefore should do so unconditionally: if the business rule the location aggregate enforces is that there cannot be more than 20 pallets in a location, then a domain event which effectively leads to there being 20 thousand pallets in a location means that you have 20 thousand pallets in that location.

Indeed. But this should be impossible as your domain shouldn't allow this to happen. Hauling back to your subdomain design and context map, it is interesting to consider whether it is complete.

In reality you don't drive to a warehouse, drop off your pallet and leave, and then let the workers at that location deal with the problem that the warehouse is at full capacity.

If you'd do Event Storming you may find that a Saga is involved to coordinate the whole process. You'd most likely e.g. "receive (or request) a freight letter from HQ telling you which Location to visit, that has storage space already reserved." (Note: there's probably Ubiquitous Language in this sentence).

A whole bunch of concepts may flow from this:

  • FreightDelivery saga
  • Inventory.ReserveStorageSpace command
  • StorageSpaceReserved event, with a LocationID set
  • StorageUnit.PALLET enum
  • ... etcetera

Looking at this now, with the insights I gleaned from quickly typing this answer, I now wonder the following: Should Pallet be an aggregate root? Should Pallet have a PlaceOnLocation method? A pallet doesn't place itself when you command it to. It is not an actor. Maybe Pallet should just be a regular domain entity that has a Location property which is updated as it is moved around.


I hope you found this useful. Once again I want to stress that there's no one good solution, there are many variations that make perfect sense :)

Arnold Schrijver
  • 3,588
  • 3
  • 36
  • 65
  • Yeah I find it useful, thanks a lot. Isn‘t the „follow-up command“ that you create in your event handler crucial to the domain logic? In event storming, we place command notes directly next to events, and this association can only be known by being a domain expert, which implies that the handler should be in the domain layer (aggregate). This creates a coupling between aggregates, true. But why is this a problem? Shouldn’t we *want* this coupling if it is there in the real world (subdomain) as well? – domin Jun 30 '21 at 22:46
  • In the event storming this is indeed a crucial insight provided by the domain expert, but the event handler can be anywhere. Events are anonymous facts of something that already occurred for anyone interested to act upon. The events thus provide loose-coupling, while commands lead to tight-coupling that make the code more complex and harder to test/debug as your application evolves. I scope command handlers to subdomain application layer that only ensure that domain entities are invoked correctly and with valid, complete data present. – Arnold Schrijver Jul 01 '21 at 06:25
  • I don't agree that events lead to loose-coupling. Events just reverse the dependency, i.e. the subscribing entity depends on the publishing entity's event. This is very much the same as a caller who depends on a callee's contract (i.e. interface). So by first subscribing to an event in the application layer, and then forwarding the call as a command to an aggregate, you basically have a coupling nonetheless, even if it isn't direct. If you change the receiving aggregate, you might not be able to appropriately handle the event anymore, i.e. you broke because of the hidden coupling. – domin Jul 01 '21 at 08:01
  • So a "handler listening to event X, transforming event X into command Y, and dispatching command Y to entity B" is the same in terms of coupling as "handler listening to event X, passing X directly into to entity B to handle". That is, **IF you don't apply any business logic during the transformation step**. But IF you apply business logic there, you'd leak it into the application layer where it doesn't belong. The right way to do it then would be to introduce a **domain service** which transforms the event X into command Y and dispatches to the aggregate. – domin Jul 01 '21 at 08:10
  • Correction for the comment two steps above: I meant "[...] If you change the **emitted event**, you might not be able to appropriately handle it (i.e. transforming it into a sensible command for entity B). [...]", thus you'd probably need to adapt entity B as well, exposing the hidden coupling. – domin Jul 01 '21 at 08:15
  • Yes, emitted events are very much part of the domain, and you become dependent on how you define them (i.e. they are domain concepts). The _handlers_ of said events are a different matter however. How this works depends on your architecture decisions, but there may be a Pub/Sub mechanism, eventbus, or whatever where anyone with an interest can handle them. This may be in same subdomain in infra or app layer, or another subdomain or even remote services. There's a nice webinar about this.. I'll check if I can find it and then I'll add below. – Arnold Schrijver Jul 01 '21 at 08:32
  • Re:"subscribing entity depends on the publishing entity's event". In my domain there is no subscribing entity. The subscription lives in another layer, independent of the domain, and the entity only cares for its encapsulated state and related entities in case it is an aggregate. – Arnold Schrijver Jul 01 '21 at 08:35
  • OK, I guess we talk past each other a bit. I agree that the subscription resides in the application layer. But this is for *technical reasons* only. Since handling an event invokes other aggregates, those aggregates must be loaded first. Also, the event itself must be validated and correctly deserialized. Now you *still* have two options: Do you pass the event X as an event to a `.HandleX(...)` method in the aggregate, or do you directly call a commanding method `.DoY(...)` (where Y is some prescriptive verb) on the aggregate? In the latter case, you transform X into Y, involving domain logic. – domin Jul 01 '21 at 08:43
  • Ha, yes it's hard to express well in these short comments. I use the latter design, and there are no events passed into an aggregate. PS. Couldn't find exact video, but [Actors or Not: Async Event Architectures](https://www.youtube.com/watch?v=FM_wuZj83-8) at 45.40min. recommends "stay away from Commands unless you really need them". Other than that [Designing Events-First Microservices](https://www.youtube.com/watch?v=1hwuWmMNT4c) by Jonas Bonér is interesting, even if not doing microservices. – Arnold Schrijver Jul 01 '21 at 08:59
  • Thanks for the pointers. I am pretty much aware of the principles discussed there (intent vs. fact etc.). Events just invert the dependency, that's all. Sometimes, it is right to react to things happening, other times its right to ask someone to do something explicity. Dependencies exist in both scenarios. The question I have is entirely unrelated to this discussion. It is only about the internals of a microservice: Do you **transform an event to a command** *outside* the domain layer (aggregates, domain services), or *inside* of it, and why? :) – domin Jul 01 '21 at 09:16
  • The answer I always hear is: "Do it outside of the domain layer". But I am missing the arguments. I subscribe to events outside, for sure, but since transforming events to commands involves pure domain expert knowledge, I want to put this into the domain layer where this kind of logic belongs. I also need to clarify that we have much more events/aggregates inside the same bounded context (not just 1), and most of those events are internal anyway. – domin Jul 01 '21 at 09:22
  • For example: If some aggregate emits an event `OrderCreated`, and a domain expert tells us that this should lead to the reservation of a product, which can be done by calling a method `.ReserveProduct(...)` on some other aggregate. Now, if I place the translation between `OrderCreated` event and the `ReserveProduct` command somewhere outside the domain layer, I have violated the principle of hexagonal architecture, which states that domain logic only resides in the domain layer (the innermost part of the hexagon). Assume all of this is in the same bounded context for simplicity's sake. – domin Jul 01 '21 at 09:36
  • My answer would be: It depends. If `ReserveProduct` means placing a sticker with the OrderId on it, and they are in the same bounded context then it may be okay if the Product is aware of the existence of a ProductId. Product and Order have some tight coupling, but it may be fine. But if I want to reserve products for other reasons, not related to an order (reserve for high XMas demand next month, reserve for other warehouse where its out of stock) then I have an issue. Do I also want to incorporate these concerns in my Product entity? I get ever more tight coupling and less flexibility. – Arnold Schrijver Jul 01 '21 at 09:54
  • The decisions all come with tradeoffs. If you know 100% you don't need the extra flexibility then go for it. In general I see Events (published by an aggregate) as part of the domain, and Commands not (they invoke method(s) on the aggregate from app layer). Commands are invoked from endpoints (ports & adaptors) and also from Sagas when more complex process flows are involved. – Arnold Schrijver Jul 01 '21 at 09:58
  • The term *command* is overloaded here. There are *command methods* on aggregates (plain old void class method) but also *command objects* (DTOs known to the application layer). The concept is the same, just on another level. So an event handler method implementation on some aggregate could just delegate to a command method on the same aggregate, effectively doing the aforementioned transformation. The called command can of course also be called directly for a completely different use case. The crucial point is that the transformation is in the domain layer, in this case in the aggregate. – domin Jul 01 '21 at 10:58
  • The tradeoff seems to be: Do you couple an aggregate directly to a domain event while keeping everything business-related in the domain layer, or do you place the event-command-transformation outside, removing at least the *direct* coupling but **leaking domain logic** (i.e. the transformation) into the application or even adapter layer? If a transformation takes an event into more than one call to more than one aggregate, potentially conditioned on some predicate over the event, we surely would introduce a domain service to do it, as this involves domain decision logic (i.e. policy). Right? – domin Jul 01 '21 at 11:07