Domain Driven Design–Organization Is Important!
Wednesday, October 30, 2013 10:36 AM
I’ve been doing some kata’s lately and have decided that there is quite a lot in a name. Working on another project that has things named more generically has been a somewhat difficult adjustment to get used to.
The funny thing is that I think that one of the big reasons people opt for descriptive names is because object taxonomy / classification is difficult! In order to properly classify things you have to know exactly how they relate to one another. This implies that you need to know everything there is to know about all of the classes to do it properly. Hopefully there is that someone on the team that is willing to help classify classes as they are created. Otherwise a large project can quickly become chaos!
Some time ago I stopped drawing “layers” or “tiers” in my architectural diagrams. The reason is that it’s too easy to get confused and think that DDD has anything in common with N-Tier architecture. There may be some similarities on the surface of things which makes them diagram like each other, but really they couldn't be more different. In DDD the Domain objects (those objects who contribute directly to solving the problem) should be the center of the universe. When it comes to DDD, I really take this concept to heart!
Because objects that those domain objects depend on may not actually be considered domain objects themselves, I opt to omit any/all of these infrastructure objects from my architecture diagram. Why?
- When drawn this way it’s easy to think that the database is the center of your architecture.
- Infrastructure objects, including database access objects, are not very helpful when looking at the high-level architecture because we mostly want to see how these domain objects interact with each other.
- They easily clutter and detract from the purpose of the diagram. After all, the purpose of an architectural diagram is not to show every class or even every kind of class. That is what a class diagram is useful for, and an architectural diagram is a little more abstract.
- By the very nature of DDD, our domain experts need to be able to consume this diagram. That’s tricky when a bunch of objects they don’t understand are also there.
Therefore, when I diagram architecture, I use circles with arrows indicating directionality of the dependency. If I draw an infrastructure object, I will make it rectangle to set it apart from my domain objects.
Again, I recommend omitting infrastructure dependencies (aka concerns or cross-cutting concerns) altogether! Another consideration is that sometimes these domains can have hierarchical dependencies. For example, I could have a namespace ‘MyCompany.OrderProcessing.Orders.SalesTax.Providers.SimpleTax’. Because of this hierarchical tendency our domains can literally have smaller domains inside of them. After all, Order Processing is a main domain of the MyCompany company. Orders are going to be at the center of the Order Processing activity. SalesTax deals specifically with orders but is outside the core functionality of an order. Providers are likely required to hook into other systems, and we want to have a specific implementation for SimpleTax. This is the level to which we want to abstract.
It’s going to take skill and perhaps a helpful domain expert to decide where objects outside of this domain live. For example does a customer belong in the ‘MyCompany’ domain or do we assume we have no customers unless we have orders and put them in the ‘MyCompany.OrderProcessing’ domain.
Inevitably someone is going to be setting up the projects and get confused about what projects to make and what to name them. My advice is simply this:
- Create a project in domain spaces where there would/could be code reuse.
- Place interfaces and enumerations in the domain level right above where those interfaces would be implemented (MyCompany.OrderProcessing.Orders.SalesTax.Providers in the example.)
- Build to expand, but don’t put every class in it’s own project. I.E. Order and OrderItems probably belong in the same place (MyCompany.OrderProcessing.Orders). This is because while OrderItems are a closely related and do not make a lot of sense without one another.
- Name data and other infrastructure objects like you would name the domain objects, but remember they are not domain objects!
- Avoid “Common”, “Shared”, “Utility”, or any other project that is a dumping ground for miscellaneous. Classify these classes and put them where they belong, even if they are not domain objects.
- Test projects should test only one domain project. This will allow the tests to follow the project as it evolves.
- Avoid naming the projects with the term “Service” unless they are actually a service like a WCF service.
- Avoid naming projects with the suffix “Interfaces”. This is because the parent domain level should take care of providing the interface. Only in rare cases should the interfaces be included independently. This only creates mostly-empty projects that are unneeded. Additionally it’s more helpful to provide an abstract class for a inheriting class to implement rather than just an interface.
- Avoid separating an objects data from it’s behavior (e.g. You should not split SalesTax into SalesTaxData and SalesTaxManager).
- Use static sparingly! You loose your ability to use dependency injection when you use a static class. In non-static classes, only create a static method when all of the following are true:
- Both the class and the method are stateless.
- None of the inputs to a method are a complex type. You should especially other domain objects. Sealed classes are okay.
- There would never be a need to alter (or for any other reason inherit from) the class.
- The return type from this class is not a complex type or is a sealed type.
Another question that is often asked is how to deal with multiplicities. I.E. what class would deal with finding all orders for a given customer. The answer is that it depends. Many times it would be appropriate to put a “GetOrdersByCustomer” call into Order, but many other times it should go into a peer class (a class in the same namespace) or a parent scope class. In this particular example I would probably create a class named `MyCompany.OrderProcessing.Orders.
For this particular example let’s consider how this domain would be organized and where these pieces would fit-in. Lets say that we have a class called `MyCompany.OrderProcessing.Orders.Order`. This class will deal with order information, both the creation of new orders and as an historical order. You would probably want an `MyCompany.OrderProcessing.Orders.OrderHistoryProvider` or `OrderProvider`, or something of the like. Just remember your SOLID principles!