Modern Clean Architecture

Bertil Muth
Javarevisited
Published in
7 min readAug 10, 2021

Clean Architecture is a term coined by Robert C. Martin. The main idea:
Entities and use cases are independent of frameworks, UI, the database, and external services.

A Clean Architecture style has a positive effect on maintainability:

  • We can test domain entities and use cases without a framework, UI, and infrastructure.
  • Technology decisions can change without affecting domain code. And vice versa. It is even possible to switch to a new framework with limited effort.

My goal is to flatten the learning curve and reduce the effort to implement a Clean Architecture. That’s why I created the Modern Clean Architecture libraries.

In this article, I will show you how to create an application with modern clean architecture. From an HTML/JavaScript frontend to a Spring Boot backend. The focus will be on the backend.

Let’s start with an overview of the sample application. A timeless classic.

The sample To Do List application

A to-do list is a collection of tasks. A task has a name, and is either completed or not. As a user, you can:

  • Create a single to-do list, and persist it
  • Add a task
  • Complete a task, or “uncomplete” it
  • Delete a task
  • List all tasks
  • Filter completed/uncompleted tasks

Here’s what a to list with 1 uncompleted and 2 completed tasks looks like:

We start at the core of the application, the domain entities. Then we work our way outwards to the front end.

The domain entities

The central domain entities are TodoList and Task.

The TodoList entity contains:

  • a unique id,
  • a list of tasks,
  • domain methods for adding, completing, deleting tasks…

The TodoList entity doesn’t contain public setters. Setters would break proper encapsulation.

Here’s part of the TodoList entity. Lombok annotations shorten the code.

What is the AggregateRoot interface good for? The aggregate root is a term from Domain-Driven Design (DDD) by Eric Evans:

An AGGREGATE is a cluster of associated objects that we treat as a unit for the purpose of data changes. Each AGGREGATE has a root and a boundary. The boundary defines what is inside the AGGREGATE. The root is a single, specific ENTITY contained in the AGGREGATE.

We can change the aggregate’s state only through the aggregate root. In our example, that means: we always have to use the TodoList to add, remove or change a task.

That allows the TodoList to enforce constraints. For example, we can’t add a task with a blank name to the list.

The AggregateRoot interface is part of the jMolecules library. That library makes DDD concepts explicit in the domain code. During the build, a ByteBuddy plugin maps the annotations to Spring Data annotations.

So we only have a single model. Both for representing domain concepts, and persistence. Still, we don’t have any persistence-specific annotations in the domain code. We don’t tie ourselves to any framework.

The Task class is similar, but it implements the jMolecules Entity interface instead:

The constructor of Task is package private. So we can’t create an instance of Task from outside of the domain package. And the Task class is immutable. No changes to its state are possible from outside of the aggregate’s boundary.

We need a repository for storing the TodoList. To stick to domain terms in the domain code, it is called TodoLists:

Again, the code uses a jMolecues annotation: Repository. During build, the ByteBuddy plugin translates it to a Spring Data repository.

We’ll skip the domain exceptions since there’s nothing special about them. That’s the complete domain package.

The behavior (i.e. the use cases)

Next, we define the behavior of the application that is visible to the end user. Any interaction of the user with the application happens as follows:

  1. The user interface sends a request.
  2. The backend reacts by executing a request handler. The request handler does everything necessary to fulfill the request:
    - Access the database
    - Call external services
    - Call domain entity methods
  3. The request handler may return a response.

We implement a request handler with a Java 8 functional interface.

A handler that returns a response implements the java.util.Function interface. Here’s the code of the AddTask handler. This handler

  • extracts the to do list id and task name from an AddTaskRequest,
  • finds the to do list in the repository (or throws an exception) ,
  • adds a task with the name from the request to the list,
  • returns an AddTaskResponse with the added task’s id.

Lombok creates a constructor with the TodoLists repository interface as a constructor argument. We pass in any external dependency as an interface to the handler’s constructor.

The requests and responses are immutable objects:

The Modern Clean Architecture libraries (de)serialize them from/to JSON.

Next, an example of a handler that doesn’t return a response. The DeleteTask handler receives a DeleteTaskRequest. Since the handler doesn’t return a reponse, it implements the Consumer interface.

One question remains: who creates these handlers?

The answer: a class implementing the BehaviorModel interface. The behavior model maps each request class to the request handler for this kind of request.

Here’s a part of the TodoListBehaviorModel:

The user(...) statements define the request classes. We use systemPublish(...) for handlers that return a reponse. And system(...) for handlers that don’t.

The behavior model has a constructor with external dependencies passed in as interfaces. And it creates all handlers and injects the appropriate dependencies into them.

By configuring the dependencies of the behavior model, we configure all handlers. That’s exactly what we want: a central place where we can change or switch the dependencies to technology. That’s how technology decisions can change without affecting the domain code.

The web layer (i.e. the adapters)

The web layer in modern clean architecture can be very thin. In its simplest form, it consists of only 2 classes:

  • One class for configuration of dependencies
  • One class for exception handling

Here’s the TodoListConfiguration class:

Spring injects the implementation of the TodoLists repository interface into the behaviorModel(…) method. That method creates a behavior model implementation as a bean.

If the application uses external services, the configuration class is the place to create the concrete instances as beans. And inject them into the behavior model.

So, where are all the controllers?

Well. There aren’t any that you have to create. At least if you only handle POST requests. (For the handling of GET requests, see the Q&A later.)

The spring-behavior-web library is part of the Modern Clean Architecture libraries. We define a single endpoint for POST requests. We specify the URL of that endpoint in the application.properties:

behavior.endpoint = /todolist

Iff that property exists, spring-behavior-web sets up a controller for the endpoint in the background. That controller receives POST requests.

We don’t need to write Spring specific code to add new behavior.
We don’t need to add or change a controller.

Here’s what happens when the endpoint receives a POST request:

  1. spring-behavior-web deserializes the request,
  2. spring-behavior-web passes the request to a behavior configured by the behavior model,
  3. the behavior passes the request to the appropriate request handler (if there is one),
  4. spring-behavior-web serializes the response and passes it back to the endpoint (if there is one).

By default, spring-behavior-web wraps every call to a request handler in a transaction.

Sending POST requests

Once we started the Spring Boot application, we can send POST requests to the endpoint.

We include a @type property in the JSON content. So that spring-behavior-web can determine the right request class during deserialization.

For example, this is a valid curl command of the To Do List application. It sends a FindOrCreateListRequest to the endpoint.

curl -H "Content-Type: application/json" -X POST -d '{"@type": "FindOrCreateListRequest"}' http://localhost:8080/todolist

And that’s the corresponsing syntax to use in Windows PowerShell:

iwr http://localhost:8080/todolist -Method 'POST' -Headers @{'Content-Type' = 'application/json'} -Body '{"@type": "FindOrCreateListRequest"}'

Exception handling

Exception handling with spring-behavior-web is no different than in “normal” Spring applications. We create a class annotated with @ControllerAdvice. And we place methods annotation with @ExceptionHandler in it.

See the TodoListExceptionHandling for example:

Note that in a real application, the different exception types need different treatment.

The frontend

The frontend of the To Do List application consists of:

We focus on main.js here. It sends requests and updates the web page.

Here’s part of its content:

So for example, this is the JSON object for a ListTasksRequest:

const request = {“@type”:”ListTasksRequest”, “todoListUuid”:todoListUuid};

The post(…) method sends the request to the backend, and passes the response to the response handler. (The callback function you passed in as second parameter.)

That’s all about the To Do List application.

Questions & Answers

What if…

… I want to send GET requests instead of POST requests?

… I want the web layer to evolve separately from the behavior?

… I want to use a different framework than Spring?

… I have a much bigger application than the To Do List sample. How do I structure it?

Here are the anwers.

Conclusion

In this article, I presented you a particular way to implement a Clean Architecture. There are many other ways.

My goal is to reduce the effort of building a Clean Architecture. And flatten the learning curve.

To achieve this, the Modern Clean Architecture libraries provide the following features:

  • Serialization of immutable requests and responses without serialization specific annotations.
  • No necessity for DTOs. You can use the same immutable objects for requests/responses in web layer and use cases.
  • Generic endpoint that receives and forwards POST requests. New behavior and domain logic can be added and used without the need to write framework specific code.

In my next article, I will describe how to test a Modern Clean Architecture.

I invite you to visit the Modern Clean Architecture GitHub page.

See the To Do List sample application.

And please give me feedback in the comments. What do you think about it?

If you want to keep up with what I’m doing or drop me a note, follow me on LinkedIn or Twitter.

Acknowledgements

Thank you to Surya Shakti for publishing the original frontend only todo list code.

Thank you to Oliver Drotbohm for pointing me to the awesome jMolecules library.

--

--