Implementing Domain-Driven Design
Rate it:
Open Preview
4%
Flag icon
An Aggregate is composed of either a single Entity (5) or a cluster of Entities and Value Objects (6) that must remain transactionally consistent throughout the Aggregate’s lifetime. Understanding how to effectively model Aggregates is quite important and one of the least well understood techniques among DDD’s building blocks.
4%
Flag icon
Can I DDD? You can implement DDD if you have • A passion for creating excellent software every day, and the tenacity to achieve that goal • The eagerness to learn and improve, and the fortitude to admit you need to • The aptitude to understand software patterns and how to properly apply them • The skill and patience to explore design alternatives using proven agile methods • The courage to challenge the status quo • The desire and ability to pay attention to details, to experiment and discover • A drive to seek ways to code smarter and better
5%
Flag icon
We primarily want to use DDD in the areas that are most important to the business. You don’t invest in what can be easily replaced. You invest in the nontrivial, the more complex stuff, the most valuable and important stuff that promises to return the greatest dividends. That’s why we call such a model a Core Domain (2). It is these, and in second priority the significant Supporting Subdomains (2), that deserve and get the biggest investment. Rightly, then, we need to grasp what complex means.
5%
Flag icon
DDD Scorecard: Use Table 1.1 to determine whether your project qualifies for an investment in DDD. If a row on the scorecard describes your project, place the corresponding number of points in the right-hand column. Tally all the points for your project. If it’s 7 or higher, seriously consider using DDD. Table 1.1. The DDD Scorecard
7%
Flag icon
The Ubiquitous Language is a shared language developed by the team—a team composed of both domain experts and software developers. That’s it. Now you’ve got it!
7%
Flag icon
Ubiquitous means “pervasive,” or “found everywhere,” as spoken among the team and expressed by the single domain model that the team develops. • The use of the word ubiquitous is not an attempt to describe some kind of enterprise-wide, company-wide, or worldwide, universal domain language. • There is one Ubiquitous Language per Bounded Context.
9%
Flag icon
One thing to be aware of is that the term Domain may be a bit overloaded. Domain can refer to both the entire domain of the business, as well as just one core or supporting area of it. I will do my best to distinguish each use of the term. When referring to just one area of the business, I will generally qualify it with the use of Core Domain, Subdomain, and the like.
12%
Flag icon
We don’t need to give the name Checking Account to the object in the Checking Context or the name Savings Account to the object in the Savings Context. Both concepts may safely be named Account because each Bounded Context distinguishes subtle meanings.
12%
Flag icon
When integrations are needed, mapping must be done between Bounded Contexts. This can be a complex aspect of DDD and calls for a corresponding amount of care. We don’t usually use an object instance outside its boundary, but related objects in multiple contexts may share some subset of common state.
12%
Flag icon
Yet, we don’t create an Author object out of thin air. Every collaborator must be prequalified. We confirm the existence of a User playing the appropriate Role within the Identity and Access Context. The attributes of an authentication descriptor are passed with requests to the Identity and Access Context. To create a new collaborator object, such as a Moderator, we use a subset of User attributes and a Role name. The exact details of how we obtain object state from a separate Bounded Context is not important (although later on it’s explained extensively). What’s important now is that these ...more
12%
Flag icon
Generally you can determine a proper separation because the similar objects have different properties and operations. In that case the boundary has separated the concepts appropriately. However, if you see the exact same objects in multiple contexts, it probably means there is some modeling error, unless the two Bounded Contexts are using a Shared Kernel (3).
13%
Flag icon
Developers should not have been able to reference User here, let alone query a Repository (12) for one. Even Permission should have been out of reach. It was possible because these were wrongly designed as part of the collaboration model.
15%
Flag icon
Whatever course you decide upon, there is always someone to tell you that you are wrong. There are always difficulties arising which tempt you to believe that your critics are right. To map out a course of action and follow it to an end requires courage. —Ralph Waldo Emerson
20%
Flag icon
Tempting though it may be, it is not advisable to directly expose a domain model via RESTful HTTP. This approach often leads to system interfaces that are more brittle than they need to be, as each change in the domain model is directly reflected in the system interface. There are two alternative approaches for combining DDD and RESTful HTTP.
22%
Flag icon
One technique suggested by [Dahan, CQRS] always explicitly displays on the user interface the date and time of the data from the query model that a user is currently viewing. To do so, each record in the query model needs to maintain the date and time of the latest update.
23%
Flag icon
There are varying definitions of Event Sourcing, so some clarification is fitting. We are discussing the use where every operational command executed on any given Aggregate instance in the domain model will publish at least one Domain Event that describes the execution outcome. Each of the events is saved to an Event Store (8) in the order in which it occurred. When each Aggregate is retrieved from its Repository, the instance is reconstituted by playing back the Events in the order in which they previously occurred.10 In other words, first the very earliest Event is played back, and the ...more
23%
Flag icon
Over a long period of changes to any and all Aggregate instances, doesn’t the playback of hundreds, thousands, or even millions of Events cause serious latency and overhead in processing the model? At least for some of the higher-traffic models that would most certainly be the case. To avoid this bottleneck we can apply an optimization that uses Aggregate state snapshots. A process is developed to produce, in the background, a snapshot of the Aggregate’s in-memory state at a specific point in Event Store history. To do this, the Aggregate is loaded into memory using all previous Events to the ...more
25%
Flag icon
We design a domain concept as an Entity when we care about its individuality, when distinguishing it from all other objects in a system is a mandatory constraint.
25%
Flag icon
Rather than focusing on the attributes or even the behavior, strip the Entity object’s definition down to the most intrinsic characteristics, particularly those that identify it or are commonly used to find or match it.
30%
Flag icon
To quote Martin Fowler, “Self encapsulation is designing your classes so that all access to data, even from within the same class, goes through accessor methods” [Fowler, Self Encap]. Using this technique provides several advantages. It allows for the abstraction of an object’s instance (and class/static) variables. It provides a way to easily derive attributes/properties from any number of others the object holds. And not least for this specific discussion, it lends support for a simple form of validation.
32%
Flag icon
Challenge Your Assumptions If you think that the object you are designing must be mutated by its behavior, ask yourself why that is necessary. Would it be possible instead to use replacement when the Value must change? Using this approach where possible is designing toward simplification. Sometimes it makes no sense for an object to be immutable. That’s perfectly fine, and it indicates that the object should be modeled as an Entity. If your analysis leads you to that conclusion, refer to Entities (5).
33%
Flag icon
you are leaning toward the creation of an Entity because the attributes of the object must change, challenge your assumptions that it’s the correct model. Would object replacement work instead? Considering the preceding replacement example, you may think that creating a new instance is impractical and lacks expressiveness. Even if the object you are dealing with is complex and changes somewhat frequently, replacement need not be an impractical, or even ugly, proposition. A later example demonstrates Side-Effect-Free Behavior for a simple and expressive way to deal with Whole Value replacement.
39%
Flag icon
Sometimes, it just isn’t a thing. . . . When a significant process or transformation in the domain is not a natural responsibility of an ENTITY or VALUE OBJECT, add an operation to the model as a standalone interface declared as a SERVICE. Define the interface in terms of the language of the model and make sure the operation name is part of the UBIQUITOUS LANGUAGE. Make the SERVICE stateless. [Evans, pp. 104, 106]
39%
Flag icon
A Value Object, UserDescriptor, is returned from the Service method. This object is small and secure. Unlike a full User, it includes only a few attributes essential to referencing a User:
40%
Flag icon
Much of the source code actually distributed online in support of this book leans toward dependency set up by way of constructors, or by passing dependencies directly to methods as parameters.
41%
Flag icon
The more technical Domain Service implementations that definitely live in Infrastructure are often those used for integration. For that reason I have delegated such examples to Integrating Bounded Contexts (13). There you’ll see the Service interfaces, implementation classes, and also Adapters [Gamma et al.] and translators used by the implementations.
48%
Flag icon
An Idempotent Operation An idempotent operation is one that can be executed two or more times in succession with results identical to those of executing the same operation only once.
50%
Flag icon
We have to give careful consideration to the perceived need to divide cohesive domain model objects into separate models, or to keep them together. Sometimes the linguistics of the true, actual domain will jump out at you, and sometimes the terminology will be fuzzy. In cases where terminology is fuzzy and it is not clear if contextual boundaries should be created, first consider the possibility of keeping them together. This approach will use the thinner boundary of Module to separate, rather than the thicker one of Bounded Context.
51%
Flag icon
An invariant is a business rule that must always be consistent. There are different kinds of consistency. One is transactional consistency, which is considered immediate and atomic. There is also eventual consistency. When discussing invariants, we are referring to transactional consistency. We might have the invariant c = a + b Therefore, when a is 2 and b is 3, c must be 5. According to that rule and conditions, if c is anything but 5, a system invariant is violated.
51%
Flag icon
A properly designed Aggregate is one that can be modified in any way required by the business with its invariants completely consistent within a single transaction. And a properly designed Bounded Context modifies only one Aggregate instance per transaction in all cases.
51%
Flag icon
Keeping performance and scalability in mind, what happens when one user of one tenant wants to add a single backlog item to a product, one that is years old and already has thousands of backlog items? Assume a persistence mechanism capable of lazy loading (Hibernate). We almost never load all backlog items, releases, and sprints at once. Still, thousands of backlog items would be loaded into memory just to add one new element to the already large collection.
51%
Flag icon
10.3 containing the zoomed composition. Don’t let the 0..* fool you; the number of associations will almost never be zero and will keep growing over time. We would likely need to load thousands and thousands of objects into memory all at once, just to carry out what should be a relatively basic operation. That’s just for a single team member of a single tenant on a single product. We have to keep in mind that this could happen all at once with hundreds or thousands of tenants, each with multiple teams and many products. And over time the situation will only become worse.
51%
Flag icon
Which ones are necessary? The simple answer is: those that must be consistent with others, even if domain experts don’t specify them as rules. For example, Product has name and description attributes. We can’t imagine name and description being inconsistent, modeled in separate Aggregates. When you change the name, you probably also change the description. If you change one and not the other, it’s probably because you are fixing a spelling error or making the description more fitting to the name. Even though domain experts will probably not think of this as an explicit business rule, it is an ...more
51%
Flag icon
Smaller Aggregates not only perform and scale better, they are also biased toward transactional success, meaning that conflicts preventing a commit are rare. This makes a system more usable. Your domain will not often have true invariant constraints that force you into large-composition design situations. Therefore, it is just plain smart to limit Aggregate size. When you occasionally encounter a true consistency rule, add another few Entities, or possibly a collection, as necessary, but continue to push yourself to keep the overall size as small as possible.
51%
Flag icon
Often, in such cases, the business goal can be achieved with eventual consistency between Aggregates. The team should critically examine the use cases and challenge their assumptions, especially when following them as written would lead to unwieldy designs. The team may have to rewrite the use case (or at least re-imagine it if they face an uncooperative business analyst). The new use case would specify eventual consistency and the acceptable update delay. This is one of the issues taken up later in this chapter.
52%
Flag icon
Having an Application Service resolve dependencies frees the Aggregate from relying on either a Repository or a Domain Service.
52%
Flag icon
Distributed operations are managed by what [Helland] calls two-party activities, but in Publish-Subscribe [Buschmann et al.] or Observer [Gamma et al.] terms it’s multiparty (two or more). Transactions across distributed systems are not atomic. The various systems bring multiple Aggregates into a consistent state eventually.
52%
Flag icon
There is a frequently overlooked statement found in the [Evans] Aggregate pattern definition. It bears heavily on what we must do to achieve model consistency when multiple Aggregates must be affected by a single client request: Any rule that spans AGGREGATES will not be expected to be up-to-date at all times. Through event processing, batch processing, or other update mechanisms, other dependencies can be resolved within some specific time. [Evans, p. 128]
52%
Flag icon
Ask the domain experts if they could tolerate some time delay between the modification of one instance and the others involved. Domain experts are sometimes far more comfortable with the idea of delayed consistency than are developers. They are aware of realistic delays that occur all the time in their business, whereas developers are usually indoctrinated with an atomic change mentality.
52%
Flag icon
There is a practical way to support eventual consistency in a DDD model. An Aggregate command method publishes a Domain Event that is in time delivered to one or more asynchronous subscribers:
52%
Flag icon
What happens if the subscriber experiences concurrency contention with another client, causing its modification to fail? The modification can be retried if the subscriber does not acknowledge success to the messaging mechanism. The message will be redelivered, a new transaction started, a new attempt made to execute the necessary command, and a corresponding commit made. This retry process can continue until consistency is achieved, or until a retry limit is reached.6 If complete failure occurs, it may be necessary to compensate, or at a minimum to report the failure for pending intervention.
52%
Flag icon
Discussing this with Eric Evans revealed a very simple and sound guideline. When examining the use case (or story), ask whether it’s the job of the user executing the use case to make the data consistent. If it is, try to make it transactionally consistent, but only by adhering to the other rules of Aggregates. If it is another user’s job, or the job of the system, allow it to be eventually consistent. That bit of wisdom not only provides a convenient tie breaker, but it helps us gain a deeper understanding of our domain. It exposes the real system invariants: the ones that must be kept ...more
52%
Flag icon
Eventual consistency requires the use of some kind of out-of-band processing capability, such as messaging, timers, or background threads.
53%
Flag icon
Thus, when our design is forced to, sometimes it works out well to modify multiple Aggregate instances in one transaction.
62%
Flag icon
The domain model and its encompassing Domain Layer is never the correct place to manage transactions.
62%
Flag icon
When a Facade’s business method is invoked by the User Interface Layer (14), whether on behalf of a human or another system, the business method begins a transaction and then acts as a client to the domain model. After all necessary interaction with the domain model is successfully completed, the Facade’s business method commits the transaction it started. If an error/exception occurs that prevents completion of the use case task, the transaction is rolled back by the same managing business method.
62%
Flag icon
To enlist changes to the domain model in a transaction, ensure that Repository implementations have access to the same Session or Unit of Work for the transaction that the Application Layer started.
63%
Flag icon
Be careful not to overuse the ability to commit modifications to multiple Aggregates in a single transaction just because it works in a unit test environment. If you aren’t careful, what works well in development and test can fail severely in production because of concurrency issues.
65%
Flag icon
One such straightforward approach is for a Bounded Context to expose an application programming interface (API), and another Bounded Context to use that API via remote procedure calls (RPCs).
65%
Flag icon
A second straightforward way to integrate Bounded Contexts is through the use of a messaging mechanism. Each of the systems that need to interact do so through the use of a message queue or a Publish-Subscribe [Gamma et al.] mechanism.
« Prev 1