Repository navigation
Aggregates
In short, an aggregate is a cluster of one or more associated objects that are treated as a unit for the purpose of data changes. An aggregate boundary defines what objects are considered within the aggregate, while the objects outside of the boundary are not in the aggregate. Each aggregate has a root entity known as the aggregate root.
- The root is the only member of the
aggregatethat outside objects are allowed to hold references to. Objects within the boundary can hold references to one another without restriction.- By "hold reference", I mean defining a property on an outside object of an object within a different aggregate.
- The root of the
aggregateand hand out references to internalentitiesto objects outside of theaggregate, but those those references must treated as transient and cannot be associated with those outside objects.- To clarify, a transient reference is the usage of a reference for a single operation, which is then discarded after completion.
- The
aggregate roothas global identity and is responsible for enforcing and checkinginvariantswithin theaggregate.- An
invariantis a consistency rule that must be upheld whenever data within the aggregate changes.
- An
-
Entitieswithin theaggregatethat are not theaggregate rootonly have local identity; this corresponds to the restriction that no outside object can ever see theseentitiesout of the context of theaggregate root. - Objects within an
aggregatecan hold references to otheraggregate roots. - Only
aggregate rootscan be obtained directly by database queries. The other objects within anaggregatemust be accessed via traversal through theaggregate root. - In the situation where deletion of an
aggregateis desired, all objects within its boundaries must also be deleted. - The
aggregate rootcontrols all access to the objects within theaggregate.
It is clear that Target is modeled as an entity in our domain model, as there is an absolute need to distinguish the identity of the various recipients of notifications regardless of the many changes they may sustain. For example, if a recipient of a notification ended up changing their name, or perhaps they changed their phone number, we must identify that through those changes that we are interacting with the same Target.
It is also just as clear that Target cannot sufficiently be a non-root member of an aggregate, as it's lifecycle is not intuitively relative to another object. In other words, if we attempted to place Target with the Notification aggregate, we would discover that we would not be able to delete a Target if a Notification were deleted as many notifications can be sent to the same Target. Deleting the Targets would leave other Notifications associated to the same Target in an invalid state. As another example, if we experimented with Target as existing within the Audience aggregate, we would discover the same; a Target can be associated with many audiences; which suggests its lifecycle is independent of other objects.
In addition to the evidence of Target having an independent lifecycle, it would be natural to observe how Target would be omitted from updates (not just deletions) on other objects it is related to in the model. For example, if Target were a non-root member of the Audience, we would not want to update the state of each Target within the Audience object when the focus was originally to update the state of the Audience. Typically an update on an Audience may be to alter the name of the Audience, or add/remove Targets from the Audience. There wouldn't be a circumstance where we would desire to actually change the state of the Targets within an Audience, say, the name of the Target when updating the Audience as a unit. The concept applies if Target were placed in the Notification aggregate as well.
Lastly, a positive side-effect of Target being promoted to an aggregate root is that it can be directly queried. More importantly, the realization that Target should be an aggregate root also leads us to discover that it doesn't make sense to be able to create a Notification without first knowing its Audiences or Targets; being able to query for Targets would enable the application to associate existing Targets to Notifications, as well as understand whether new Targets need to be constructed in order to construct a Notification.
There is the absolutely need to be able to distinguish the identity of a Notification in our application, hence why it has been modeled as an entity. If two notifications are sent with the same content to the same recipients at the same exact time, we must be able to determine that those two notifications were distinct. It really would not make much sense to consider otherwise, since when considering all objects in our domain model, Notification is by far the most central. At the core of noti is the ability to create, send, and monitor notifications. Modeling Notification as an entity is a no-brainer.
The selection of Notification as an aggregate root is just as natural. Notification is the core entity of the model, and as such should handle changes to itslef, as well as various associated objects, as a unit.
Tackling the concept of an Audience was slightly more abstract than Target and Notification. At first glance, one might think that being able to distinguish between two audiences associated with the same Targets may not be applicable. But, when we began to start thinking about the reality that this could in fact be the case, the more we realized that Audience may be more effectively expressed as an entity. An example may be that there exists two audiences, one containing high school students for a English class, while the other containing the same exact students but represents the students on student council. Where the fuzziness comes in is when we introduce the concept of an audience name. In reference to the example just mentioned, this name provides the necessary context to distinguish between the "Student Council" audience and "English Class" audience. In a way, this name serves as a representation of the audiences identity. After squinting a little harder at this, we feel firmly that Audience should remain an entity, since the inverse would suggest that two Audiences with the same name but different associated Targets would be two different audiences. If the concept of an audience name were not introduced, it could be possible to model Audience as a value object, but our application is concerned with understanding whether two Audiences with different associated Targets was really conceptually directed at the same group of Targets. For example, the class roster of a high school English class obviously rotates very regularly. If Audience were modeled as a value object, we would have no way of establishing the continuity that the notifications were indeed being sent to the same conceptual audience, even though the actual recipients regularly fluctuates.
Once coming to the conclusion that Audience should be modeled as an entity, we had an easier time determining that Audience should be considered an aggregate root. When entertaining the possibility that Audience reside as a non-root member in the Notification aggregate, it is quick to identify that the scope of modifying data for a Notification should not involve altering the state of an Audience. The focus when changing state of a Notification does not involve or concern the state of an Audience. Put more concretely, a valid change in state for a single Notification may be to add/remove an association to an Audience, but never to alter which Targets have been associated to any one of those Audiences. Allowing such a scope would introduce significant issues, as an isolated change to a single Notification would impact all other Notifications that were associated to the impacted Audiences.
- Pages 125 - 135 in Domain Driven Design: Tackling Complexity in the Heart of Software by Eric Evans.
- https://vimeo.com/33708293
- http://dddcommunity.org/library/vernon_2011/
- Domain
- Model
- Ubiquitous Language
- Modules (TBD)
- Up and Running
- Testing
- Content Negotiation
- Architecture (TBD)