A recurring question I get asked from people not familiar to DDD is how to actually start implementing it.

Implementationwise the most important DDD concept for me is the notion that a single set of models cannot represent a complex system. Different perspectives stretch out the models making them ambiguous and difficult to maintain. DDD accepts this problem and tries to tackle it with Bounded Contexts, which are language boundaries that narrow the scope for the models. If you follow this fundamental principle you don’t need to do DDD “by the book”. On the other hand trying to apply DDD and not understanding this cornerstone is a recipe for disaster.

The opposite is also a good principle to follow, if the system you’re working on is not complex enough and can be represented by a single set of models, don’t bother with DDD.

DDD is a journey of research and refinement of domain knowledge, it’s more than just code.

Driving your design based on conversations and modelling the problem according to those conversations is the key take away from the whole technique. By u/n0xie

But this post is about code, and code in DDD is all about its tactical patterns. Such a vast technique like DDD can cause blank canvas syndrome so let’s concentrate on the first step and put some paint in the canvas. Assuming you’ve had a thousand meetings with domain experts and know all about the problem at hand, let’s create a domain! A domain should only contain plain objects, no application or infrastructure concepts should be allowed in it. Domain = business translated into code, it’s all about the business. You can do this separation in any language by using folders, namespaces or modules.

It’s very difficult to give a “cake recipe style” tutorial on DDD, so instead I’ll point out some things you should watch out for.

The Domain

Suppose you’re about to start a project on any language/framework, the domain should be built using only plain objects. So create a folder preferably outside of the application called Domain . Bounded Contexts will exist inside the domain folder. If an element of the domain has infrastructure like repositories and projections, only their interface (if supported) should be allowed inside the domain folder.

Bounded Contexts

One very important thing about bounded contexts: they don’t share logic between them. That means a model from one context cannot be used on another context. If the same model exist on both contexts you’ll need to create some duplication, it’s a bit counter intuitive but that’s how it is.

There are many ways of doing this duplication, if the Bounded Contexts live on the same server the duplication will mostly be about code, not data. Watch out for some signs:

Don’t mix logic between contexts, Bounded Contexts should be isolated from one another. Create tooling to keep them separated, for example you can make a build fail if mixing of contexts is detected.

The duplicated models on each context don’t need to look the same, they should only have what’s necessary to their contexts, they can also have different names. Bounded Contexts are a language boundary that creates the space for things to be different.

If you only need to read from a model in the Bounded Context don’t have write operations for that model there. Again, only have in the context what it needs to work.

You can even have a Bounded Context without any infrastructure, it could for example receive all the data it needs from the outside.

If contexts need to talk to each other prefer asynchronous communication between contexts using domain events when possible. If you need synchronous communication you could create gateway classes or something similar acting as an anti-corruption layer (ACL) between contexts.

And remember, a bounded context is a language boundary, that means someone somewhere must be actively using that language in order to justify its existence.

Repositories

I see people getting repositories wrong all the time. A repository should have a 1–1 relationship with a model, it’s a place where you store and retrieve that model, period. Think of repositories as a collection (with infrastructure) of a given object. Developers keep expanding what repositories do and before you realise they’re returning other objects or parts of objects, producing reports and aggregating data. Projections are more appropriate for that. If other objects or a collection of those objects are part of an aggregate root you should only get those objects through the aggregate root and not through the repository. Again, a repository should only have logic related to retrieving and storing one single type of object.

Projections

Similar to repositories in the sense that it usually needs infrastructure to work, AKA a connection to the database. As a rule of thumb if you need data that is not represented by any model, or only parcial objects, or if you need aggregated data, or computed data and so on… use projections.

Architectural Alignment

Just by describing how Bounded Contexts should be you can see how good they would look as independent services. If you create an independent or a “micro” service around a Bounded Context you achieve an architectural alignment with the technique, which is the best case scenario. In this case cohesion is way more important than size, things should be together if they belong together, that’s why I’m not a big fan of buzzwords like “micro services”.

Application and Infrastructure

With the domain in its folder and composed of only plain objects the rest is either application or infrastructure. I tend to also create a folder for infrastructure where I can put the implementation of repositories and projections (only their interfaces go into the domain folder, forgot?). For the application I keep the structure of whatever framework I’m using as it is: controllers, commands, configuration, views, etc…

Tests

There’s a natural alignment between these 3 layers with the 3 types of tests:

Domain = unit

Infrastructure = integration

Application = functional

Unit tests: if the domain is composed of only plain objects most of its tests will be unit tests, I mean more than 90% of the tests. You might need some in-memory integration tests to feel safe about how some elements like domain services work, but not much more than that.

Integration tests: in order to mock interfaces from the domain with confidence you need full test coverage of their implementations.

Functional tests: anything that needs to bootstrap and run the entire application in order to be tested. Usually API, HTTP or console/command tests.