Several people have recommended the book Domain Driven Design by Eric Evans. I've heard it referenced on podcasts and in blogs. My technical manager informed me when I first started on my current project that the architecture was partially based on this book. It takes a while for a message to seep in, but I finally decided it was time to pick up this book.
When I saw the book cover, I was surprised. No, not by the retro cheesiness of the graphics, colors, and fonts. I was surprised because I recognized it. I had had this book on my shelf for over two years.
I bought this book from Amazon because it was paired with Software Factories. I started reading DDD shortly after finishing SF, but gave up before even getting through the preface. I found Evan's style unnecessarily verbose and obfuscated.
So the book languished on my shelf for over two years before I made the discovery that an important sub-discipline within my industry was passing me by, simply because of my disdain for one man's writing style. So I forced myself to wade through his awkward word choice, parabolic explanations, and prolonged narratives to extract the meat of the subject. And I'm glad I did.
It turns out, we're doing DDD wrong.
What is Domain Driven Design
The point of Domain Driven Design is to put all of the domain knowledge into a rich object layer. This layer captures the essential information about the problem domain, the relationships among those objects, and the responsibilities of those objects. If the domain is commerce, as it is in our system, then you have domain objects for products, orders, and shopping carts. The methods of these objects capture business knowledge, like inventory rules and regulatory restrictions.
This is very similar to the old Rumbaugh OOA&D thinking that my career started out with. We as an industry have come to think of that "look for the nouns" guidance as naive. Programs that run on computers need objects that represent computer ideas. So we have objects for windows, database connections, proxies, and sockets. If you follow the Gang of Four as I do, you'll write objects like factories, visitors, and strategies. Nouns from the real world have a hard time coexisting with these technical concepts in today's object oriented programs.
But Evans provides some guidance that allows us to draw from both of these views of objects. The domain objects are isolated from the technical objects. Patterns like Repository/Specification and Unit of Work hydrate domain objects without coupling them to persistence technology. Evans shows us how to take a set of objects that a domain expert would understand, and use them in a modern software application.
The advantage of Domain Driven Design is that domain objects become a medium of communication. Programmers and experts can talk about the domain by using the source code. Or at least, the classes and methods in the source code. If the source code does not accurately reflect the knowledge of the domain expert, then the source code is wrong. Using Evans' guidance and patterns, the domain model can be quickly refactored as developers learn more about the problem domain.
Unfortunately, the way that we have been applying Evans' guidance and patterns on our eCommerce system has made it more difficult to refactor the domain, and left us with a fairly rigid system. Here are some of the big mistakes we made.
Layered architecture does not mean tiered architecture
Evans recommends a layered architecture:
The presentation layer handles UI, the application layer implements features, the domain layer captures business knowledge, and the infrastructure layer lends low-level support. This approach isolates the domain objects from technical concerns like storage and presentation. It allows the solution to be implemented in the language of the domain, instead of forcing the domain to implement those features.
Our approach was to separate these concerns into physical tiers. These tiers could be deployed to different servers in the datacenter for the benefits of security and scalability. Imagine my surprise when I get to page 114, where Evans says, "This could be a strong argument if the code actually got deployed on different servers. Usually it does not." I wasn't surprised because he told me something I didn't know; I was surprised because I knew and hadn't objected.
The fact is, we aren't going to need quite so many tiers. Yes, we are separating the presentation tier because it is hosted in SharePoint and resides in the DMZ. But besides that, the other layers -- not tiers -- can benefit from being in the same process and not being forced across machine boundaries.
SOA's "services" are not DDD's "services"
We architected our eCommerce application to be service oriented. The benefits are that isolated services are reusable and independently versionable. These are significant goals of this project, as we are building not just an eCommerce application, but a platform for all commerce in the company.
Domain Driven Design also talks about services. But a DDD service is an operation that doesn't belong to just one object. A service is still a domain concept, in that it captures domain knowledge, but it isn't an instance method on a domain object. We've exposed our services as WCF web services, when it may have been more appropriate to simply expose them as static methods in the domain layer. Only those services that external systems would want to consume should be published through WCF.
Entity Framework's "entities" are not DDD's "entities"
Evans' guidance is strongly informed by the difference between entities -- objects whose identity is important -- and value objects -- whose identity is not. His patterns talk about how to carefully rehydrate entities such that their in-memory identity matches their persistent identity (where it is important to do so). Above all, entities are first-class domain objects.
Before I joined the project, linq to SQL was rejected because it does not play well with rich domain objects. Certainly, a linq query pulls data from a record set and puts it into objects, but it does nothing to respect the identity of those objects. If you run two queries, it will happily construct two objects to represent the same ID.
The decision was made to use the Entity Framework, then in beta. The idea was that EF entities were partial classes, so domain logic could be added. The framework would handle persistence, and our code could take over from there.
As it turned out, Entity Framework is no better than linq to SQL for our purpose. The primary concern of the entity model is the mapping between record sets and objects. The entity model exists in a limbo between a good object model and a good relational model. Domain objects are, first and foremost, objects. If they have to compromise to conform to good relational design, then they fail to be good domain objects.
On the other hand, either EF or linq to SQL would make a good memento. Linq to SQL favors anonymous types, while EF favors its flavor of entity. If the persistence of domain objects handled the translation of domain objects to mementos, then either one could take it the rest of the way to the relational database.
We already had a repository and specification
The piece of advice from DDD that people most often cite is to use a pattern of Repositories and Specifications. A repository is a place to store objects when they are not in memory. A specification is a condition -- separate from the repository -- that you can use to fetch the objects you are interested in. The advantage of this pattern is that the application layer can provide the specification, the infrastructure layer the repository, and the domain objects remain blissfully unconcerned with these non-domain details.
The piece of advice that is not often cited, however, is "don't fight your frameworks" (page 157). If your framework already has a good implementation of the pattern, just use it. This is advice that we ignored to our peril.
.NET 3.5 introduced linq. Besides the language features that support linq, the core framework component is the IQueryable interface. As it happens, IQueryable is the repository/specification pattern. Any object that implements this interface is a repository. Any boolean delegate (or lambda expression) is a specification. Combine the two using the Where extension method, and you have implemented the pattern.
What we did, however, was to create generic interfaces with names like IAggregateRootRepository, ISpecification, and IUnitOfWork. In order to use these interfaces, you have to define entire classes that differ in no more than a class name and a where clause. The result is a common base class library that is difficult to learn and encumbered with Evans' poor choice of words. Our application is full of extra classes that could have been inlined as lambda expressions. We have an entire suite of unit tests dedicated to ensuring that these specification classes contain the right boolean expression.
Here's my solution
Although we are too far along in the process to make radical architectural changes, I like to keep an ideal in mind for new development and refactoring. The ideal is a simple one: encode business knowledge in rich domain objects, use mementos to persist them, build application and domain layers into a single process, and publish WCF web services only where needed.
I am not a born-again DDD convert. I apply Evens' advice only with an open and skeptical mind. But where I see his book brandished as justification for poor decisions, I dig deeper to find what he really meant. Yes, it takes a lot of digging, given his choice of obscure terms for otherwise well-known concepts, but there is value to be found.