A DDD Architecture in Laravel — The 4-Layer Structure and Dependency Inversion
From here, as Part 2, we explain how to implement the DDD concepts you learned in Part 1 with Laravel.
From this chapter on, Laravel-specific features (controllers, Eloquent, service providers, and so on) appear. Their basics are explained in Chapter 2, "Basics of Laravel API development", so refer to it as needed.
A comparison of architectures
The traditional Laravel structure
Business logic is scattered across the controller and the model.
DDD + clean architecture
Clean architecture is a design philosophy proposed by Robert C. Martin (commonly known as "Uncle Bob"). It puts domain logic at the center and aims to make it independent of external technologies such as frameworks and databases.
It is an architectural pattern proposed in Robert C. Martin's book "Clean Architecture." The essential idea is to put business logic (the domain layer) at the center and place the technical details (framework, DB, UI) on the outside. This keeps business rules unaffected by changes in technology and lets you build a system that is maintainable in the long term.
Reference: The Clean Architecture
The 4-layer structure this book adopts (placing the domain at the center) is, strictly speaking, closer to Jeffrey Palermo's Onion Architecture. Robert C. Martin's Clean Architecture is originally expressed as four concentric rings (Entities / Use Cases / Interface Adapters / Frameworks & Drivers). Both share the core idea of "pointing dependencies toward the inside (the domain)." In practice they are often referred to collectively as "clean architecture" without distinction, so this book follows that convention.
The responsibilities of each layer
Each layer has a clearly separated role, and this separation localizes the scope of changes and enables testable code.
| Layer | Responsibility | What it contains | Why it matters |
|---|---|---|---|
| Presentation layer | Handling HTTP requests/responses | Controller, Request, Resource | Resilient to changes in input format. A change in the interface, such as migrating from a REST API to GraphQL, does not affect the domain |
| Application layer | Coordinating use cases | UseCase, Command | Clarifies the business flow. You can take an overall view of the business process and coordinate multiple domain objects |
| Domain layer | Expressing business rules | Entity, Value Object, Repository Interface | Protects business logic. Completely independent of technical details, holding only pure business rules |
| Infrastructure layer | Technical implementation | Repository Implementation, Eloquent Model | Flexibility in technology choices. A change in persistence method, such as migrating from Eloquent to Doctrine ORM, does not affect the domain |
Why this separation matters
-
Limiting the scope of changes
- UI changes do not affect the business logic
- Database changes do not affect the business logic
- Framework changes do not affect the business logic
-
Improved testability
- The domain layer can be tested without external dependencies
- Fast unit tests using mocks
- Testing of business logic that needs no database
-
Easy-to-understand code
- Each layer's responsibility is clear, so you know where to write what
- Business logic is collected in the domain layer
- Even new members can develop without confusion
The direction of dependencies
The dependency inversion principle (DIP)
Important: the infrastructure layer depends on the domain layer (the dependency inversion principle). The domain layer never depends on the infrastructure layer.
It is the "D" of the SOLID principles, an important principle of object-oriented design.
What the principle says:
- High-level modules (the domain layer) must not depend on low-level modules (the infrastructure layer). Both should depend on abstractions (interfaces)
- Abstractions must not depend on details. Details (implementations) should depend on abstractions
In plain terms: It means "depend on interfaces (contracts), not on implementations."
Why does the infrastructure layer depend on the domain layer?
In a normal layered structure, dependencies go "from top to bottom" (Controller → Service → Repository implementation). But clean architecture puts the domain layer at the center, so it "inverts" the infrastructure layer's dependency.
Understanding with a concrete example
// Domain layer: define an interface (the abstraction)
namespace App\Domain\Order;
interface OrderRepositoryInterface
{
public function save(Order $order): void;
public function findById(OrderId $id): ?Order;
}
// Infrastructure layer: implement the interface (the detail)
namespace App\Infrastructure\Repository;
use App\Domain\Order\OrderRepositoryInterface; // ← depends on the domain layer
final class EloquentOrderRepository implements OrderRepositoryInterface
{
public function save(Order $order): void
{
// An implementation using Eloquent
}
}
// Domain layer: the entity
namespace App\Domain\Order;
// The domain layer knows only the "interface"
// It does not know the existence of Eloquent (it does not depend on it)
final class Order
{
// Business logic
}
The benefits of this design
-
The domain layer is independent of technical details
// Testing the domain layer: testable quickly without a DB$order = Order::create($orderId, $address);$order->addItem(...);// Test only the business logic -
Easy to swap implementations
// Even if you change from Eloquent to another ORM, the domain layer is unchangedfinal class DoctrineOrderRepository implements OrderRepositoryInterface{// An implementation using Doctrine ORM} -
The flow of dependencies The traditional design (bad example) — the business logic strongly depends on Eloquent.
After applying DIP (good example) — the domain is completely independent of the infrastructure.
The directory layout
app/
├── Domain/ # Domain layer
│ ├── Order/
│ │ ├── Order.php # Aggregate root (entity)
│ │ ├── OrderId.php # Identifier (value object)
│ │ ├── OrderLine.php # Entity
│ │ ├── OrderLineId.php # Identifier (value object)
│ │ ├── OrderStatus.php # Enum
│ │ ├── ProductId.php # Identifier (value object)
│ │ ├── ShippingAddress.php # Value object
│ │ └── OrderRepositoryInterface.php # Repository interface
│ │
│ └── Shared/
│ └── Money.php # Shared value object
│
├── Application/ # Application layer
│ └── UseCase/
│ └── Order/
│ ├── CreateOrderUseCase.php
│ ├── CreateOrderCommand.php
│ └── ConfirmOrderUseCase.php
│
├── Infrastructure/ # Infrastructure layer
│ ├── Eloquent/
│ │ ├── OrderModel.php # Eloquent Model
│ │ └── OrderLineModel.php # Eloquent Model
│ ├── Repository/
│ │ └── EloquentOrderRepository.php
│ └── Provider/
│ └── RepositoryServiceProvider.php
│
└── Http/ # Presentation layer
├── Controllers/
│ └── OrderController.php
└── Requests/
└── CreateOrderRequest.php
The knowledge and responsibilities of each layer
| Layer | What it knows | What it does not know |
|---|---|---|
| Controller | The HTTP request/response format | Business rules, table structure |
| UseCase | How to use domain objects | The HTTP request format, table structure |
| Domain Entity | Business rules | The persistence method, the framework |
| Repository | The table structure, Eloquent | The HTTP request format |
The difference between an Eloquent model and a domain entity
| Characteristic | Domain Entity | Eloquent Model |
|---|---|---|
| Responsibility | Expressing business rules | Mapping to the database |
| Business rules | Holds them | Does not hold them |
| DB operations | Does not hold them | Holds them (save, find, etc.) |
| Testability | High (testable without a DB) | Low (needs a DB) |
Why separate them?
- Separation of concerns: separate business logic from persistence logic
- Testability: the domain entity can be tested without a DB
- Flexibility: changing the persistence method does not affect the domain
- Clarifying business rules: business rules are collected in the domain entity
Further reading
If you want to learn more about clean architecture and DDD, see the following resources.
Clean architecture
- Robert C. Martin, "Clean Architecture" (book)
- The original text of clean architecture. You can comprehensively learn the principles of architectural design.
- The Clean Architecture (article)
- Uncle Bob's article explaining clean architecture
The SOLID principles
- The dependency inversion principle (DIP) is the "D" of the SOLID principles
- Understanding the SOLID principles is essential for understanding clean architecture
In the next chapter, we look in detail at the design of the application layer (the use case layer).