1. Home
  2. Developers
  3. Clean Architecture - The Definitive Guide to building Software

Clean Architecture – Build Software like an Artisan

Harikrishna Natarajan

Harikrishna Natarajan

Software Architect who loves building apps that run off small devices.

Last updated on

If you ever walk through the kitchen appliances section of a shopping mall, you will see lemon juicers made of steel, plastic and wood. The raw material doesn’t matter. One glance at the simple tool and you know what it is. The details about what the juicer is made of, who made it and how the holes at the bottom were poked become irrelevant.

A temple is obvious from its structure. A shopping mall screams at you to come shop inside. Army enclaves have tell-tale layouts, stern looking guards and enough signboards to let you know that you should stay away and avoid trespassing.

Architecture-12_16X9-1180x664-ylzjg

Table of Content

Meanwhile in software…

That’s not true with software. Pick up the source code of any application in the world today. If you are a programmer, a look at the arrangement of directories tells you what tool was used to build it. ‘Oh this is NodeJS Express’, ‘Well, well, this is Ruby on Rails’, ‘Hey, this programmer uses Python and Flask, just like I do. Hi five!’.

Sentences like these are very common in the world of software. However, let’s stop these programmers and ask them. ‘Nice, but what does this program do?’. They stop in their tracks and then pepper you with jargon. ‘Uh, for that I’d have to go into the app directory and look at the contents of the view, model and controller sub-directories. Give me 30 minutes, I will figure out.’ ‘Let us run the program and we’ll find out!’

How that sounds

Let’s go back to the appliances and buildings and try the sentences of the software professionals. ‘It’s a building made of brick and glass, but I can’t tell you what it is until I go in and find out.’ ‘It’s a tool made of wood. Let’s try to put different things inside it, fiddle with it and find out what it does.’

Clearly, that’s not how the real world works. Why should software be any different?

What is clean architecture?

Clean architecture is a way of developing software, such that just by looking at the source code of a program, you should be able to tell what the program does. The programming language, hardware and the software libraries used to achieve the objective of the program should become irrelevant. The aim of clean architecture is to make the following sentence possible: ‘Hey, the arrangement of directories tells me this is a shopping cart app. I don’t know which programming language or software library is used. I need to go into the directories and find out.’

More than being a method of writing software code, clean architecture is a way of thinking. It is a way of designing things such that the question ‘what is it’s purpose’ becomes more important than ‘how is it made’.

Clean architecture was first introduced to the world by Robert Martin, a software engineer with over 30 years of experience. You can read his path-breaking book Clean Architecture: A craftman’s guide to software structure and design.

Clean architecture by example

It is hard to explain how clean architecture works without an example. So let’s dive in and make an example system. In our system, we will receive a greeting ‘Hi’ from the user while greeting him/her back with a ‘Hello’.

We always start any system by looking at who the users are and how they will use the system in steps. It is like a story narrative and is called a ‘use case’. The use case narration for our system goes like so:

  1. The greeter greets our system.
  2. On receiving the greeting ‘Hi’ (and only ‘Hi’), our system responds with ‘Hello’, which the greeter receives.
  3. Any greeting other than ‘Hi’ will be ignored and the system will simply not respond.

Observe the following about the above use case.

  1. It comprehensively covers every step in the use case covering all inputs and outputs. It distinctly says that only a greeting of ‘Hi’ will be responded to and that other greetings will be ignored without response. No error messages, etc.
  2. The use case also has obvious omissions. The word ‘greet’ is a vague verb which doesn’t say how it’s done. Does the greeter speak to the system and the system speak back. Does the greeter type at a keyboard or use text and instant messaging? Does the system respond on the screen, shoot back an instant message or send an email?

As far as a use case is concerned, those are implementation details, the decisions for which can be deferred for much later. In fact, input and output systems should be plug-and-play, where one system can be swapped for another without any effect on the program’s core working, which is to be greeted and to greet back.

The EBI system

The implementation of clean architecture is centred around getting a solid EBI system in place. But what’s EBI? It stands for Entity-Boundary-Interactor. The functionality that is absolutely essential, i.e. the business logic and rules of our software, are contained inside entities. A director named ‘interactor’ works with the entities to get the work done. A strict boundary is drawn around the interactor and entity. Everything that is not core logic or rules is kept outside the boundary. This outside world may change anytime, but the boundary should be so firm that the changes outside do not affect the entity or the interactor at all.

To uncomplicate this abstract description, let’s take an example. If you were to order omelette at a restaurant, the ONLY ONE person with the core functionality is the chef (similar to clean architecture’s interactor). Preparing the omelette is the only activity that is non-negotiable and it must be done to perfection, no matter what else happens. When you order an omelette, you expect to receive a nutritious, tasty omelette and nothing else.

Every other experience is peripheral. You may visit the restaurant, call the restaurant’s delivery service over phone or order over Swiggy (Input method). The restaurant may serve you the omelette on a china clay plate, a steel plate or deliver it to you in a paper package (Presentation).

It doesn’t matter how the chef gets the eggs, onion, chillies or spices, but he has the ingredients in front of him when he needs them (Inventory is a service managed separately from cooking). The chef may prepare the omelette on a non-stick pan over a gas-burning stove or with an anodised pan over an electric induction stove. So, even the tools for cooking may change (like using different software frameworks).

What matters in the end is that in response to your request for an omelette, you end up receiving a well-cooked omelette. As a user of the system, that’s the only thing that matters. And as a restaurant owner, cooking is the core, non-negotiable service.

EBI system in clean architecture

Let’s turn to how EBI is used in clean architecture. There are 5 components within the EBI framework.

1. Request model

The request model is an object that represents the input from the user to the system. The request model usually has simple data types like numbers and text. Our system has one input, i.e. greeting from the user. It is a plain text in English language, such as ‘Hi’.

2. Response model

The response model is an object that represents the output that is sent to the user of the system, usually in response to the input or due to other triggers such as a scheduled time or an event happening. The response output is also usually in forms such as text and numbers. In our system, the output is plain English text such as ‘Hello’.

3. Interactor (I)

The interactor is an object which receives inputs from user, gets work done by entities and returns the output to the user. The interactor sets things in motion like an orchestra director to make the execution of a use case possible. There is exactly one interactor per use case in the system.

4. Entities (E)

The entities contain the data, the validation rules and logic that creates an output in response to an input. After receiving input from the user, the interactor uses different entities in the system to achieve the output that is to be sent to the user. Remember that the interactor itself must NEVER directly contain the logic that transforms input into output. In our use case, there must a lookup table, where an entry will have ‘Hello’ as the response to the greeting ‘Hi’. The Interactor simply uses the services of this lookup table.

5. Boundaries (B)

Clean architecture is designed in layers, so that peripheral components such as output media and database systems can be swapped as requirements change. For this reason, the core components of the system, i.e. entities and interactor, never talk directly to the peripheral components such as a web framework (e.g. ExpressJS, Servlets, etc) or a database system (MySQL, Mongo, etc) directly. Rather, several interfaces called boundaries are made so that calls are made across them. One side of the boundary makes calls and expects a form of response that is agreed upon. The other side of the boundary receives the calls and returns those responses. Both sides usually do not know who is on the other side. They just act upon the requests and responses. Such design also allows components to be tested individually by using mock / fake components across the boundaries.

Let’s talk about three types of boundaries.

A. Input port

This is the boundary between the input system and the interactor. The input system does not deal directly with the interactor. Instead an interface is offered along with a set of method calls. These calls promise that the input will reach the interactor properly and that the use case will be executed.

B. Output port

This is the boundary between the output system and the interactor. This makes sure that the interactor does not know how the response will be shown to the user. The interactor passes along the data inside the response. But formatting the response is upto the output system on the other side of the boundary.

C. Gateway

The interactor and entities will often need to interact with the systems outside the core, such as databases, online services, email servers, SMS servers, etc. These boundaries are called gateways. Instead of hard-wiring the code specific to a service inside an entity, a boundary interface is made. The entity calls the methods over a boundary and the components on the other side will use the specific service. This approach makes the system very modular and plug-and-play.

Controllers

With our EBI system complete to take care of the use case, we must realise that ultimately the system will be used by humans and that different people have different preferences for communication. One person may want to speak to the system, while another prefers instant messaging. One person may want to receive the response as an email message, while another may prefer the system to display it on a big flat LCD with decoration.

A controller is an object which takes the input in the form the user gives and converts it into the form required by the request model. If a user speaks to the system, then the controller’s job is to convert the voice to plain English text before passing it on to the interactor.

Presenters

On the other side is a presenter that receives plain text from the interactor and converts it into a form that can be used by the UI of the system, e.g. a large banner with formatting, a spoken voice output, etc.

Let’s code.

Now that we have learnt some terms, let’s put them to use using some Javascript code and a diagram.

This diagram explains our system.

EBI System Diagram
EBI System Diagram

Please pore over the diagram and recollect the description of each component. Now it’s time to code. We shall use JavaScript. But as mentioned before, the choice of language is usually irrelevant. The principles can be applied to any language where object-oriented concepts are strongly supported.

Code for Input port (boundary acting as input to interactor):

/**
* @interface {IGreetingInput}
*/
IGreetingInput = function() {}

/**
* @method greet
* @param {String} greeting
*/
IGreetingInput.prototype.greet = function(greeting) {}

Code for Output port (boundary acting as output of interactor):

/**
* @interface {IGreetingOutput}
*/
IGreetingOutput = function() {}

/**
* @method greetBack
* @param {String} greeting
*/
IGreetingOutput.prototype.greetBack = function(greeting) {}

Code for Lookup gateway (boundary acting as gateway to an external service such as a database that contains the list of mappings of which greeting should be responded with which other greeting):

/**
* @interface
*/
var ILookupGateway = function() {}

/**
* @method
* @param {String} greeting
* @returns {String}
*/
ILookupGateway.prototype.findResponseFor(greeting);

Code for Lookup table (entity):

/**
* @class
* @constructor
* @param {ILookupGateway} lookupGateway
*/
let LookupTable = function(lookupGateway) {
this.lookupGateway = lookupGateway;
}

/**
* @method
* @param {String} greeting
* @returns {String}
*/
LookupTable.prototype.getResponseGreeting = function(greeting) {
return this.lookupGateway.findResponseFor(greeting);
}

Two things to note here.

  1. While initialising the LookupTable, the constructor accepts an instance of a class that is an implementation of IlookupGateway that we saw before this. The LookupTable doesn’t care which implementation is passed, so long as it is based on the IlookupGateway interface.
  2. The LookupTable simply uses the findResponseFor method of the gateway to get the response. On the other side of the gateway could be a database, a redis map, a JSON file or anything that can map one key to one value. Ideally, all validations and business rules also go into the entity’s method, i.e. getResponseGreeting. But for simplicity, I have omitted those extras.

Code for Greeting Interactor (interactor):

/**
* @class
* @implements {IGreetingInput}
* @constructor
* @param {IGreetingOutput}
* @param {LookupTable}
*/

let GreetingInteractor = function(outputPort, lookupTable) {
this.outputPort = outputPort;
this.lookupTable = lookupTable;
}

/**
* @override
*/
GreetingInteractor.prototype.greet = function(greeting) {
responseGreeting = lookupTable.getResponseGreeting(greeting);
outputPort.greetBack(responseGreeting);
}

This is the main orchestration code for our use case. When the interactor is created, it is supplied an output port, which is usually the presenter, and a lookup table entity. The interactor’s code inside the greet function simply uses the LookupTable entity to look up a response greeting. This greeting is then pushed over the output port, which defines a greetBack method.

A ‘main’ code to bind them all:

let lookupGateway = new DatabaseLookupGateway();
let lookupTable = new LookupTable(lookupGateway);
let greetingOutput = new GreetingPresenter();
let greetingInput = new GreetingInteractor();
let greetingController = new GreetingController(greetingInput);

The above code starts all the components and waits for the user input. Let’s look at each line.

  1. The first line creates an instance of a lookup gateway that uses a database to map one greeting to a response greeting. We have not shown the code for DatabaseLookupGateway since that is irrelevant to this blog post. In fact, that is the beauty of clean architecture. We just need to ensure that any concrete implementation of ILookupGateway implements the method findResponseFor that guarantees that a response greeting is looked up for every greeting that the user uses on the system. To the core components of the clean architecture software, the implementation is just a peripheral detail such that one can be swapped for another when the time arrives.
  2. The LookupTable entity is then created with the DatabaseLookupGateway as the parameter. The LookupTable entity will use the services of the gateway to look up responses.
  3. The greetingOutput is initialised to an object that implements the IgreetingOutput interface. This interface will directly receive the response greeting from the system. In our code, the GreetingPresenter is the chosen one. We haven’t given you the code for this object, but it receives the response greeting in a way that suits the output device of the system. E.g. an HTML page or a text-to-speech voice output, the text formatted as a stylish PNG banner, etc.
  4. Next, the GreetingInteractor is initialised as the implementation of IGreetingInput interface.
  5. The user’s greeting is fed into the interactor. But how? This is where the GreetingController comes in. We have omitted the code for the controller, but here’s how it will work. The input supplied by the user is converted from the user’s form to plain text form. E.g. if the user were to send his/her greeting as a chat, then the chat message’s body will be extracted, stripping out other information such as timestamp, etc.
  6. On receiving the user’s input, the controller converts the message to plain text and then calls the interactor’s greet method. The interactor’s presence is unknown to the controller. The controller only sees that an input port has been passed to it.

Conclusion

Clean architecture lets us forget about implementation details and allows us to focus on the functionality first. The purpose of the system comes to the fore, while the implementation details are pushed back and their decisions deferred for later. Only business logic is considered the core of the program, while details such as web protocol, screen size, database, etc. are implemented later. In fact, test cases are written such that the peripheral services are mocked using dummy objects, so that the core functionality can be tested thoroughly. Clean architecture says ‘logic first, details later’.

// Related Blogs

// Find jobs by category

You've got the vision, we help you create the best squad. Pick from our highly skilled lineup of the best independent engineers in the world.

Copyright @2024 Flexiple Inc