
Domain-driven Design
Domain-driven Design (DDD) is not a methodology, framework, or technology, but more of a way of thinking practically. It is geared towards making software development move at a faster pace. It puts the focus on the domain and domain logic as it is truly the center of any application. Having a robust application and infrastructure wrapped around poorly designed domain logic is a problem waiting to happen.
The topic of Domain-driven Design is a vast one. To get you started in understanding the high-level concepts, I will outline the basics here. As we continue to build our framework and application, you will find some of the following principles applied.
Note
For more information about Domain-driven Design check out this website: http://www.domaindrivendesign.org.
Ubiquitous language
The concept of ubiquitous language is a simple one. It basically states that all individuals involved with a software development project—business owners, project managers, developers, that is, just about everyone—use the same language to describe the aspects of the software being developed. This reduces the confusion, which in turn increases the speed of overall development.
This concept is not just for discussion purposes. It extends to the actual naming of classes, methods, and more. Once this occurs all discussions will sound similar no matter who is involved with the discussion. When this is followed, and everyone is speaking the same language in all conversations, confusion is totally removed and there are no longer islands of expertise.
In the end, the domain and domain logic become more refined. The application is better for it!
Entities
An entity is an object in your application that maintains its state for the life of your application. This means that it can be rehydrated from an XML file, saved to a database, later loaded from that database instance, serialized, and sent across the network— resulting in the same object in all the cases. This is performed with the use of a unique ID. This could be anything from an auto-incrementing numeric ID, a GUID, a Social Security number (SSN), including anything that would be unique in your system.
An example would be a person in the US. In this case, we could use a person's SSN and each resulting person would be unique. When we look at all the people in the world, the SSN would no longer be considered a good form of unique ID as not all people in the world have one. So in this case, we would probably have to start looking at multiple properties of a person to define their uniqueness. We could take their birth date, last name, country/state, city, and so on. A combination of this information should result in a unique entity in your system. Entities are the most important objects of your domain.
Do all objects in an application need to be unique? Are all objects necessarily entities? Well, the answer is 'No'. In the next section, we will look at value objects as the answer to these questions.
Value objects
A value object is less important than an entity. It does not require an identity, and hence it can be easily created without being concerned to determine its uniqueness. We are more interested in what the item is rather than who it is. A value object should be immutable. If you need to modify the value object, toss it, and create a new one. But if you find that the object is not immutable, or can't exist without its own identity, it is most likely to be an Entity object.
To extend our person example from the Entities section further, we could make an "address" value object rather than have a person with properties of state, city, zip code, street or any other information pertaining to the address. The value object would then store information about state, city, zip code, street, and so on. This address object could then be part of a person. So you could say person.Address.City
. Technically, this address could be shared for all the people in the same house. We don't care so much for the address itself, but for the fact that it is attached to a specific person.
Why do we need value objects? Value objects are important. They are not only a way of grouping bits of information as you saw in the above example. Being "lightweight" objects, they reduce the amount of resources used in an application. They also simplify an application in that value objects don't require uniqueness.
Services
As we discuss our application using the Ubiquitous Language (discussed previously in a separate section), we will quickly end up with a vocabulary of nouns (entities and value objects) and verbs (methods of those objects). However, not all the verbs that we end up with will easily fit into our defined entities and value objects. What do we do with these equally important but homeless verbs? They are obviously needed by the application. Do we stick them into one of our existing objects for lack of a better place to put them? Doing that would create clutter and confuse the objects making them difficult to use. So, the answer to the previous questions is 'No'. We would rather create a service.
A service is not an entity or a value object. Having said that, we have to create an object of some kind or another considering the fact that we are using an OOP-based language! So we create a service-based object that provides the needed action (verb) for our application. Take an e-commerce application for example. We might have a customer and a vendor wherein one of them needs to contact the other via email. Would it be appropriate to add a method to the customer object to send them email from the vendor to the customer? Would it be appropriate to add this method to the vendor object? It doesn't quite seem right in either object. So we could create an email service. Now when our customer needs to contact the vendor, or the vendor needs to contact the customer, either one can use the service to take care of the communication.
Modules
Even if you have never heard of or worked with Domain-driven Design, you will easily understand what a module is. A module is a group of features and functionality in your application. Modules are a way of organizing large and complex domain logic into smaller and more understandable units.
In this application, we will have accounts, blogs, picture galleries, and many other features. While we could easily create one vast library of code to cover all these features, it would be easier for us to create smaller units of code that specifically describe the features of each module separately. Obviously, we will have some overlap among modules as an account is used in both galleries and blogs. It would also be possible for blogs to reference an image in an image gallery. As you can see, not only do modules make your code easier to understand, they also help keep things decoupled as you have further refined your code into yet another container!
Aggregates
Up to this point we have discussed the idea that we must use a ubiquitous language to define our application's vocabulary. From that definition we will draw out entities and value objects. Where methods don't fit our entities or value objects, we can create services. And all these items can be grouped into modules. Now we are going to move on to how these items can be managed, created, and stored using aggregation, factories, and repositories.
With a complex application, we will have lots of objects to deal with. So far we have tried to reduce complexity by keeping things easy to understand by way of a well-defined vocabulary and by grouping that vocabulary into smaller containers. Also, we have tried to reduce complexity by stating that some objects need to be able to maintain the state across the application, while others do not.
What we have not yet discussed is how to maintain an object's life cycle. We know that they will be created, stored, passed around, and so on. But what about when it is time to completely erase one of these objects from the system? If we do not closely manage the usage of our objects, we could end up with objects randomly floating about our system for no reason at all. This in turn could create issues for us in the form of memory leaks resulting in a system crash.
For this reason, it is important that we plan for simplicity in how our objects interact with one another. Rather than have objects spin one another up haphazardly, it would be nice to have gatekeepers that we have to go through to gain access to other objects. For example, the only way for object Z to gain access to object B is through object A. This way when object Z is done using object A and object B, we can simply remove object A, and object B will go too.
An aggregation is a boundary that we can use with our objects. We discussed that our person
object was an entity object, and that the address information was a value object. Let's extend this example to say that we would also have an object that handles the person's contact information, which could be phone numbers, email addresses, and so on. This too would be a value object. Rather than letting external objects directly access the address and contact information, we could force them to go through the entity object to read this data. Also, if an external object wanted to add a phone number to the list of phone numbers for a person, we could enforce that the only way to do this is through the person
object rather than through the contact information object.
Following through with this concept further simplifies our domain logic in the form of breaking our class structure down into even smaller buckets of code. It is not only easier to work from a development point of view, but also simpler to manage how objects come and go in our application.
Factories
With all the simplicity that we are striving to achieve in our design, how can we limit the complexity that is involved in the creation of an object? It doesn't make sense to store that logic in the object that we are trying to create. It is one thing to put some simple logic in the constructor of an object and let the object make itself, but quite another when that object is an entity and itself has several value objects as part of its make up (an aggregate). In this case, we would want to stash the creation logic into something called a factory.
As we strive to keep the objects focused on their single concern it makes sense to have a person factory that would create a person, the person's address information, and contact information rather than have the person
object know how to do these tasks, as well as how to be a person. To grasp the concept better, take the example of buying a TV. If you wanted a new TV, you would go to the local electronics store and purchase one. You wouldn't expect the vendor to give you a screen, a box, some electronics, a few cables, some plastic, a handful of buttons, and the directions to assemble your TV. You would walk down an aisle, pick a TV, purchase a box with a working TV in it, and go home. We should keep our objects working in the same order. Think of a factory as the electronics store, and your objects as the assembled TV. Your object should do what it does best, act like a person, and not worry about how to read genetic code and assemble cells.
Repositories
Now as we have understood that a factory's sole purpose is the creation of objects, what about the hydration of an already existing object? Perhaps we have a list of people stored in a data store. Should a factory be responsible for retrieving these people? In DDD, it is stated that this is the role of the Repository.
A Repository is an object whose purpose is specific to a single Entity object. For our purposes, we will have a PersonRepository
object. This object would know how to get a person (or many people) based on certain well-defined parameters. It would also know how to persist person
objects to a data store.
Note that I use 'data store', and not 'database'. A repository works closely with your application's infrastructure code. It should know how to work with all sorts of data stores that your application might know to work with. This could be a database, a web service, a collection of XML files, or any other data store. While your repository is intelligent enough to work with infrastructure code, the interfaces that it presents to other domain objects should be domain oriented and should be simple for the other domain objects to use.
Some examples can be passing in a social security number to hydrate a person
object, or passing in a whole person
object to be persisted to the appropriate data store. The clients of your repository should not be required to know anything about infrastructure code at all!