Clean Architecture

How all software should be built?

Clean (Hexagon / Onion) Architecture

There’s been a shift to Microservices and Domain Driven Design for a number of years now. Predominately driven by the complexity and ability to adapt to rapid change demanded by Internet scale systems such as Amazon and Netflix. In this article, I’d like to put forward the view that all software should be designed to these principles.

First a Recap

The term was first coined by software pioneer and guru Robert C. Martin (Uncle Bob), following on from his seminal book Clean Code. However, there have also been several over notable contributions to what we know as Clean Architecture today. This includes the concept of Hexagon Architecture from Alistair Cockburn and Domain-Driven Design from Eric Evans. In turn, these principles themselves are built on long standing best practices in software leveraging concepts such as coupling and cohesion, fan-out, layers and encapsulation.

So what is Clean Architectue? In simple terms it’s about ensuring that domain (business / application) information and logic is completely isolated and not dependent on technology frameworks and infrastructure. The acid test is if you need to swap out the persistence / database component in your application, the domain information model and logic does not need to be altered. Similarly if you need to have multiple interfaces to your application such as Web UI, REST API and JMS Message. The Onion and Hexagon (shown above) model attempts to illustrate this.

The most important patterns in Clean Architecture are the Repository Pattern and Dependency Injection. A Repository is an interface defined in your domain model that any data persistence layer is requried to comply with. Typically a Repository returns only domain entities and is completely free of any dependencies or references to data store technologies (tables, columns etc.) or ORM frameworks, such as JPA or SQLAlchemy. Extensive use of Dependency Injection and Inversion of Control ensure that all dependencies are “injected” into classes and not statically defined or layered.

Show Me the Code

Lets get down to brass tacks and see some code. As example of Clean Architecture I’ve developed (or more truthfully continously refactoring) a simple API service against the famous Microsoft Northwind database.

In true DDD primciples, lets start with the domain entities. Here’s a definition of ProductDetails in Python (>= 3.7 with dataclasses).

@dataclass
class ProductDetail(Entity):
    __slots__ = [
        'name',
        'quantity_per_unit',
        'unit_price',
        'units_in_stock',
        'units_on_order',
        'reorder_level',
        'discontinued',
        'category_name',
        'category_description',
        'supplier_name',
        'supplier_region'
    ]
    name: str
    quantity_per_unit: int
    unit_price: float
    units_in_stock: int
    units_on_order: int
    reorder_level: int
    discontinued: int
    category_name: str
    category_description: str
    supplier_name: str
    supplier_region: str
    MAX_UNIT_PRICE = 10000.0

Now this class does suffer from what Martin Fowler calls Anemic Domain Model. However, the excuse I have is that this API is a Data Service for analytics, not an access point for a transactional application. Anyway, there is a tiny bit of logic with a MAX_UNIT_PRICE constant!

Next comes the Port or interface.

class IProductDetailRepository(ABC):

    @abstractclassmethod
    def find(
        name: str,
        unit_price_from: float,
        unit_price_to: float,
        category_name: str
        ) -> List[ProductDetail]:        
        pass

Note that the find method returns a List of ProductDetail.

In this example I have implemented a front end message bus and service manager that uses the Command Pattern. Here’s the definition of the SearchForProductDetails command.

@dataclass
class SearchForProductDetails(ICommand):
    __slots__ = [
        'name',
        'unit_price_from',
        'unit_price_to',
        'category_name'
    ]
    name: str
    unit_price_from: float
    unit_price_to: float
    category_name: str

    def __post_init__(self):
        if self.unit_price_from is None:
            self.unit_price_from = 0
        if self.unit_price_to is None:
            self.unit_price_to = ProductDetail.MAX_UNIT_PRICE
        if self.unit_price_from > self.unit_price_to:
           raise InvalidUnitPriceRangeException

The idea of the Command Pattern is that it reflects your API design and should contain all the logic and validation required.

Next comes the use of Dependency Injection. Here I create an instantiation of the ProductDetailsRepository (SQLite) and inject it into the Service Manager.

datastore_context = SQLiteDatastoreContext(app_config)
product_detail_repository = SQLiteProductDetailRepository(datastore_context)
service_manager = ServiceManager(product_detail_repository)

In the Service Manager the type of Command recieved is then matched to appropriate application logic use case.

    self.command_handlers={
            SearchForProductDetails: self.__search_product_catalog
        }
...

    if isinstance(message,ICommand):
        results = self.__handle_command(message,queue)
...
   def __search_product_catalog(self,
            search_product_details: SearchForProductDetails
        ) -> List[ProductDetail]:
        try:
            return self.__product_detail_repository.find(
                search_product_details.name,
                search_product_details.unit_price_from,
                search_product_details.unit_price_to,
                search_product_details.category_name
            )
        except Exception:
            raise

Hopefully this example highlights the key principles of Clean Architecture. You can find the example in my Github repo.

If you want to know more on Clean Architecture, I recommend the following resources:

So, Clean Architecture for all Software?

Hopefully this short article, and the numerous resouces out on the Web, have persuaded you that every piece of software should aim to follow the principles of Domain-Driven Design and Clean Architecture. Doing so massively improves software adaptability and agility, and these are the most important atttributes of any software in today’s world of fast moving technology.