Web Engineering 117: Domain Driven Design Introduction
Domain Driven Design (DDD) is by far my most favourite approach to building platforms and solving problems. At it’s most basic, DDD attempts to group like concepts into “Domains”. Domains provide developers/teams/organizations with very clear boundaries.
Let’s take a look at a sample system like an accounting system.
Our system has a few core concepts;
Accounts
Customers
Vendors
Transactions
On the surface you can conceptually think of an accounting system like an excel workbook. Each Account is a workbook sheet. Each row is a transaction. You either have a customer or a vendor, and some money amount per row. This basic view of the data is how most developers would approach this problem.
Let’s say we have an Ecommerce website and we sell an item. The site adds a transaction to the database (a credit). Now what if we want to integrate another revenue channel. We receive a bulk list from our point of sale system; it needs to lock our database for x minutes while it uploads, what happens to our ecommerce website. I mean it’s only a few minutes right? Well now we need to add revenue transactions from another source, and another… quickly this becomes a giant tangled mess. Suddenly our database has no one single owner… This becomes a nightmare if you ever need to change the schema on any of these tables.
DDD helps prevent these problems by securing “domains” in their own logic. As a developer who “owns” the domain you decide how systems can interact with your domain (typically by listening for events in your systems) and then you create commands to do work in your domain.
A perfect example is a email microservice. Your system (The Email Domain) needs to send out emails to customers via a third party email service like SendGrid. The marketing team gives you the designs, your team implements them and coordinates with other teams to source the data for them. Other domains like (User Domain, or the Security Domain) will raise Events that communicate what has happened, and your domain will act upon them.
A basic system might need:
Reset password
Two Factor Auth
Account Verification
Unknown Login notification
Created Account
You will notice that all of these functions are easily transformed into Events. The power of domains is that they allow multiple different aspects of a system to independently act upon the same event. This is the natural progression/evolution of procedural form submission code.
Some of the rules that I like to follow when building a system based upon DDD:
Events can be consumed by other domains (if something happens other people might want to know about it)
Domains cannot use commands that do not exist inside their own domain (play in your own sandbox)
Any state change should be wrapped in a command (you never know when you might need to do this again, or in a different order)
Any state change should fire an event to let the system know something changed
To circle back to the accounting example;
Let’s say a customer ordered an item through our Ecommerce store.
PlaceEcommerceOrder command. this would generate OrderWasPlaced event. (Order Domain)
This would be listened to by (Accounts domain) and GenerateInvoice command is issued. Followed by InvoiceGenerated.
The (Transaction Domain) would listen for InvoiceGenerated and add a debit (CreateDebit Command) to the customer account and TransactionAdded event would fire
The (Email domain) would listen for TransactionAdded, checking to see if the balance was negative (ie they owe us money) and send out an email with the required details.
Now let’s add our Point of sale; we take the bulk import and for every row we add commands (PlacePOSOrder). The commands simply emit OrderWasPlaced. Nothing else to do the system just works. Also, We are no longer operating directly on the database so we eliminate the POS system halting our system. A third channel? No problem new Command and handler.
Instead of a single execution flow in controller method, we have now abstracted that logic into different “domains”/”systems”. Instead of copy/pasting code from one controller method to the other, or using inheritance to glue commonalities together, we have created a robust system of dependencies that is very easily tested at an integration level. In practice you typically have a file in your configuration dedicated to the mapping of events to listeners/subscribers and making it very easy to understand the flow of data through the system. Documenting the domain dependencies becomes trivial, since the only pieces of data that are being transmitted comes in the form of events.
Bonus points for those who realize this type of system has a perfect way to create an audit log.
Conclusion
DDD is the adult way to take system design and turn it into Lego. Build small compact purposeful systems and wire them together to solve your problem all while creating clean and testable code that you can be proud of.