One day I called an old software architect work friend of mine. He had recently finished reading a book that he said I must read. It was called Implementing Domain-Driven Design by Vaughn Vernon.
It had been a while since I last tackled a development book. I had spent the previous year and a half working through Home Recording Studio: Build it Like the Pros by Rod Gervais to design and build a soundproof recording room in my basement.
Having gone through that book prepared me for the book I was about to read. Each chapter contained foreign concepts that I would begin to further understand as other chapters connected the pieces. I read each chapter of both books many times before attempting to apply my new found knowledge. Both books had many examples of different ways to apply that knowledge at different levels of complexity. I learned how to apply the most complex of both topics, then scaled back to what made sense for my particular projects. Both decisions came down to effectiveness vs cost (bang for the buck).
Being that I was using C# for my project, I started by finding a sample project that Vaughn Vernon had started on github that uses C# based on the samples used in the book. This was very useful for seeing what decisions were made whenever I had to make similar decisions. It was also very useful for understanding the overall architecture as a whole, instead of just seeing pieces of code here and there throughout the book. It was also nice to see the code samples in C#. Although they were still easy to understand, like many of the programming books that I’ve read, the code samples were in Java. The basis of the book though is more about the strategic and tactical practices of domain-driven design than anything language specific.
A Little About the Style I Used
I really like, and am kind of addicted to Emergent Design. It just works so well when you refactor the hell out of your code as needed, as you go. I have to have a rough overview of the whole project to know that every high level design and thought is possible going forward, but then go for it with no fear of what lies ahead. I like to write a main function that has a name so blatantly obvious that a user would know what it does, then just start banging out my code in that class while refactoring every time a new function or object emerges as an obvious extraction. Depending on how it starts, I might write a comment for each next lower level step, make each comment a function with a similar name, then refactor by auto-generating those functions with a “not implemented” exception. If something’s really complicated or difficult, I might write a bunch of code until it works, then logically break it out into its individual pieces (classes, methods, etc.) afterward until it reads like a book. Either way, that code should read so well, that it almost reads aloud like a story. Then there is no reason for comments that eventually become outdated.
So my point is that this did not become a Big Design Up Front project. I followed the concepts of Domain-Driven Design, but applied them and refactored under the guidance of their concepts as I went along.
A major part of the strategic side to DDD is communicating directly with the users to define a ubiquitous language. The idea is that the developer(s) and users can then speak the same language, and the developer(s) use this language when coming up with class names, variable names, etc. This way, the code reads as if a user is speaking.
The project that I was working on was for receiving electronic payments from health insurance companies, and matching the payments to the charges in the Electronic Health Records (EHR) system. Much of the ubiquitous language was defined in documentation, but I also spoke directly with the users to determine any more casual wording that might have been common in conversation. I also got a feel for how they described certain events as well.
Domains and Bounded Context
A domain is a logical division or section of what you’re trying to write software for. A domain generally has its own ubiquitous language. For example, a business may have an inventory domain, a purchasing domain, a human resources domain, accounts receivable, and so on. A bounded context is the boundary around the code that represents the solution for that domain. These could be divided with a concept as simple and loose as a module (which I found in the book means to be the equivalent of a C# namespace), or as extreme as a microservice with its own database. The best description of domains and bounded contexts is that domains are the problem and bounded contexts are the solution.
In my project, I separated my project into two bounded contexts. One that represented receiving a file from the health insurance company that was parsed into discrete data for saving, and another for receiving command objects (data transfer objects that an application receives for processing) to apply those received payments to charges. This made for the payment posting domain to be very testable by automated unit tests, and reusable due to its separation from any knowledge of where that data came from. The command objects that it receives define exactly what it needs in order to process a payment.
Now that I think about it, that first “domain” that receives the file, creates, and saves discrete data for sending to the payment posting domain is more of an Anti-Corruption Layer between the system sending the file and the system (payment posting domain) applying the payments from the file. Either way, they are separated like domains would be.
Aggregates, Entities, and Value Objects
I will define these as briefly as possible, but they are major players in designing classes using DDD. None of these classes access the database or any other external files. They are strictly classes that contain properties and methods with business logic. This makes for them being excellent candidates for automated unit testing.
I will start with Value Objects. These are classes that are immutable (properties cannot change once they are created). This helps when it comes to validation. Since you only have to validate through the constructor, you don’t have to worry about multiple locations of validation, since none of the properties are going to change past the initial validation when the object is constructed. This makes for much easier testing, and less room for error.
Value Objects equality is defined by the whole of their properties. Meaning, if the properties of two value objects are equal, then the objects themselves are considered equal. An example would be an address. If two address objects have the same city, state, zip, address 1, and address 2, then you probably don’t care if they have different IDs, since the properties are what define whether that address is the same or not. This is helpful in that they can be used among many different common groupings of attributes (ex: address) throughout multiple classes in an application while containing the common code to be shared.
Entities are domain classes that are uniquely defined by a unique identifier. They contain any business logic related specifically to that entity (as do value objects and aggregates). Some examples of entities that I created are ClaimPost and Charge with methods such as GetTotal() and RollToNextInsurance(Payer nextPayer), respectively.
Aggregates are logical groupings of entities that make sense to be saved under an “all or none” (transactional) scenario. The root entity is the aggregate. The trick is to define these where they will not be so large as to take forever to retrieve or save.
For example, I had to break one of my aggregates into two. Health insurance payment files contain checks, and within those checks are the claim posts (payment information). A file can have multiple checks within it, but it is usually not an enormous amount. A check on the other hand can have any number of claim posts within it with information that is split out into multiple entities each. Therefore, it made logical sense (performance wise as well) to split these aggregates into a file aggregate (root entity) which contained the multiple check entities, then a claim post aggregate which contained the multiple entities within a claim post. The claim post aggregate referenced the check entity by its unique identifier. This made for much more efficient processing and retrieval.
Repositories are classes used to save and retrieve aggregates. They are represented by interfaces in the domain (specifically in the application and domain services), and their implementations are injected. This keeps the domain from ever needing to be aware of any persistence mechanisms or external infrastructure such as databases or web APIs or services, thus making it even more testable. I was able to choose an implementation using something such as entity framework or ADO.Net, knowing full well that I could change that to use something else at any point, even per repository if I wanted to, simply by implementing a new repository for that interface.
Application services are the entry points to a bounded context. They are what your client side (or other application services) logic will access in order to send commands in for what needs to be done. Application services receive the injection of the repository implementations. They are what use those repositories to retrieve the necessary aggregates for orchestrating the business logic from all needed classes to get the job done. Application service methods should be short and sweet. Especially since the vast majority of your logic will be contained in the appropriate aggregate, entity, and value object classes.
The above has only mentioned the very basics of DDD. There is far more to be learned than just this. This has been just a very brief introduction. For example, I did not discuss Domain Events or Domain Services. I close here with links to the best references out there about these topics.
Although Eric Evans Domain-Driven Design book is the original that I still plan on reading someday, I am glad that Vaughn Vernon’s book was recommended to me first, due to the code examples and project that I was able to reference. A couple of additional references that helped me once I was able to get through the book were this series of papers by Vaughn Vernon about designing effective aggregates, and this article by Vladimir Khorikov with a clearer definition on the differences between domain services and application services. Also, this article about the differences between domain events and integration events by Cesar de la Torre is the best source that I could find about that topic, which has references to other great sources on how to handle these types of events. I hope this introduction and the mentioned references prove to be useful for your learnings and journey ahead.