DDD, Ports & Adapters and CQRS reference architecture (C#)
My previous blog post describes how to apply the concepts of Domain-Driven Design, Ports & Adapters and Command Query Responsibility Segregation in a real C# application. Several readers requested an example project they could refer to while implementing these concepts in their projects.
The theory I’ve read does not describe how to implement things explicitly. Software architecture is something that dictates how long it takes developers to deliver new features in their domain. Every domain is different. And every application has different requirements. There is no correct, or wrong way of doing things. Either something works in your domain, or it doesn’t. Use my code as inspiration or as a conversation starter.
I’ve made choices in my example code. They’re made to fulfill some imaginary requirements. This article describes the choices I’ve made in my implementation.
The project structure: It’s a dispatching application
The application I’ve built is a simple dispatching application. It dispatches cabs and they drive customers to the train station.
A project structure should tell the developer what kind of application he’s working on. This application is about dispatching. Is does something with the AAA, too. It has an API, a broker and it stores some stuff in a database.
- The Dispatching project contains the domain model.
- The Dispatching.Aaa project retrieves traffic information from the AAA. The solution will contain lots more of such projects, eventually. Creating invoices in a PDF format might, for example, result in Dispatching.Printer, and sending e-mails to customers might result in Dispatching.Emails. That way, the first glimpse of the application you’ll get will tell you this application does something with the AAA, prints stuff and sends e-mails and you’ll be able to tell all this without opening a single file.
- The Dispatching.Api implements the REST API.
- And so forth…
Why so many projects?
Businesses hardly ever require an application have a RESTful interface, nor do they require an application to be a CQRS application. Eventually, these choices are made by the developers or the architect, within the boundaries of the functional requirements.
Some non-functional requirements do require things to be done in a certain way. Refer to these choices as the -ilities of the application. For example:
-Ilities change. An application should support such change. Not mixing business rules with infrastructural concerns makes that possible. In my case, I’ve written down my business rules in the Dispatching project. It has no dependencies. That makes it easy to just take that part, and plug it into some other type of application, without CQRS, for example.
In this case, I’ve chosen to apply CQRS. So, we’ll be needing a read-model and some queuing mechanism: A broker. But who says we’ll be queuing commands through posting JSON to some endpoint? They might as well have been queued by a WinForms application. That’s why I’ve chosen to put the API in a separate project.
Map, invoke, map! And map some more…
Business logic is executed by invoking the domain model. Everything must conform to the format of the domain model. And those are hard to (de)serialize considering that they don’t have public setters. As a result, several proxy classes appear in the solution map the domain model into an external data contract, invoke a third party service and map the result into a domain model:
That creates the following folder structure:
Provide a clear API
Throughout my career, I’ve seen several codebases deteriorate because developers started using too many classes in too many other projects, creating way too many dependencies with a big spaghetti as a result.
Don’t expose your privates!
A project that implements secondary ports should be a swappable, autonomous, testable component that has one single dependency only: The domain model.
A project itself is responsible for providing instances of concrete implementations of interfaces. Only interfaces (or abstract classes) that will be used by dependent projects should be public:
Making startup.cs readable
Having concrete implementations internal results in the following problem: Who will instantiate them?
Note that every project has a configuration folder. Every folder contains a Dependencies.cs file. It registers the classes that are in the project, so the dependency injection framework can instantiate them:
These dependencies contain the following type of code.
As a result, the startup.cs file looks like this:
After writing an article about DDD, CQRS and Ports & Adapters, several readers requested a sample codebase that implemented these concepts. It’s now on GitHub. Find it here.
The project is divided into several projects to make sure changing -ilities can easily be implemented. The domain is the heart of the application, and everything is connected to it. To interact with the domain, foreign data contracts are mapped into entities that are known to the domain, the domain is being invoked and the results are mapped into the foreign data contract. With a lot of mapping as a result.
Every project provides an API by exposing interfaces only. The concrete implementations of these interfaces are internal to the project. That creates a clear API.
This is an example of how DDD, CQRS and Ports & Adapters can be applied. There’s no written law that dictates things to be done in a given way. What works, and what doesn’t depends on your domain and your requirements.