Practicing Domain Driven Design. Part 3: Implementation.

Bogdan Melnychuk
6 min readAug 25, 2018

--

In my previous articles, we covered the process of understanding the problem using an event storming session (part 1) and then defined bounded contexts (part 2). I strongly recommend that you read it first before continuing.

In this article, we will talk about software architecture and focus on implementation.

What is NOT software architecture?

Software architecture is not a framework, database or library. Those are tools that help you to build your application, but they can not be your application. A very common problem in software is that specific frameworks dictate your application architecture. As a result, business logic leaks into the framework, persistence logic or to the UI, and you get spaghetti code which is difficult to test, refactor and maintain.

The Database. For some reasons, a lot of projects start with defining database tables and relations between them. Then domain models try to match the same structure and suddenly the database has a central role in your system.

The database

As a result, data access code gets mixed up with business logic that does not need the database at all. And what if you want to test that logic? That would mean hitting the database. In this case, your tests require additional set up, they are slow and probably will never exist. If the database is down, you probably cannot even work anymore.

But what is so special about a database? A database is just the storage, where we can persist the data. It may have some nice features, but in the end, it’s just a tool. This is not your architecture.

The client app. Another typical problem is when business logic leaks into the application’s UI, or vice versa when business logic code is concerned with UI concepts (colors, sizes…).

The client applications are mostly about presentation and there are a lot of cool frameworks that help you with that. But also there is a lot of logic that is not part of rendering: validation rules, analytics, a-b tests, etc.. I guess UI is the most dynamic part and at some point, you may want to try something new (change the framework completely or use some alternative libraries). Does it mean that you need to rewrite the whole client? Probably you still have the same parsing rules, analytics events, and backend integration. Ideally the rendering part should turn your application state into something that the user can interact with.

What is clean architecture?

There are a lot of articles about clean architecture. At the highest level, it is a modular system that suggests dividing software into layers. it strictly follows the design principle separation of concerns.

Those are the main criteria of clean architecture:

  1. Testable.
  2. Independent of frameworks and interfaces to third-party dependencies.
  3. Independent of a database.
  4. Independent of a UI.
Clean architecture

The arrow in the picture is the dependency rule: source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about the implementation of something in an outer circle.

Domain: we define business rules and entities. It encapsulates the most general and high-level rules. It is the least likely to change when something external changes.

Application: all of the use cases of the system. Use cases orchestrate the flow of data to and from the domain.

Infrastructure: generally composed of frameworks and tools such as the Database, the Web Framework, etc. This layer is where all the details go. The Web is a detail. The database is a detail. We keep these things on the outside where they can do little harm.

- from The Clean Architecture

Now we will apply it to our specific problem.

In the previous article, we have defined multiple bounded contexts. In this article, I will focus only on the ‘Question answering’ context. Also, I will ignore the ‘Progress’ context to make it simpler (you can check the GitHub repository for more code).

‘Question answering’ bounded context

Let’s start with the layers.

Domain: pure business rules and domain entities. ‘Question answering’ is about a question in the first place. So ‘Question’ is definitely our entity. Then we have two processes: next question generation and answer validation, which is also part of the domain.

Application: all use cases we can fulfill in this context. A use case is an intent, something we want to do. A good hint could be the number of commands to the context. In our case, we have two use cases: ‘Get Question’ and ‘Answer Question’.

Infrastructure: third-parties integration, communication to the other contexts. We know that the ‘Question answering’ context communicates to other contexts. For example, we want to get all available questions from somewhere. In this layer, we implement how exactly that communication happens.

‘Question-answering’ architecture

Now we can convert this to code. I will use javascript + flow.

Domain

We can start by defining the Question entity:

[DOMAIN] Question entity

As there is no real behavior right now, it’s just a data structure.

We have two policies: NextQuestionPolicy and AnswerValidationPolicy. First, we can define them as interfaces and think about the implementation later.

[Domain] Next question policy
[Domain] Answer validation policy

For now, we can implement our policies in the most simple way.

We can just pick a random question as the next question:

[Domain] Random next question policy

And to validate the answer we can just compare strings:

[Domain] Text equality answer validation policy

To cover the communication to another context we define the QuestionRepository interface. By this, we say that we need to be able to get available questions or specific question, but at this point, we don’t know how exactly it is going to be implemented.

[Domain] QuestionRepository

Application

Now we can think about the use cases. Imagine you need to describe the process of getting the next question without the code, probably the simplest algorithm would look like this:

  1. Get all available questions
  2. Take the next question from the available questions

If you convert this to code you have your use case:

[Application] GetNextQuestion use case

And the answer validation process looks like this:

  1. Get the correct answer to the question
  2. Compare the correct answer with the user’s answer

And this is how we can code it:

[Application] Answer question use case

We were able to write logic for use cases without concrete implementations. We don’t know how exactly our policies work, we have no idea where the questions are coming from (could be database, static file, HTTP call). And you don’t have to be a senior developer to follow the code.

Infrastructure

At this layer, we need to integrate with the ‘question-store’ context and define a public API for our context.

Integration with the ‘question-store’ depends on how it provides its public API. This could be the HTTP endpoint or some library. In our case, we are given functions that can be imported directly (see the full source code for complete example):

‘Question-store’ API

So all we need to do is to call a function and then convert the result to something we understand, in our case to the ‘Question’ entity we have defined earlier:

[Infrastructure]

And the last step is to define public the API of our context. From the previous article, we remember that this will be used by the client application. Here we should put all dependencies together and define concrete implementations for our interfaces:

Keep in mind that you don’t want to change your public API often, so think carefully about every field you want to expose. For this reason we did not return entities directly, instead, we convert them to raw data transfer objects (DTO).

Summary

As a result, we have completely independent pieces of logic in our application that can be tested and updated separately. You can easily improve policies in the domain or change the integration with the ‘question-store’ context in the infrastructure layer without touching anything else. All external interfaces are abstracted away (in our case, the details of the ‘question-store’ from a different context are hidden behind theQuestionRepository interface) so we have no third party dependencies in our business logic.

You can check the full code at GitHub: https://github.com/bmelnychuk/brainpicker

--

--