Clean Architecture Overview

Luke, 24 Mar 2022

I wanted to give a clear example of what clean architecture really means in practise for a mobile developer in 2022. We're working potentially with SwiftUI, with Combine, async/await, and lots and lots of other technologies. How does it all fit into a clean architecture? Clean architecture has become a go-to in terms of software design philosophy in recent years, for good reason: it has a clear and simple structure that can make software flexible and future proof. Here I just want to go over the core fundamentals of clean architecture, and in the next article I'll give an example of how that can work with SwiftUI.

So heres an overview of the clean architecture philosophy. First: we are splitting the component parts of the application into a hierarchy of layers. Second: We use that hierarchy to govern the communication between component parts, to make things testable, reliable and independent. The long term goal is to allow the software to be future proof by being sufficiently flexible to future changes.

So first, what are the layers? Think of these in order of, the godly, most important, untouchable elements at the top of a tower, and as you go down through the layers, they become lesser and lesser mortals. This then dictates how they communicate with each other.

The tower of elements

Domain Layer

So at the top of the tower, the most sacred of the elements: the Domain layer, containing mostly the data objects. These pure and perfect things represent the core of the application in cold hard numbers. Most likely they are a set of structs, for example: Person struct, containing a persons name, age, height, etc. Or a Form struct, containing the contents of a filled in form. Or a CreditCard struct, containing the card number, expiry, owner name, etc. These pure data structs are the bosses - they dont trouble themselves communicating directly with lower elements. I like to imagine the Domain Layer as like a cathedral at the top of a tower, containing the high priests of the land.

Use Cases Layer

Next down the tower are the UseCases, aka Application Layer. These dictate how those previously mentioned data objects are used in combination, to satisy an example use of the application. For example: a user wants to be able to order a credit card. For that to be achieved, some logic must check that the Person is over 18, that they have completed a Form correctly, and that a CreditCard object has been created and has a status of 'ordered'. There will be some logic here, but this standard use case could be repeated across different front end applications, like Web, Mobile App, or even in person in a bank branch. For each scenario, the underlying logic would be the same, which is why its better encapsulated up here near the top of the tower, for reuse everywhere. Often the terms Service or Manager are appended to Use Cases.

In terms of communication, its ok for Use Cases to refer to Domain layer objects by name, directly. But the reverse is never ok - for Domain objects to mention UseCase objects. This is the dependency rule in action, with upper (or inner) level objects left untroubled by the names of things below their level, so they are safely separate, and changes below their level dont affect them.

Adapter Layer

At the next level, adapter objects communicate with the above use cases / business rules for consumption by an external agency, such as a mobile or web GUI, or a database. For GUI, this level would contain all elements of a standard application architecture, such as the Model, View and Controllers if using MVC, or the Models, Views and ViewModels in MVVM. For clarity, the models here, according to a strict clean architecture implementation, would not be the actual ones from the Domain layer. A flow of data would be more like: a Presenter or Controller from this level would pass a data object to a UseCase object, and an output object on the UseCase could then populate the passed data object with the data that the consumer interface requires, and return it. In reality this exchange would be governed by an interface or interfaces - specifically protocols in Swift. For a database, an adapter here between the database (that actually lives in the infrastructure layer) and the UseCase, would take the data from the database, query a UseCase, get the result, and populate the database with it. The GUI could also query the same database adapter for that result, to display it.

Infrastructure Layer

Finally below the adapter layer would lie things like Databases, Web Frameworks and other external items. They are kept to an outward edge so that changes in them dont affect the internal workings of our application. For example a CoreData database might live at this level, but if we decided to swap out this database for another type, it is easier to achieve as it is not mentioned throughout the app, and is safely separated.

Tagged with: