Skip to main content
Create your own

Clean Architecture for Services

Hello! Welcome to your next lesson.

Introduction

In our last session, we focused on the tactical DDD pattern of Aggregates, establishing them as the consistency boundaries that protect the integrity of your domain model within a bounded context. We defined what an aggregate is, how to size it based on true invariants, and how to enforce its rules through repositories and optimistic concurrency control.

Now that we have a well-defined domain model, the natural next question is: how do we structure the code of the service that will host this model? This brings us to today's learning outcome: Apply Clean Architecture layers and dependency rules to structure the code within a service or bounded context.

Clean Architecture, popularized by Robert C. Martin ("Uncle Bob"), provides a robust blueprint for organizing software. Its primary goal is the separation of concerns, which results in systems that are independent of frameworks, databases, and UIs, making them highly testable, maintainable, and adaptable to change.

Today, we will explore:

  • The core principles and layers of Clean Architecture.
  • The fundamental Dependency Rule that governs the entire structure.
  • How to map these abstract layers to a practical project structure.
  • Where our DDD concepts, like Entities and Aggregates, fit within this architecture.

This lesson bridges the gap from domain modeling (what the system does) to software architecture (how the system is built).

1. The Theory: Layers and The Dependency Rule

Clean Architecture is not a single invention but an amalgamation of similar ideas like Hexagonal Architecture and Onion Architecture. They all share a common objective: to place the core business logic at the center, shielded from the volatile details of the outside world.

To understand the foundational principles, let's go straight to the source.

Clean Architecture by Uncle Bob - The Clean Code Blog

This blog post by Robert C. Martin, 'The Clean Architecture,' is the canonical text on the subject. It lays out the philosophy, the structure, and the rules that define the pattern.

Please read the entire article. As you read, pay close attention to: The diagram with the concentric circles at the top of the article. This is the classic representation of the architecture. The description of each layer: Entities, Use Cases, Interface Adapters, and Frameworks & Drivers. The Dependency Rule, which is the most critical concept. The section on 'Crossing boundaries,' which explains how the Dependency Inversion Principle is used.

Let's summarize the key concepts from the article.

The Layers

The architecture is visualized as a set of concentric circles, each representing a different layer of the software.

  1. Entities (Inner-most): This layer contains the core business logic. In our context, this is precisely where the Entities and Aggregates from our previous DDD lesson reside. These objects encapsulate enterprise-wide or application-wide business rules and are the least likely to change when external factors like the database or web framework change.

  2. Use Cases: This layer contains application-specific business rules. It orchestrates the flow of data to and from the Entities. A use case (sometimes called an Interactor) implements a specific user story or system operation, like "Place an Order" or "Register a User." It coordinates the aggregates to fulfill the required task.

  3. Interface Adapters: This layer acts as a set of converters. It transforms data from a format convenient for the Use Cases and Entities to a format convenient for external systems (like a database or a UI), and vice versa. This is where you'll find:

    • Repositories: Implementations of the repository interfaces defined in the inner layers.
    • Presenters/Views/Controllers: In an MVC pattern, these components belong here.
    • Gateways: Adapters to other external services.
  4. Frameworks & Drivers (Outer-most): This layer is composed of the external tools and technologies. The web framework, the database, the message broker—these are all details that live in the outermost circle. You write minimal code here, mostly "glue code" to connect to the next layer in.

The Dependency Rule

This is the cornerstone of Clean Architecture: Source code dependencies can only point inwards.

  • Code in an inner circle cannot know anything about code in an outer circle.
  • This means a Use Case cannot have a using or import statement that refers to a Controller. An Entity cannot know what database is being used.
  • Data passed across boundaries should be simple data structures (DTOs). You should not pass database-specific objects or framework-dependent models into the inner layers.

This rule is enforced using the Dependency Inversion Principle (DIP). Inner layers define interfaces (e.g., IOrderRepository), and outer layers provide the concrete implementations (e.g., PostgresOrderRepository). The inner layers depend on the abstraction, not the concrete detail, thus inverting the dependency flow away from the direction of control.

2. A Practical Implementation Walkthrough

Theory is essential, but seeing how these layers translate into actual code and project structure makes the concepts concrete. Your experience with large-scale Java systems will make the patterns shown in this .NET-based example immediately recognizable.

The following video provides an excellent, step-by-step guide to building a service using Clean Architecture.

Clean Architecture | A Practical ASP.NET Core Implementation

This video, 'Clean Architecture | A Practical ASP.NET Core Implementation' by Julio Casal, demonstrates how to structure a project according to Clean Architecture principles. It clearly shows the separation of concerns and the dependency flow.

Please watch the following segments. The project structure and dependency management concepts are directly transferable to a Java/Maven/Gradle environment. Introduction & Dependency Rule (00:00 - 00:54): A quick recap of the core rule. The Core Project (Entities & Use Cases) (00:54 - 07:47): See how the Entities and Use Cases layers are combined into a single Core project. Notice that use cases depend only on interfaces for persistence (IGameMatchRepository) and messaging (IBus). The Contracts Project (07:36 - 09:00): A practical addition for defining DTOs and messages shared with external clients. Testing the Core (08:51 - 11:35): This is a key benefit. Observe how the core business logic is tested in complete isolation from any infrastructure. The Infrastructure Project (11:35 - 15:31): This project implements the interfaces from Core. This is where dependencies on MongoDB and RabbitMQ are introduced. The API Project & Composition Root (16:57 - 20:42): This is the entry point. It contains the web endpoints and, crucially, the Composition Root (Program.cs) where all the dependencies are wired together using dependency injection. End-to-End Flow (20:32 - 26:31): The debugging session clearly visualizes the flow of a request through the layers.

This practical example highlights a common and effective way to structure your service:

  • YourService.Core (or Domain + Application): Contains your Entities, Aggregates, Value Objects, Use Cases, and the interfaces for any external dependencies (e.g., repositories, event buses). This project has zero dependencies on infrastructure.
  • YourService.Infrastructure: Contains the concrete implementations of the interfaces defined in Core. It references database drivers, SDKs for external services, etc. It depends on Core.
  • YourService.Api (or Web): The executable project. It contains controllers/endpoints and the composition root. It depends on both Core and Infrastructure to wire everything together.

This structure strictly enforces the Dependency Rule and ensures your core business logic remains pure and isolated.

3. A Deeper Look at the Infrastructure Layer

The "Infrastructure" layer can become a catch-all for many different concerns. In complex systems, it's often beneficial to further subdivide it.

Clean Architecture: How to Build The Infrastructure Layer

Let's watch a few clips from 'Clean Architecture: How to Build The Infrastructure Layer' by Milan Jovanović. This video provides a more granular view of the responsibilities that live in the infrastructure layer.

Watch these segments to see how a real-world infrastructure layer is organized: Persistence (01:32 - 04:05): Shows how database-specific concerns like the DbContext (similar to a JPA EntityManager), repository implementations, and migrations are isolated here. Splitting Responsibilities (04:05 - 06:56): Discusses splitting Infrastructure from Persistence and shows other infrastructure concerns like authorization policies and background jobs (e.g., for an outbox pattern). Cross-Cutting Concerns (07:41 - 09:21): Covers implementations for cross-cutting concerns like an event bus (IEventBus) or a direct SQL connection factory.

This reinforces a key point: any code that has knowledge of an external system—be it a database, a message queue, an identity provider, or even the file system—belongs in an outer layer, implementing an interface defined by the core. This gives you the flexibility to swap out these details without impacting your business logic. For instance, you could switch from a PostgreSQL-based repository to a MongoDB-based one simply by changing the dependency injection configuration in the composition root.

Conclusion

In this lesson, we've mapped out the structure of a service using Clean Architecture, providing a clean, testable, and maintainable home for the domain models we designed previously.

Key Takeaways:

  • Clean Architecture's goal is the separation of concerns, isolating business logic from external details like frameworks and databases.
  • The Dependency Rule is absolute: all source code dependencies must point inwards, from low-level mechanisms to high-level policies.
  • DDD aggregates and entities live in the Entities layer, the very core of the application.
  • Use Cases orchestrate these aggregates to perform application-specific tasks, depending on interfaces for external operations.
  • The Infrastructure layer provides concrete implementations for these interfaces, containing all knowledge of external systems.
  • The Composition Root (in the outermost layer) is where all layers are wired together using dependency injection. This structure is what makes the architecture pluggable and testable.

Preview of the Next Lesson

With a solid architectural foundation in place, we are now equipped to explore more advanced patterns that leverage this separation of concerns.

In the next lesson, "Design a CQRS-based architecture for a given workload, implementing separate read and write models," we will see how to take this separation a step further. CQRS (Command Query Responsibility Segregation) divides the application into two distinct sides: one for handling commands (writes) and another for handling queries (reads). Clean Architecture provides an excellent starting point for implementing a CQRS-based system effectively.

Can't find a good explanation? Sign up and we'll make it for you

Sign up