CODEX
Introduction to Domain-centric Architectures (clean, hexagonal, …)
I recall struggling to understand clean and hexagonal architectures, so I decided to write my explanation by resorting to evolving models, resembling the way we learn.

The hexagonal architecture (also known as ports and adapters) is a must-know architectural pattern to separate the domain from the infrastructure. The onion architecture and the clean architecture introduce entities and use cases. All of these architectures are domain-centric — they’re derived from domain-driven design (DDD). I’ll present what I believe matters from each.
The app
People and systems from the outside world interact with your app and vice-versa. This is our starting point. I’ll use the word app but that can mean many things; from a device driver to a server-side rendered UI. In domain-driven design, it usually means a bounded context. A domain-centric architecture is not about physical architecture but instead about software design. It boils down to the decoupling of the business domain from the technology, as we’ll see later.

Separating the domain from the infrastructure
Let’s zoom into the app and split it into domain and infrastructure. You could view them as high-level software layers. That division embodies the separation of business from technology.

➡️ The domain (short for ‘business domain’; also known as core) is the reason why your app exists. It’s the added value to the business/users/world. It’s the app’s essence and what makes it unique. It’s supposed to be independent of technologies like databases, web APIs, and frameworks. All it contains is business logic and your domain modeling, which should use a ubiquitous language (inspired by the actual domain).
The core logic, or business logic, of an application consists of the algorithms that are essential to its purpose. They implement the use cases that are the heart of the application. When you change them, you change the essence of the application. Ports-And-Adapters
Core code doesn’t directly depend on external systems, nor does it depend on code written for interacting with a specific type of external system. Advanced Web Application Architecture
➡️ The infrastructure represents the technology; it’s all that’s not business domain; it’s how the app speaks with the outer world, whether humans or machines. On one hand, it’s how the app gets delivered (e.g. GUIs, web APIs, CLIs); on the other hand, it’s how it stores and gets its data (e.g. databases, web APIs, filesystems).
A domain-centric architecture aims to protect the domain, which contains all the essential complexity (the domain complexity we are trying to model). You should build your codebase around the domain, not the data, the database, or any other technology. The domain couldn’t care less about the surrounding infrastructure. To put it simply, the infrastructure is like a shell surrounding the core — domain. Ultimately, you could swap all the infrastructure without impacting the domain.
Domain
business / core
stable code / high-level code
essential complexity
the reason to build the app (the essence)
Infrastructure
technology / I/O
volatile code / low-level code
accidental complexity
the supporting technical mechanismsEven in a simple app like a console utility, it’s worth separating the algorithm (the goal of the app: the domain) from the rest (the I/O: the infrastructure). This separation is the first stage, but the most important one. Many apps only need to get this far. You get lots of benefits for a very low cost (code readability is the most obvious).
Adapters, use cases, entities
OK, we’re ready for the second maturity level. This will be a refinement of the previous stage. Let’s consider the most relevant components of the infrastructure: the adapters.
➡️ Adapters are how your app interacts with the world. They wrap the communication with an external mechanism, isolating technical details. Common examples include API gateways, controllers (e.g. REST, GUI), loggers, monitors, email/SMS services, queue handlers, and database repositories. Adapters also mediate access to unpredictable things like identifier generators and the system clock.

Adapters bridge the gap between the app and the outside (they represent the outside). Since the “database is a detail” and “the web is a detail”, every interaction with them is always done through an adapter.
The adapters are part of your codebase. The remaining portion of the infrastructure is drivers, MVC frameworks, web frameworks, filesystem APIs, client libraries, and any other mechanism that bridges away to the outside. Handlers are usually coupled with frameworks and that’s fine because they are supposed to be thin. They are just translators and only contain data conversion (i.e. parsing, formatting, rendering), error handling, and validation of syntactic rules (e.g. fail if a string is parsed as an integer). Never put business logic in adapters (e.g. web handlers, UI controllers, event handlers, service gateways).
Recall that the domain embodies the domain’s business rules. This is typically called the “business logic layer”. If we zoom into it, we’ll see the use cases and the entities.

➡️️ Use cases (also known as ‘application services’) represent user-centered operations like Checkout or View Debts; they're the reasons to build and use the app. Use cases are not CRUD actions (that would be technical); instead, they encode business-sounding interactions with the app.
To make your app’s intents clear, use cases should be first-class citizens of your app. Additionally, a use case name should start with a verb (e.g. Create User, Delete Account, Withdraw Money). It can be a query if you’re interested in its output (e.g. Get Loan) or a command if you care about its side effects (e.g. Make Loan) (command-query separation).

Use cases should bear no dependency on the way they’re delivered to the outside world (e.g. GUI, CLI). They solely depend on entities and abstractions of adapters. Use cases should be pure, so you should isolate all side effects and nondeterministic things into adapters (e.g. updating data, network calls, and reading the system clock).
➡️ Entities are business objects as they reflect the concepts that your app manages (e.g. Account, Deal, Movie). There’s a common belief that entities don’t contain logic but that’s wrong (anemic domain model antipattern); entities have domain logic that relates directly to them.


Use cases deal with entities, but entities don’t depend on anything else. Both use cases and entities contain business validation rules (i.e. semantic rules, also known as domain invariants). For example, “a loan’s due date can’t surpass 30 years”.
The domain contains other elements like events, value objects (e.g. Email, Point) and enums (e.g. PaymentType) and all that represents business domain concepts, but never any reference to technology, such as REST, JSON, networking, file system, ORM, SQL, MVC, frameworks, templating engines, and environment context (e.g. env vars).
Core code doesn’t need a specific environment to run in, nor does it have dependencies that are designed to run in a specific context only. Advanced Web Application Architecture
Database tables, ORM entities, mappers, DTOs (e.g. read/write models, serializers, view models, etc.), and GUI concepts are not domain entities; they should not contain business logic. Specifically, avoid ORM objects in the domain as they pollute it with technology.
Driving and driven adapters
Recall that adapters are the mediators between the outside world and the domain (i.e. core app). We can categorize them into primary and secondary:
➡️ Primary adapters are entry points of your app operated by primary actors such as users, other systems, and automated tests. They are ‘driving adapters’ since they trigger the domain code. Primary adapters include APIs, GUIs, CLIs (notice they’re all interfaces), jobs, data migrations, and other scripts. They are also known as delivery mechanisms because that’s the only way your app’s value can be delivered to its actors — every interaction with your app comes through them.
➡️ Secondary adapters connect your app to secondary actors like databases and third-party services. They are ‘driven adapters’ since the domain triggers their usage. A secondary adapter represents a view of the world from your app’s perspective. This helps to insulate your code from changes outside of your control and therefore supports teams during contract changes since they represent single places of change. Typical examples include API clients, cloud storage clients, database adapters, and the system clock, loggers, and monitors.

Primary adapters
Convert a concrete technology to domain
Entry points ~ Delivery mechanisms
Interfaces provided to primary actors
'Driving' because they invoke the domain
Triggered by a primary actor
Secondary adapters
Convert domain to a concrete technology
Interact with secondary actors
'Driven' because they're triggered by the domain







