Clean Architecture

About

It is a model with the objective of decoupling the application independent business rules (or domain), from external resources (like frameworks, DBs).

Clean Architecture is Usecase driven.

In a way every Clean Architecture is also Hexagonal Architecture (Ports and Adapters), but the opposite is not.

In Hexagonal Architecture you don't have the distinction of usecases and entities, you only have the application (hexagon) which is the sum of usecases with entities.

Rules

The inner layers (high level) should not know implementations of external layers (low level), which is D (Dependency Inversion Principle - DIP).

4 Layers

Entities

They are responsible for abstracting the Independent Business Rules, which can be from an object with methods to a group of functions.

What are Independent Business Rules?

Ex.:

  • Is CarPlate valid?

  • What is the distance between two Coords?

  • How much is the Ride Fare?

  • How the Ride Status change?

Domain Objects vs ORM Objects

Clean Architecture + DDD (Domain Driven Design)

Entities can be broken in Aggregate, Entity, Value Object and Domain Service by using DDD (Domain Driven Design).

Clean Architecture don't clearly specify what an Entity can be as DDD does.

Anemic Domain

Anemic means that an Object only handles Properties (State) OR Behavior, but not both.

Use Cases

They orchestrate the entities and external resources, meaning they will be sort of a bridge between Entities and Interface Adapters.

Usecases are behaviors offered to the Clients. (You application exists because of your exposed Usecases)

Usecase names are related to [[Screaming Architecture]], which states to give names related to what it does.

In very simple cases, a Usecase can be the same as CRUD operations.

But as complexity increases they will be less alike.

Interface Adapters

These are the bridges between Usecases (High level) and External Resources (Low level).

This is where you:

  • Treatment of requests and HTTP responses. (Dealing with parameters)

  • Access the Database. (All the SQL code belongs here)

  • Integrate with External APIs.

  • Read and Write to files. (Interact with Filesystem)

  • Convert data. (Like convert to CSV, PDF)

External Interfaces

Here lies the lowest level of abstraction.

Where you actually interact with technology, with the components that connect to the Database, that deal with the HTTP requests, that interact with the filesystem or access S.O resources.

The ideia in here is to abstract only some level of the technology used, so that the Interface Adaptors won't know for instance if they will connect to a Postgres or MySQL database.

DatabaseConnection.ts
export default interface DatabaseConnection {
    query(statement: string, params: any): Promise<any>;
    close(): Promise<void>;
}
PgPromiseAdapter.ts
export default class PgPromiseAdapter implements DatabaseConnection {
    connection: any;
    
    constructor() {
        this.connection = pgp()('postgres://...');
    }
    
    query(statement: string, params: any): Promise<any> {
        return this.connection?.query(statement, params);
    }
    
    async close(): Promise<void> {
        await this.connection?.$pool.end();
    }
}

Interface Adapter vs External Interfaces

An example of mixing Interface Adapters with External Interfaces would be:

Having the code that connects to the Database inside a Repository like this.

UserRepositoryDatabase.ts
class UserRepository {
    async getUserById(userId: string): Promise<User> {
        const connection = pgp()('postgres://...');
        const user = connection.query('SELECT * FROM user WHERE id = $1', [userId]);
        await connection.$pool.end();
    }
}

So, there is a certain level of coupling of Postgres with our Interface Adapter that is called from the Usecase.

Examples of Folder Divisions

Simpler

/src
  ├── /core
    ├── /exceptions
    └── /entities
      └── <entity-name>.ts
  ├── /usecases
    └── <usecase-name>.ts
  ├── /infra
    ├── /repositories
    ├── /gateways
    ├── /http
    └── ...
  └── main.ts
/tests
  ├── /unit
  ├── /integration
  └── /e2e

More complex

Last updated