Hello! Let's begin our second lesson.
Introduction
In our previous lesson, we established that the primary goal of Domain-Driven Design (DDD) is to manage software complexity by creating a model that deeply reflects the business domain. We learned that this involves collaboration with domain experts and using a Ubiquitous Language.
But how do we structure our code to protect this precious domain model from being tangled up with technical details like databases, frameworks, and user interfaces?
Today, we'll answer that question. The learning outcome for this lesson is to describe the layers of a Clean/Hexagonal Architecture (Domain, Application, and Infrastructure). This architectural style provides a blueprint for separating concerns, ensuring that your core business logic remains pure, independent, and easy to test and maintain. It's a foundational pattern for successfully implementing DDD in practice.
1. The Principle of Layering and The Dependency Rule
At its core, Clean/Hexagonal Architecture is about creating boundaries in your code. The main idea is to separate the code into layers, with the business logic at the center, completely isolated from external systems. This is often visualized as a series of concentric circles or a hexagon.
You'll hear this pattern called by a few names:
- Hexagonal Architecture: Also known as Ports and Adapters. This name provides a great metaphor. The "hexagon" is your application core (business logic). The "ports" are the interfaces it exposes (like USB ports on a computer), and the "adapters" are the external tools (like a keyboard or a database) that plug into those ports.
- Clean Architecture: A term popularized by Robert C. Martin that generalizes this layered approach.
Regardless of the name, the goal is the same: to protect the core of your application.

This structure is governed by one simple, non-negotiable rule:
The Dependency Rule: Source code dependencies can only point inwards.
This means:
- The Domain layer knows nothing about any other layer.
- The Application layer knows about the Domain layer, but not the Infrastructure layer.
- The Infrastructure layer knows about the Application layer (and by extension, the Domain).
Outer layers can depend on inner layers, but inner layers must remain completely ignorant of the outer layers. Let's start with a video that introduces this concept.
Clean Architectures in Python - presented by Leonardo Giordani
This clip from the talk 'Clean Architectures in Python' by Leonardo Giordani introduces the layered approach and the all-important Dependency Rule.
Please watch from 12:00 to 14:40. Focus on understanding the concept of layers and the direction of dependencies.
Now, let's examine what goes into each of these layers.
2. The Three Core Layers
While Clean Architecture can be described with four or more circles, for our purposes in DDD, we'll focus on the three essential layers mentioned in the learning outcome: Domain, Application, and Infrastructure.
A. The Domain Layer: The Heart of the Business
This is the innermost layer and the most important one. It contains the "crown jewels" of your software—the business logic.
- Purpose: To represent the business concepts, rules, and logic in code. This is where the Ubiquitous Language lives.
- Contents:
- Entities and Value Objects: The building blocks of your model that we'll cover in a future lesson.
- Domain Services: For business logic that doesn't naturally fit within a single entity.
- Domain Events: To represent significant occurrences in the business process.
- Key Characteristic: It is completely independent. It has zero dependencies on any other layer in your project. It's pure business, expressed in Python, without any knowledge of how it will be stored, displayed, or accessed.
To understand what a Domain Service is, let's look at a quick definition.
Unpacking the Layers of Clean Architecture – Domain ...
The article 'Unpacking the Layers of Clean Architecture' by Dan Does Code provides a clear definition of services in each layer. Let's look at the Domain Service.
Read the section 'What is a Domain Service?'. Notice its three characteristics: it performs an operation related to a domain concept, it's stateless, and its name is part of the Ubiquitous Language.
B. The Application Layer: The Use Cases
This layer sits just outside the Domain. It doesn't contain business logic itself, but it directs the domain objects to perform work.
- Purpose: To orchestrate the execution of specific application use cases (e.g., "place an order," "register a user"). It acts as a mediator between the outside world and the domain.
- Contents:
- Application Services: Classes that correspond to use cases. They fetch domain objects, call their methods, and save the results.
- Interfaces (Ports): Crucially, this layer defines the contracts for what the infrastructure must do (e.g.,
IOrderRepositorywith methods likesave(order)orget_by_id(order_id)). It does not contain the implementation of these interfaces.
- Key Characteristic: It depends on the Domain layer but is completely independent of the Infrastructure layer. It tells the domain what to do and defines the interfaces for how things like persistence should happen, without knowing the specific technology used.
Let's read about the role of an Application Service.
Unpacking the Layers of Clean Architecture – Domain ...
Let's return to the 'Unpacking the Layers' article to see how Application Services fit in.
Read the section 'What is an Application Service?'. Note its role in orchestrating logic and sharing it between different user interactions (like commands and queries).
C. The Infrastructure Layer: The Technical Details
This is the outermost layer. It contains everything that your application needs to run but that isn't part of the core business logic.
- Purpose: To provide the technical implementation for the interfaces defined in the Application layer. It acts as the "glue" between your application core and the outside world.
- Contents:
- Adapters: Concrete implementations of the application's ports.
- Database Repositories: Code that implements
IOrderRepositoryusing SQLAlchemy, Django ORM, or direct SQL queries. - Web Frameworks: Controllers or views in Flask/Django that handle HTTP requests and call Application Services.
- External Service Clients: Code for sending emails, processing payments via Stripe, or publishing messages to a queue.
- Database Repositories: Code that implements
- Adapters: Concrete implementations of the application's ports.
- Key Characteristic: This is where the "dirty" technical details live. This layer depends on the Application layer to know which interfaces to implement. It's the most volatile layer—you might switch from PostgreSQL to MongoDB, or from Flask to FastAPI, but these changes should not affect the Application or Domain layers.
The following video clip explains why things like the web framework and database are considered "details" in the outermost layer.
Clean Architectures in Python - presented by Leonardo Giordani
Let's go back to 'Clean Architectures in Python' to see how external systems like web frameworks and databases fit into this model.
Watch from 16:54 to 24:30. Pay attention to why the web framework and database are considered 'details' and how the application interacts with them via interfaces.
3. Summary of Layers
This table summarizes the responsibilities and characteristics of each layer. It's a useful reference based on the "Service Cheat Sheet" and "Service Examples" from the article we've been reading.
| Layer | Purpose | Contents | Key Characteristic |
|---|---|---|---|
| Domain | Encapsulate core business rules and logic. | Entities, Value Objects, Domain Services, Domain Events. | Independent of all other layers. Pure business. |
| Application | Orchestrate domain objects to fulfill use cases. | Application Services, Interfaces for Infrastructure (Ports). | Depends only on the Domain layer. |
| Infrastructure | Provide technical implementations. | Database access, web frameworks, message queues, 3rd party API clients (Adapters). | Depends on the Application layer to implement its interfaces. |
4. How the Layers Connect: Ports, Adapters, and Dependency Inversion
We've established that the Application layer defines interfaces (ports) and the Infrastructure layer implements them (adapters). But how does the Application layer, which can't "see" the Infrastructure layer, actually use the concrete implementations?
The answer is Dependency Inversion.
This principle states that high-level modules (like our Application layer) should not depend on low-level modules (our Infrastructure layer). Both should depend on abstractions (the interfaces).
In practice, this is achieved through Dependency Injection. At runtime, an external component (often called a "DI container" or just the main entry point of your application) "injects" the concrete infrastructure class (e.g., SqlAlchemyOrderRepository) into the Application Service that needs it. The Application Service only knows it's receiving an object that satisfies the IOrderRepository interface, not which specific implementation it is.
Let's watch a final clip that explains this "Golden Rule" with a clear code example.
Clean Architectures in Python - presented by Leonardo Giordani
This clip from 'Clean Architectures in Python' perfectly illustrates the right and wrong way to handle dependencies, demonstrating the power of dependency injection.
Watch from 28:23 to 29:55. Compare the two code snippets. The first one couples the use case to a specific database implementation (Postgres), while the second one depends only on an interface, making it flexible and testable.
To reinforce the terminology, let's get a final, concise definition of "Ports and Adapters."
Shahriyar Rzayev: Building Hexagonal Python Services
The video 'Building Hexagonal Python Services' by Shahriyar Rzayev offers a very direct explanation of what ports and adapters are.
Watch from 01:26:06 to 01:26:49. This short clip clearly states that a port is the contract/interface and the adapter is the implementation.
Conclusion
In this lesson, we've outlined the structure that allows us to protect our domain model and put DDD principles into practice.
Here are the key takeaways:
- Clean/Hexagonal Architecture separates concerns into three primary layers: Domain, Application, and Infrastructure.
- The Domain layer contains the core business logic and is completely independent.
- The Application layer orchestrates use cases and defines interfaces (ports) for external concerns.
- The Infrastructure layer implements these interfaces (adapters), handling all technical details like databases, web frameworks, and external APIs.
- The Dependency Rule is paramount: dependencies must only point inwards, a principle enforced through Dependency Inversion and Injection.
By adhering to this architecture, you create a system where the business logic is shielded from technical changes, making it more robust, adaptable, and easier to test.
Preview of the Next Lesson
We've now seen how to structure a single application or service. But what about a large system with many moving parts? In our next lesson, we will "Define a Bounded Context as a primary architectural boundary for a software model." We'll learn how to apply the "divide and conquer" strategy we touched on in the first lesson to break a large, complex system into smaller, well-defined services, each with its own layered architecture.