The patterns of Domain Driven Design 2/2
Tactical patterns
In our last article we discussed some of the benefits of Domain Driven Design (DDD) from a bird's-eye-view, as well as the patterns you can use to start designing your next DDD-based application. Now it’s time to open this box a bit further and take a look at the nuts-and-bolts, which keep it together – the tactical patterns of DDD.
Compared to strategic design patterns these are not only more hands-on, but endeavour to bridge the gap between our software and the business’s objects, rules & processes.
However, always keep in mind what DDD is actually trying to solve – communication. I’ll probably be throwing in a few words on primary keys, or databases in the next few chapters, but I’ll trust you to keep that between us. These concepts are meant not only to solve implementation headaches, but also to facilitate discussion between you and the businesspeople in your life.?
Their meaning is inherently meant to carry weight with them, you getting a cool new way of implementing a service is just a bonus.
Entity
Entities, as DDD defines them, have one key property, their id. This id is assigned upon creation and must remain unchanged for the object’s entire lifecycle. Two entities of the same type, with the same id are considered to be the same entity, even if their properties differ. As an example, compare yourself to the you of 2010, many of your properties may have changed, heck even your name might be different, but you’re still the same person, right?
As an entity's properties are merely descriptors, and do not define it, they are completely mutable. DDD allows for setters and getters to be implemented as normal, but it does recommend that, if possible, they relate to actual business terminology. Take a subscription, it may have an end-date property on it, but instead of using a simple “setEndDate” function, you could cancel it. Functionally it could then call a private setter within that object, but could also contain further functionality within it such as sending a farewell email to that customer.
As you can see, entities within DDD are meant to not only hold data, but also contain its business processes, rules or other forms of logic. DDD uses the term “anaemic” to describe an entity, which only contains data along with its various setters & getters.
Value Object
Value objects are an incredibly powerful idea within DDD. Simply put, it's an immutable object, which holds some form of business relevant data & logic. Now, I’m not sure about you, but to me that is something I’d just read and move on, without grasping the implications of that simple ruleset. An example, I find, usually does the job best.
A good one to start with is a single-value object, say a user’s email address. Now usually we’d just have a field, which takes a string on the user object itself, or maybe an array of strings if you want to support more. But by wrapping it in an object, we can encapsulate additional logic, such as validation & verification within that one object. Thus, we contextualise a string into a more specific object, ensuring, readable, clean & testable code.
Furthermore, the immutability of value objects ensures no side-effects & thread safety, which is just nice to not have to deal with.
Value objects within DDD have no meaningful id (they may contain one due to database requirements, but it should play no role in their function). They’re stores of business data & logic and as such are compared through their property values. If two value objects are of the same type and contain the same values, DDD considers them to be equal.
Sidenote: Apache commons lang3 has a few nice reflection-based functions to make implementing equals and hashcode functions a breeze for all you Java-developers.
Aggregates
You may have noticed I haven’t been mentioning adding value objects to entities themselves. While outside of DDD it’d be completely normal to add that email string field on your User object, DDD uses its concept of Aggregates to form complex objects made of one or multiple entities and value objects. These objects form a cohesive whole, which is meaningful to the business. For example, a Machine or a Customer in a gym management application.
Every aggregate thus requires at least one entity to function, this becomes the aggregates root, whose id is then used to identify the aggregate itself. After an aggregate is created, it may only be referenced through its root, so as to protect its state. The aggregate itself ensures that its state is consistent at all times through the business policies, which apply to it, which properties are accessible, or mutable, through getters and how they’re shaped.
To ensure their ACID-ity, a good rule of thumb is to only allow your functions to change one aggregate per transaction. You can use our next DDD pattern – domain events, for operations that require the mutation of many aggregates within a system.
Domain events
Domain events, in their most basic sense, are what it says on the tin. They’re business events that describe things that happen and change within a certain domain. Think of them as logs, but with added context to each log, just as value objects added context to a string, domain events such as “User x cancelled their subscription” or “Employee y was promoted” should carry meaning.
领英推荐
These events must be immutable, though that may or may not cause some GDPR considerations, a complete log of events is worth it in the end, due to the advantages they bring.
Now it’s relatively obvious how certain systems, where the events are actually the primary data holders, such as financial ledgers or medical record applications benefit from this concept, or it’s extension – event sourcing.
But events can allow you to do much more, their primary purpose is meant to function as communicational messages between systems in a standard Publisher-Subscriber (or Observable-Observer) relationship. Many DDD systems can use a only slightly extended logging service to handle most if not all communication needs within their systems, this can make implementing such logic a breeze, but requires eventual consistency to be applicable, as data is passed on merely as fast as each subscriber’s polling rate is.
Domain Service
We’re all familiar with services as a concept, which in standard n-tier (or similar) architectures function as orchestrators between say an entity's repository & it’s controller. A domain service is not that. DDD still uses services as normal for orchestration, but classifies them as application services.
Domain services are merely holders of business logic that applies to multiple domain objects (entities or value objects) within a given context. These services are stateless, highly cohesive, can publish their own domain events and can interact with other domain events.
While they technically can also interact with repositories, this is not their primary purpose and should be avoided if possible.
Factory
A DDD factory functions much the same as any factory pattern, but with a few additional rules applied to it. DDD Factories are needed when business logic is involved in an aggregate’s construction, which cannot be applied to the constructor itself, for example if the input data changes in aggregate's structure or content.
Repository
Nowadays we’re all pretty familiar with the concept of a repository as an interface of logic pertaining to the storage and retrieval of objects. DDD is where they come from. The only thing to note, regarding their definition, is that when they were originally laid out by Eric Evans, they are meant to store aggregates, as value objects or entities within a DDD framework hold little business meaning, and as such it wouldn’t make sense to require retrieving them alone.
However, do keep in mind that if you’re using something like a SQL database, in which you’re probably storing value objects and entities directly, merely aggregating them upon retrieval from the repository, things can get slow fast, as the complexity of your aggregates grows. As such it's generally recommended that you try to keep your aggregates lean, but if that still impacts performance too much for your liking, or you simply require more complex aggregates, you can use CQRS to speed things up.
Command and Query Responsibility Segregation (CQRS) completely decouples the write (command) and read (query) operations from each other. Within it commands travel along the normal path through their respective repositories, while queries go straight to the database (or sometimes a read-only copy of it) and fetch only what is needed in that moment. The DTO’s formed through such queries then contain the aggregate ID, so that changes to the aggregate can be requested from the client.
Module
One last pattern, for the road, this one’s quick. DDD modules, much like any implementation of modules in various languages, are primarily concerned with encapsulating code into cohesive wholes. DDD only uses business policies, objects & processes to dictate which snippets of code belong in any singular module, rather than only being coupled by function as they would be elsewhere.
Thus, modules in DDD correspond to a bounded context, but do not necessarily encompass it entirely, as a bounded context usually contains multiple modules.
Final Thoughts
You now have all the basic tools needed to start designing your next application, though quite a few parts of DDD are left to explore. If you’d like to see more content like this, please leave a like or comment below, as a signal to both me and my manager, that we’re on the right track.
Co-authored by?Miha Mulec
Sources: