- Can I DDD?
- Why You Should Do DDD
- How To Do DDD
- The Business Value of Using DDD
- The Challenges of Applying DDD
- Fiction, with Bucketfuls of Reality
- Wrap Up
The Challenges of Applying DDD
As you now implement DDD you will encounter challenges. So has everyone else who has succeeded at it. What are the common challenges and how do we justify using DDD as we face them? I discuss the more common ones.
- Allowing for the time and effort required to create a Ubiquitous Language
- Involving domain experts at the outset and continuously with the project
- Changing the way developers think about solutions in their domain
One of the greatest challenges in using DDD can be the time and effort required to think about, research concepts and terminology, and converse with domain experts in order to discover, capture, and enhance the Ubiquitous Language rather than coding in techno-babble. If you want to apply DDD completely, with the greatest value to the business, it's going to require more thought and effort, and it's going to take more time. That's the way it is, period.
It can also be a challenge to solicit necessary involvement from domain experts. No matter how difficult, make sure you do. If you don't get commitment from at least one real expert, you are not going to uncover deep knowledge of the domain. When you do get their necessary involvement, the onus falls back on developers. Developers must converse with and listen carefully to the true experts, molding their spoken language into software that reflects their mental model of the domain.
If the domain you are working in is truly distinguishing to your business, domain experts have the edge-knowledge locked up in their heads, and you need to draw it out. I've been on projects where the real domain experts are hardly around. Sometimes they travel a lot and it can be weeks in between one-hour meetings with them. In a small business it can be the CEO or one of the vice presidents, and they have lots of other things to do that may seem more important.
Cowboy Logic AJ: “If you can't rope the big steer, you're gonna go hungry.”
Getting domain expert involvement may require creativity...
Most developers have had to change the way they think in order to properly apply DDD. We developers are technical thinkers. Technical solutions come easy for us. It's not that thinking technically is bad. It's just that there are times when thinking less technically is better. If it's been our habit to practice software development only in technical ways for years, perhaps now would be a good time to consider a new way of thinking. Developing the Ubiquitous Language of your domain is the best place to start.
Cowboy Logic LB: “That fella's boots are too small. If he don't find himself another pair, his toes are gonna hurt.”
AJ: “Yep. If you don't listen, you're gonna have to feel.”
There's another level of thought that is required with DDD that goes beyond concept naming. When we model a domain through software, we are required to give careful thought to which model objects do what. It's about designing the behaviors of objects. Yes, we want the behaviors to be named properly to convey the essence of the Ubiquitous Language. But what an object does by means of a specific behavior must be considered. This is a level of effort that goes beyond creating attributes on a class and exposing getters and setters publicly to clients of the model.
Let's now look at a more interesting domain, one that is more challenging than the rudimentary one previously considered. I purposely repeat my previous guidance here to reinforce the ideas.
Again, what happens if we simply provide data accessors to our model? It leans heavily in the direction of data modeling. Consider the following two examples and decide for yourself which of the two requires more thorough design thought, and which produces the greatest benefit to its clients. The requirement is in a Scrum model, where we need to commit a backlog item to a sprint. You probably do this all the time, so it's most likely a familiar domain.
The first example, as is commonly done today, uses attribute accessors:
public class BacklogItem extends Entity { private SprintId sprintId; private BacklogItemStatusType status; ... public void setSprintId(SprintId sprintId) { this.sprintId = sprintId; } public void setStatus(BacklogItemStatusType status) { this.status = status; } ... }
As for the client of this model:
// client commits the backlog item to a sprint // by setting its sprintId and status backlogItem.setSprintId(sprintId); backlogItem.setStatus(BacklogItemStatusType.COMMITTED);
The second example uses a domain object behavior that expresses the Ubiquitous Language of the domain:
public class BacklogItem extends Entity { private SprintId sprintId; private BacklogItemStatusType status; ... public void commitTo(Sprint aSprint) { if (!this.isScheduledForRelease()) { throw new IllegalStateException( "Must be scheduled for release to commit to sprint."); } if (this.isCommittedToSprint()) { if (!aSprint.sprintId().equals(this.sprintId())) { this.uncommitFromSprint(); } } this.elevateStatusWith(BacklogItemStatusType.COMMITTED); this.setSprintId(aSprint.sprintId()); DomainEventPublisher .instance() .publish(new BacklogItemCommitted( this.tenant(), this.backlogItemId(), this.sprintId())); } ... }
The client of this explicit model seems to operate on safer ground:
// client commits the backlog item to a sprint // by using a domain-specific behavior backlogItem.commitTo(sprint);
The first example uses a very data-centric approach. The onus is entirely on the client to know how to correctly commit the backlog item to a sprint. The model, which is not really a domain model, doesn't help at all. What if the client mistakenly changes only the sprintId but not the status, or the opposite? Or what if in the future another attribute must be set? The client code must be analyzed for correct mapping of data values to the proper attributes on the BacklogItem.
This approach also exposes the shape of the BacklogItem object and clearly focuses attention on its data attributes and not on its behaviors. Even if you argue that setSprintId() and setStatus() are behaviors, the case in point is that these “behaviors” have no real business domain value. These “behaviors” do not explicitly indicate the intentions of the scenarios that the domain software is supposed to model, that of committing a backlog item to a sprint. They do cause cognitive overload when the client developer tries to mentally select from among the BacklogItem attributes needed to commit a backlog item to a sprint. There could be many because it's a data-centric model.
Now consider the second example. Instead of exposing the data attributes to clients, it exposes a behavior that explicitly and clearly indicates that a client may commit a backlog item to a sprint. Experts in this particular domain discuss the following requirement of the model:
“Allow each backlog item to be committed to a sprint. It may only be committed if it is already scheduled for release. If it is already committed to a different sprint, it must be uncommitted first. When the commit completes, notify interested parties.”
Thus, the method in the second example captures the Ubiquitous Language of the model in context; that is, the Bounded Context in which the BacklogItem type is isolated. And as we analyze this scenario we discover that the first solution is incomplete and contains bugs.
With the second implementation clients don't need to know what is required to perform the commit, whether simple or complex. The implementation of this method has as much or little logic as necessary. We easily added a guard to protect against committing a backlog item that is not yet scheduled for release. True, you can also place guards inside the setters of the first implementation, but the setter now becomes responsible to understand the full context of the object's state rather than just the requirements for sprintId and status.
There's another subtle difference here, too. Note that if the backlog item is already committed to another sprint, it will first be uncommitted from the current sprint. This is an important detail, because when a backlog item is uncommitted from a sprint, a Domain Event is to be published to clients:
“Allow each backlog item to be uncommitted from a sprint. When the backlog item is uncommitted, notify interested parties.”
The publication of the uncommitted notification is obtained for free just by using the domain behavior uncommitFrom(). Method commitTo() doesn't even need to know that it notifies. All it needs to know is that it must uncommit from any current sprint before committing to a new sprint. Additionally, the commitTo() domain behavior also notifies interested parties with an Event as its final step. Without placing this rich behavior in BacklogItem we would have to publish Events from the client. That would certainly leak domain logic from the model. Bad.
Clearly more thought is needed to create the BacklogItem of the second example over that of the first. Yet the thought needed is not so much greater than that of the first, and the benefits are so much higher. The more we learn to design in this way the easier it becomes. In the end, there is certainly more required thought, more effort, more collaboration and orchestration of team efforts, but it is not so great that it makes DDD heavy. New thought is well worth the effort.
White Board Time |
Using the specific domain you currently work in, think of the common teams and actions of the model. Write the terms on the board. Next write phrases that should be used by your team when you talk about the project. Discuss them with a real domain expert to see how they could be refined (Remember to bring the coffee). |
Justification for Domain Modeling
Tactical modeling is generally more complex than strategic modeling. Thus, if you intend to develop a domain model using the DDD tactical patterns (Aggregate, Service, Value Objects, Events, etc.), it will require more careful thought and greater investment. Since this is so, how does an organization justify tactical domain modeling? What criteria can be used to qualify a given project for the extra investment needed to properly apply DDD from top to bottom?
Picture yourself leading an expedition through unfamiliar territory. You would want to understand the surrounding land masses and borders. Your team would study maps, maybe even draw their own, and determine their strategic approach. You would consider aspects of the terrain and how it could be used to your advantage. No matter how much planning is done, some facets of such an endeavor are going to be really difficult.
If your strategy indicated that you'd have to scale a vertical rock face, you'd need some fitting tactical tools and maneuvers for that assent. Standing at the bottom and looking up, you might see some indication of specific challenges and perilous areas. Yet, you wouldn't see every detail until you were on the rock face. You might need to drive pitons into slick rock, but could use various sized cams to wedge into natural cracks. To latch on to these climbing protections, you'd bring along your carabiners. You would try to take as straight a path as possible, but have to make specific determinations point by point. Sometimes you might even have to backtrack and reroute depending on what the rock dictated. Many people think of climbing as a dangerous thrill sport, but those who actually climb will tell you it's safer than driving a car or flying an airplane. Clearly for that to be true, climbers need to understand the tools, techniques, and how to judge the rock.
If developing a given Subdomain (2) requires such a difficult, even precarious, assent, we'd bring the DDD tactical patterns along for the climb. A business initiative that matches the criteria of Core Domain should not quickly dismiss the use of the tactical patterns. The Core Domain is an unknown and complex area. The team is best protected against a disastrous mid-asset fall if using the right tactics.
Here's some practical guidance. I begin with the high-level ones, and progress to more details:
- If a Bounded Context is being developed as the Core Domain, it is strategically vital to the success of the business. The core model is not well understood and will require lots of experimentation and refactoring. It likely deserves commitment to longevity with continuous enhancement. It may not always be your Core Domain. Nonetheless, if the Bounded Context is complex, innovative, and needs to endure for a long time as it undergoes change, strongly consider the use of the tactical patterns as an investment in the future of your business. This assumes that your Core Domain deserves the best developer resources with high skill level.
- A domain that may become a Generic or Supporting Subdomain to its consumers may actually be a Core Domain to your business. You don't always judge a domain from the viewpoint of its ultimate consumers. If you are developing a Bounded Context as your chief business initiative, it is your Core Domain regardless of how it is viewed by customers outside your business. Strongly consider the use of the tactical patterns.
- If you are developing a Supporting Subdomain that, for various reasons, cannot be acquired as a third-party Generic Subdomain, it is possible that the tactical patterns would benefit your efforts. In this case consider the skill level of the team and whether or not the model is new and innovative. It is innovative if it adds specific business value and captures special knowledge, and is not just technically intriguing. If the team is capable of properly applying tactical design, and the Supporting Subdomain is innovative and must endure for years in the future, this is a good opportunity to invest in your software using tactical design. However, this does not make this model the Core Domain since in the eyes of the business it is merely Supporting.
The above guidelines may be somewhat confining if your business employs a good number of developers with vast experience in and a very high comfort level with domain modeling. Where experience is very high, and the engineers themselves believe the tactical patterns would be the best choice, it makes sense to trust their opinion. Honest developers, no matter how experienced, will indicate in a specific case that developing a domain model is, or is not, the best choice.
The type of business domain itself is not automatically the determining factor for choosing a development approach. Your team should consider important questions to help you make the final determination. Consider the following short list of more detailed decision parameters, which is more or less aligned with and expands on the above higher-level guidelines:
- Are domain experts available and are you committed to forming a team around them?
- Although the specific business domain is somewhat simple now, will it grow in complexity over time? There is risk in using Transaction Script[1] for complex applications. If you use Transaction Script now, will the potential for refactoring to behavioral domain model later on be practical if/when the Context becomes complex?
- Will the use of the DDD tactical patterns make it easier and more practical to integrate with other Bounded Contexts, whether third-party or custom developed?
- Will development really be simpler and require less code if you use Transaction Script? (Experience with both approaches proves that many times Transaction Script requires as much or more code. This is probably because the complexity of the domain and innovation of the model was not well understood during project planning. Underestimating domain complexity and the innovation involved happens often.)
- Does the critical path and time line allow for any overhead required for tactical investment?
- Is the tactical investment in a Core Domain going to protect the system from changing architectural influences? Transaction Script may leave it exposed. (Domain models are often enduring while architectural influences can tend to be more disruptive to other layers.)
- Will clients/customers benefit from a cleaner, enduring design and development approach, or could their application be replaced by an off-the-shelf solution tomorrow? In other words, why would we ever develop this as a custom application/service in the first place?
- Is developing an application/service using tactical DDD going to be more difficult than using other approaches such as Transaction Script? (Skill level and availability of domain experts is vital to answer this question.)
- If the team's toolkit was complete with DDD-enablers, would we conscientiously choose to use another approach instead? (Some enablers make model persistence practical, such as using object-relational mapping, full Aggregate serialization and persistence, an Event store, or a framework that supports tactical DDD. There may be other enablers too.)
This list is not prioritized for your domain and you can probably assemble additional criteria. You understand the compelling reasons there are to using the best and most empowering methods possible to your advantage. You also know your business and technology landscape. In the end it is the business customer, not the object practitioners and technologists, who must be pleased. Choose wisely.
DDD Is Not Heavy
In no way do I want to imply that properly practicing DDD leads to a heavyweight process with lots of ceremony and all the crufty documentation artifacts that must be supported. That's not what DDD is about. It is meant to fit well into any agile project framework, such as Scrum, that the team desires to use. Its design tenets lean toward rather rapid test-first refinements of a real software model. If you were in need of developing a new domain object, such as an Entity or a Value Object, the test-first approach works like this:
- Write a test that demonstrates how the new domain object should be used by a client of the domain model.
- Create the new domain object with enough code to make the test compile.
- Refactor both until the test properly represents the way a client would use the domain object, and the domain object has proper behavioral method signatures.
- Implement each domain object behavior until the test passes, refactoring the domain object until no inappropriate code duplications exist.
- Demonstrate the code to team members, including domain experts, to ensure that the test is using the domain object according to the current meaning of the Ubiquitous Language.
You may conclude that this is not any different than the test-first approach you already practice. Well, it might be a little different, but the point is, it's basically the same. This test stage is not attempting to prove with absolute certainty that the model is bulletproof. Later we will add tests to do that. First we want to focus on how the model will be used by clients, and these tests drive the model's design. The good news is, it really is an agile approach. DDD promotes lightweight development, not ceremonious, heavy, upfront design. From that standpoint it really isn't different than common agile development. So, while the above steps may not enlighten you about agile, I think it clarifies the position of DDD, that it is meant to be used in an agile way.
Later you also add tests that verify the correctness of the new domain object from every possible (and practical) angle. At this point you are interested in the correctness of the expression of a domain concept that is embodied in the new domain object. Reading the demonstrative client-like test code must reveal the proper expressiveness using the Ubiquitous Language. Domain experts who are non-technical should be able to with the help of a developer read the code well enough to get a clear impression that the model has achieved the goal of the team. This implies that test data must be realistic, and support and enhance the desired expressiveness. Otherwise, domain experts cannot make a complete judgment about the implementation.
This test-first agile methodology repeats until you have a model that is working according to the tasks outlined for the current iteration. The steps above are agile, and represent what Extreme Programming originally promoted. Using agile does not eliminate any essential DDD patterns and practices. They go together quite well. Of course you can choose to use full DDD without doing test-first development. You can always develop tests against existing model objects. However, designing from the model client's perspective adds a very desirable dimension.
[1] Here I am generalizing terms. In this list I use Transaction Script to represent several non-domain-model approaches.