Site Color

Text Color

Ad Color

Text Color





Sign Up to Save Your Colors


Clean Architecture Example in Kotlin by@the-code-gang

Clean Architecture Example in Kotlin

The Code Gang HackerNoon profile picture

The Code Gang

In the past few years, many blog posts and articles have been written that present the Clean Architecture, as it has been presented by Robert C. Martin (Uncle Bob) in his blog post and (in more details) in his fantastic book “Clean Architecture: A Craftsman’s Guide to Software Structure and Design.”

In this post, we present an example of a REST service that uses Clean Architecture and is written in Kotlin.

The source code can be found in this repo:

A short description of the clean architecture example modules

The project consists of 4 modules core, usecases, dataproviders, and delivery.

core module

This module contains the domain entities. There are no dependencies to frameworks and/or libraries.

usecases module

This module contains the business rules that are essential for our application. The only dependency of this module is to core. In this module, gateways for the repositories are being defined. Each use case defines the interface of the gateway that is required following the ISP. These gateways, operate on the domain entities defined in core.

In this module, UseCase and UseCaseExecutor are also defined.

The UseCase is an interface similar to the java.util.Function. It just gets a request and transforms it into a response.

The UseCaseExecutor handles the execution of a UseCase. To do so, it has an invoke method that takes the following arguments:

  1. the UseCase that will be executed
  2. a RequestDto
  3. a mapper function that converts the RequestDto to a Requestobject (the input of the use case)
  4. a mapper function that converts the Response object (the output of the use case) of the UseCase execution to a ResponseDto

There are three more overloaded versions of the invoke method, which omit the input and/or the output of the UseCaseExecutor.

Currently, the UseCaseExecutor implementation (UseCaseExecutorImp) is using java.util.concurrent.CompletableFuture and java.util.concurrent.CompletionStage for the execution abstraction. These abstractions are convenient as they can perform asynchronous executions and also have out of the box compatibility with most frameworks.

dataproviders module

This module contains the implementation of the gateways defined in the usecases module. This module depends on the framework that facilitates the data access. In our example, we use JPA and Spring Data. The Jpa*Repository classes are the actual implementation of the gateways defined in the usecases module.

These repositories, make use of the Spring Data JpaRepository. Here is an example JpaProductRepository.kt:

DBProductRepository is a subclass of the a Spring Data JpaRepository.

The entities in this module, are JPA entities, so mapper functions are required to make the translation between these entities and domain entities. In the previous snippet, we demonstrated how these mapper functions are used in the JpaProductRepository. An example of an entity isProductEntity.kt:

delivery module

This module contains all the details of the delivery mechanism that we use along with the wiring of the app and the configurations. In our example, we use rest services built with Spring Boot. Similarly, to the JPA entities of the dataproviders module, the DTOs have mappers to convert from and to the domain entities.

A rest controller gets the RequestDto and forwards it to the related use case through the UseCaseExecutor. The response of the use case (which is a ResponseDto) is the response of the controller's method that implements the endpoint. An example of such usage isProductResourceImp.kt.

The exceptions are handled by GlobalExceptionHandler.kt, and they are converted to ErrorDto.

Clean Architecture separates low-level details from high-level policies

In this pull request, we present how easy is to change the data layer without having to touch the business logic (core and usecasesmodules). The flexibility is provided by the clean boundaries that we have between each layer. Also, due to the dependency rule that outer layers depend on the in inner layers, the core and usecasesmodules are unaware of everything that we have changed in the outer modules. Hence, we don't even have to recompile these modules to deploy the application with the new data layer.

Moreover, it is much simpler for someone who joins the project to understand the domain of the application since the core andusecases modules contain only the business objects and the way they interact. Someone might say, that it is possible for a new team member to start writing new functionality without getting familiar enough first with the frameworks and/or the libraries that are used (of course there are some exceptions). The core of our application doesn't contain any magic framework that might make the learning curve steeper.

Clean Architecture is testable

Due to the separation discussed in the previous section, it is straightforward to test the business logic of the application without having to setup any framework or library. We can write simple unit tests to test the complex business logic in our application without having to deal with the frameworks. Also, we don’t have to change these tests when we change something in a framework. Of course, we should have integration tests that ensure that everything is wired up correctly, but these costly tests will only be a few and will not increase when the complexity in our business logic is increased.

Clean Architecture is not a silver bullet

It is easy to notice that we have written way too much for the simple functionality that out application offers, even in a concise language like Kotlin. Clean Architecture requires some abstractions that might be considered as over-engineering if the application we are building is relatively simple. It might not feel natural to indirect the invocations to the data and the delivery layer in the beginning, but as we add new functionality to our system, our velocity is increased, since we separate pure domain code with frameworks and configurations.

Clean Architecture, like every software architecture, comes with a cost, so it is up to the developers/architects to decide if they can benefit from it or not.

Further Reading

  1. “Clean Architecture: A Craftsman’s Guide to Software Structure and Design (Robert C. Martin Series” by Robert C. Martin (Uncle Bob)
  2. The Clean Architecture
  3. Clean Architecture using Golang