Date Published: October 28, 2020
I'm still pretty new to software development, so until recently I was very foggy on Domain-Driven Design (DDD). Here are the basics.
Firstly, I just want to point out something that I found confusing at first. Even though I know that TDD stands for Test-Driven Development and DDD stands for Domain-Driven Design, I had this weird equivalency in my mind before I really started looking into DDD. For anyone as confused as I was, there's not really an equivalency there. They're entirely different things.
DDD was first introduced in Eric Evans's 2003 book Domain-Driven Design: Tackling Complexity in the Heart of Software, which many consider a must-read of software developers. (Although I can't personally comment on that front, as I have yet to read the book)
DDD is not a one-size-fits-all silver bullet. It's most applicable in complex domains (as the name suggests), particularly when the client finds it difficult to communicate their needs to the developers. Keep in mind that technical complexity is different from domain complexity. However, it does encompass a number of patterns which can be applied to various situations. Example: DDD would be overkill in an application that just needs CRUD (create, remove, update, delete) logic.
However, where DDD is applicable, it is very helpful. It simplifies design in a complex system and makes future extensions and maintenance far easier.
A big piece of DDD is solving problems via an understanding of client needs. Write code to build software to fulfill client needs and solve problems - the problem solving is the real goal here. "People don't want to buy a quarter-inch drill; they want to buy quarter-inch holes" (DDD Course on Pluralsight).
It's also important to really communicate with the experts in a given domain. A big piece of this is talking about the problem in the language of the problem, rather than in the language of the software that you'll use to solve the problem. Talking in the language of the problem doesn't just apply to DDD; it's related to the larger topic of maintaining a consistent level of abstraction in your code and the way you talk about your code.
Another important concept: focus on one sub-domain at a time.
bounded context - a region of the application where consistent behavior is expected; one region of the application may require a Client class with birthdate information and name, while another region may only care about the name. It's important to keep these regions separate to preserve consistent behavior and conceptual integrity (which The Mythical Man-Month talks about a lot)
Sub-domain is to problem space as bounded context is to solution space
Shared Kernel - a section of code/infrastructure that is shared between contexts. Developers do not change this without consulting people working on each context sharing that kernel!
Implementation of sub-domains should fulfil the Single Responsibility Principle - focus on the sub-domain you're working on implementing, nothing else.
Focus on behavior, not classes
"Anemic" domain models focus on object state
Entities - objects which are defined by their identity (rather than by their attributes)
Value Objects - objects defined by their value; immutable
Domain Services - operations that don't belong to an entity or value object
Domain Models should be simple
Aggregates are sets of entities and/or value objects that change together. Every aggregate has an aggregate root which is the parent of all objects in the aggregate. This root can be determined by asking: "if I delete this member of the aggregate, do I need to delete the others?" ("cascading delete") If the answer is yes, that object is the root. For example, if a Person object gets rid of its Phone Number, this is not cause for deletion of the Person. But if the Person is deleted, the Phone Number should also be deleted, so the Person is the root. A unidirectional relationship between the root and other objects in the aggregate should run from the root to the other objects. The root can reference the other objects, and only the root can reference those objects.
You can also have an aggregate that only contains one object.
Data changes to an aggregate should be Atomic (all or none of the transaction), Consistent (rules are enforced), Isolated (no conflicts from syncronous commits), and Durable (should be able to be retrieved even if system fails) (ACID).
Invariants - rules in an application that must be followed in order for the model to be valid. A real-world example would be the speed of light. The aggregate root should be responsible for enforcing invariants.
Finally, only use aggregates when they will decrease complexity.
Remember the Repository Pattern? Well, that comes from DDD!
A repository controls data access in order to maintain consistency. It is responsible for object persistence.
A repository's interface should be an "illusion of a collection," according to Julie Lerman. It can also provide methods for common queries, to keep code DRY.
Repositories should be provided only for aggregate roots.
Domian Events describe state changes and take responsibility for the activities that should take place when an event takes place, rather than the objects pushing those events having that responsibility. Domain events are classes. They're important to the domain model and should be in the model's ubiquitous language, etc. This is different from how events behave in many UI models, where they are generally handled by methods instead.
You should consider modeling behavior as a domain event when you describe the behavior as an "if this-then that" statement where the "that" doesn't belong to the class performing the "this."
Domain events should have all the necessary information to convey their message, but they should not contain excess information.
Eric Evans: "no model is going to be perfect"
Domain-Driven Design - an approach to software engineering that focuses on the problem space (rather than the solution space, or software) in order to simplify the design and increase extensibility and maintainability.
Ubiquitous Language - a set of terms in the language of the domain whose definitions all parties have agreed upon and understand
Bounded Context - an area of an application where consistent behavior is expected; it should be insulated from other areas of the application.
Sub-Domain - an independent area of the domain (problem space)
Shared Kernel - a set of files shared between bounded contexts
Anemic Domain Model - a domain model focused on data/state
Rich Domain Model - a domain model focused on behavior
Entity - an object whose identity is based on a continuity of identitity and which is not defined by its value
Value Object - an object whose identity is defined by the values its properties
Domain Service - operations that do not conceptually belong with an entity or domain object
Aggregate - a set of objects which all change together in a data transaction (they should all be saved at the same time). This set includes an Aggregate Root which serves as a parent to and controls access to the other members of the object.
Invariant - a rule describing what an aggregate's state must be in order to maintain a valid model
Repository - an object responsible for persistence and data access
Domain Event - an object that communicates and enforces an "if this-then that" relationship
Anti-Corruption Layer - an insulating layer between a bounded context and external systems (including other bounded contexts within the same application)
Thanks for reading! I hope you find this and other articles here at ilyanaDev helpful! Be sure to follow me on Twitter @ilyanaDev.