Clean Architecture

Parikshit Navgire
TechVerito
Published in
5 min readMay 2, 2023

Isolating business logic from platform

You can classify an application code into two types:

  • One that solves a business problem. Also referred to as business logic.
  • The other supports business logic. It acts as a platform or an infrastructure for business logic.

Clean architecture by Robert C Martin is a class design which helps isolate the business logic from the platform code.

Why do we need clean architecture?

My painful experience

Not long ago, I was leading a team working on enhancing a feature’s user experience. This feature was transitioned to us a few months ago, so we had a high-level understanding of its technical implementation.

The plan was to migrate the UI from JSF to React Js.

We imagined that apart from creating React components based on the new UX design, we need to create REST Controllers that will replace the backing beans and interact with the service layer. We gave our initial estimation based on this assumption.
As we started working on the user stories, we encountered use cases tightly coupled with the JSF backing beans. The line between the presentation and service layer had blurred. And it required more effort than our initial estimation as we had to separate the concerns between the presentation and business logic.
We achieved our objective, but we drew many lessons from this experience.
One obvious lesson is the importance of isolating the presentation and business logic from one another. But upon pondering more deeply into this aspect, I asked myself, is that enough? Even if I corrected that, was the feature isolated from the frameworks (spring, JPA-hibernate etc.)? Was it isolated from the database if it needed to change from Oracle to SQL Server? To a degree, ORM achieves that, but it has its limitation, which we try to fit into our business logic. You may have experienced it while upgrading the versions of these frameworks.

Isolating business logic

Clean architecture addresses the separation of concerns in the following way.

Independence from Frameworks

The conventional service-oriented architecture creates three layers to separate concerns: presentation, service, and data. But does it separate the business logic from frameworks or a platform? For example, it is widespread to see a Spring Boot application where the spring framework is deeply enmeshed in all three layers.

A higher degree of code cohesion.

Have you ever experienced the service layer getting bloated as more features are added to an application? You will see in the examples below how the use cases layer in the clean architecture helps in achieving a higher degree of cohesion.

Improving testability

Once we achieve the isolation of business logic, testability improves because you are concerned about testing the business logic without considering what framework or library the logic uses.

How to implement Clean Architecture?

Clean architecture creates layers to isolate the business logic. Refer to the diagram below. Note that the dependency flows from the outer layer to the inner layer, where the two innermost layers contain the business logic and the domain objects.

The classes in the inner layer are unaware of the classes in the outer layer.

Our business logic will reside in “use cases” that will modify, read or persist the state of our application represented by the classes in the innermost layer called “entities”.

All the layers outside these two layers provide a platform to execute the business logic.

Since the dependency flows from the outer layer to the inner layer, we can modify the outer layer without making changes in the inner layer. This means we can choose to replace console I/O with REST controllers or keep both. Also, we can choose to replace any existing persistence logic, e.g.:- replace the file system with a NoSql database.

To demonstrate this, let us take an example of developing a “skills management portal” for Human Resources. This system will help HR managers to create, update, delete and read skills. Examples of skills can be “Java”, “Kotlin” and “Data Science”. We want to provide a quick prototype of this application to the stakeholders.

Note that for the sake of illustration, I am considering only the use case of creating a skill.

Let us look at an example of creating a skill to see how we are isolating business logic from the infrastructure.

data class Skill(
val uuid: UUID = UUID.randomUUID(),
val name: String,
val domain: Domain
)

Following is the business logic handling this state. In this case, it is just persisting the newly created skill.

class CreateSkill(val skillRepository: SkillRepository) {

operator fun invoke(skillInput: SkillInput): SkillOutput {
val skill = skillRepository.save(skillInput.toSkill())
return SkillOutput(skill.uuid, skill.name, skill.domain.label)
}

}

The SkillRepository is an interface. And since we are doing a quick prototype, we have created the following in-memory implementation.

class InMemorySkillRepsoitory : SkillRepository {

companion object{
val skills = mutableMapOf<UUID, Skill>()
}

override fun save(skill: Skill): Skill {
skills.put(skill.uuid, skill)
return skill
}
}

Since it is a prototype, we create following console application.

class SkillsConsoleApplication

val createSkill = CreateSkill(InMemorySkillRepsoitory())

fun main(args: Array<String>) {
while (true) {
println("Create a skill ?")
val choice = readln()
if(choice != "Y") break;
println("Enter skill name")
val name = readln()
println("Enter skill domain")
val domain = readln()
createSkill(SkillInput(name = name, domain = domain))
}
}

But the stakeholders need to be more impressed and want to see the application in web form. How complex will it be to port the business logic to the web? Well, it is super easy. If you are using SpringBoot, you only need to create the following REST controller to invoke the business logic.

@RestController
@RequestMapping("/api/skills")
class SkillsController {

@Autowired
lateinit var createSkill: CreateSkill


@PostMapping
fun create(@RequestBody skillInput: SkillInput): SkillOutput {
return createSkill(skillInput)
}
}

How is the CreateSkill usecase getting injected without @Component annotation? Check the following configuration.

@SpringBootApplication
class SkillsApplication

fun main(args: Array<String>) {
runApplication<SkillsApplication>(*args)
}

@Configuration
class ApplicationConfiguration {

@Bean
fun createSkill(skillRepository: SkillRepository): CreateSkill {
return CreateSkill(skillRepository)
}
}

Note that we use SpringBoot and Spring’s dependency injection feature, but none of that touches the business logic. Also, as more and more use cases related to the Skill entity are added to the service, you can simplify the configuration of beans for those use cases by creating a single component SkillsComponent with all the related dependencies injected by Spring. This component will provide the create the use case objects.
What if we want to replace the temporary in-memory repository with a JPA MongoRepository? Check the following implementation.

@Repository
class SkillRepositoryImpl(@Autowired private val skillMongoRepository: SkillMongoRepository) : SkillRepository {

override fun save(skill: Skill): Skill {
return skillMongoRepository.save(SkillDocument.from(skill))
.toSkill()
}
}

Below is the model used to persist Skill.

@Document("skills")
data class SkillDocument(
@Id val uuid: UUID,
val name: String,
val domain: String
) {

companion object {
fun from(skill: Skill): SkillDocument {
return SkillDocument(skill.uuid, skill.name, skill.domain.label)
}
}

fun toSkill(): Skill {
return Skill(uuid, name, Domain.from(domain))
}
}

This way, Spring Data annotations don’t touch the business entity Skill.

You can see how the business logic becomes portable because it is isolated from the infrastructure. Only if there is a change in the expected business outcome will it be modified.

In conclusion, Clean architecture brings out the beauty of OOP by using abstraction and polymorphism. It also improves the overall testability. Here the tests for CreateSkill won’t have to bother mocking spring or mongo repository features.

You can check out the complete implementation here.

Further Reading:-

- Hexagonal Architecture

- Robert C Martin’s blog on clean architecture

--

--