Udi Dahan spoke today at the DDD Exchange about common misunderstandings and problems that teams have with implementing Domain Driven Design. According to Dahan, the domain model pattern seems to be abused more often than not.
Tiers aren't layers
When applying DDD, many people have in mind a classic three layer architecture, said Dahan, and there is a common assumption that the architecture layers are the same as tiers of deployment. For example, anything that’s in the business domain layer is not going to be deployed to the client tier. According to Dahan, this is the biggest mistake teams make when applying domain driven design.
A layered model is useful to tell us not to mix user interface logic and business logic or business logic and databases, said he, but the model says nothing about deployment tiers. “Starting with an assumption that layers are equal to tiers straight-jackets our implementation and colours our impression of the model thinking that we have to have data transfer objects going across tiers. If they weren’t in the different tiers, we would not need data transfer objects”, said Dahan, continuing that “almost any project I’ve seen that said that they are doing DDD made this assumption. This constrains choices and causes too many problems that aren’t related to DDD. “
Expecting to have a single architectural model for everything, teams don’t understand that they can deploy the same components to multiple tiers. Things like validation are a common cause of confusion. This causes a lot of architectural problems, that teams work around by putting in solutions such as caches. But according to Dahan, caches are often a sign that teams assume that there is a single linear layered architecture, which then causes performance problems. Many of our misunderstandings and problems we get ourselves into are related to this mix-up – layers, tiers, and what is a business rule.
Logical and physical connections are different
Another big issue with implementing DDD today is that there is an overemphasis on reuse in code. “Reuse is drilled into us in the universities and work. Even though this doesn’t reflect what we see in practice this drives us to centralise the code. Preferably in the domain model and then reuse it anywhere. Too much reuse creates an unmaintainable mess, because the system becomes hard to understand and has lots of dependencies.”, said Dahan. “When you think about domain models, business logic, validation – be very careful about how you treat it and not projecting your ideas of reuse”, advised he.
When talking about tiers, understand that you can get the exact same component, put it on another tier, and not create a dependency. The problem is when a single logical element of reuse became equal to a single physical place. This leads to lots of service calls – if it’s logically in one place it has to be physically in one place, so lots of remote calls have to happen. Equalling logical and physical locations causes too much complexity. Dahan suggested an alternative: “We want to remove these original blinders. You are allowed to deploy business logic to the client tier – that doesn’t mean you mixed business logic and user interfaces. It’s OK to take the same validation component you use on the server side and deploy on the client side. It doesn’t have to be in the same place to be logically cohesive”.
Teams also need to understand that there are different models, said Dahan, continuing “We need to understand what needs to be immediate, what means stale, and when the information can be stale, then addressing these situations with different models.”
All rules aren't created equal
Assuming that all rules are equally likely to change is another common mistake that teams make, said Dahan. This leads to all the rules being put in the same place and the same kind of reuse applied to the same rules. Validations, calculations and workflows are not necessarily the same. For example, the requirement “the username must be unique” is common but never changes. Workflow rules (eg when a customer cancels an order, the system checks if it has been shipped and cancels if not) change substantially more. The reason for this is, according to Dahan, because these rules are actually related to a particular domain, what makes a business unique. “By virtue of the fact that all systems have the exact same requirements around username uniqueness and field length, that they are not unique for a particular business so they are not unique”, said he. These are just technical constraints, according to Dahan: “The business doesn’t care that username fields are 8 characters. Usernames have to be unique for technical reasons to be able to select customers to check details”. Rules that are not part of genuine domain logic do not have to be implemented in the domain model, suggested he, because they do not model the domain. They may be deployed to a separate tier.
Dahan quoted Martin Fowler, who defined the domain model pattern in Patterns of Enterprise Application Architecture: “Use the domain model pattern if you have complicated and ever-changing rules…. If you have simple not null checks and a couple of sums to calculate, a Transaction Script is a better bet”.
Race conditions hide business rules
Another big problem when implementing DDD, according to Dahan, is that assuming the race conditions affect the domain. Giving an example of a possible race condition between the user cancelling an order at the same time as an operator asking the system to ship the same order. “As developers, we think that race conditions are business logic, so we need to write code for that”, said he, but businesses don’t understand race conditions. “We made them up. They didn’t exist in a business before we [software] got involved.” From a business perspective, a millisecond should not substantially change the domain. Businesses have existed long before computers, on paper, with concurrency problem windows not in milliseconds but in days and enormous chances for race conditions.
“But businesses still functioned. We ignore that and think that race conditions matter. “, said Dahan. Instead of solving race conditions with code, he suggested factoring time into design and talking to the business users about how the process was implemented before computers. For example, if the user cancels an order that was already shipped, charging the customer money unless they are of a particular status. The command does not fail because of a race condition, it extends to handle edge cases.
“If you think you have a race condition, you don’t understand the domain well enough. These rules didn’t exist in the age of paper, there is no reason for them to exist in the age of computers. When you have race conditions, go back to the business and find out actual rules”, advised Dahan