PHP - Implementing Domain-Driven Design (DDD) in PHP
Domain-Driven Design (DDD) is a software development approach that focuses on building applications around the core business domain and its rules. Instead of concentrating only on database tables or technical structures, DDD emphasizes understanding the business process, terminology, and real-world workflows before writing code. In PHP applications, especially large enterprise systems, DDD helps developers organize complex business logic into structured and maintainable components.
DDD was introduced by Eric Evans and is widely used in systems where business requirements are complicated and continuously evolving. Examples include banking systems, e-commerce platforms, hospital management software, logistics applications, and large SaaS products. The main goal is to ensure that developers and business experts speak the same language and that the code directly reflects business operations.
Core Idea of Domain-Driven Design
In traditional PHP applications, business logic often becomes scattered across controllers, models, and database queries. Over time, this creates tightly coupled code that is difficult to maintain. DDD solves this problem by placing business logic inside the domain layer.
The domain layer contains:
-
Business rules
-
Validation logic
-
Core workflows
-
Domain-specific operations
-
Entities and value objects
This separation keeps the application organized and prevents business rules from being mixed with infrastructure code like databases or APIs.
Main Building Blocks of DDD
1. Domain
The domain represents the business problem the application is trying to solve.
For example:
-
In an e-commerce application, the domain includes products, orders, customers, payments, and shipping.
-
In a banking application, the domain includes accounts, transactions, loans, and interest calculations.
The domain is considered the heart of the application.
2. Ubiquitous Language
Ubiquitous language means developers and business stakeholders use the same terminology throughout discussions and code.
For example, if the business team uses the term “Invoice,” developers should also use the class name Invoice instead of something unrelated like BillingRecord.
This improves communication and reduces misunderstandings.
Example:
class Invoice
{
private float $amount;
public function __construct(float $amount)
{
$this->amount = $amount;
}
}
The code reflects the exact business concept.
3. Entities
Entities are objects that have a unique identity and can change over time.
Example:
-
Customer
-
Order
-
Product
-
Employee
Even if some properties change, the entity remains the same because its identity is preserved.
Example:
class Customer
{
private int $id;
private string $name;
public function __construct(int $id, string $name)
{
$this->id = $id;
$this->name = $name;
}
}
The customer ID uniquely identifies the entity.
4. Value Objects
Value objects do not have identity. They are defined only by their values.
Examples:
-
Address
-
Money
-
Email
-
Date Range
If two value objects contain the same data, they are considered equal.
Example:
class Money
{
private float $amount;
private string $currency;
public function __construct(float $amount, string $currency)
{
$this->amount = $amount;
$this->currency = $currency;
}
}
Value objects are usually immutable, meaning their values cannot change after creation.
5. Aggregates
An aggregate is a group of related entities and value objects treated as a single unit.
For example:
-
An Order aggregate may contain:
-
Order entity
-
Order items
-
Shipping details
-
Payment information
-
The aggregate root controls access to all internal objects.
Example:
class Order
{
private array $items = [];
public function addItem(OrderItem $item)
{
$this->items[] = $item;
}
}
This ensures consistency within the aggregate.
6. Repositories
Repositories provide a way to retrieve and store domain objects without exposing database details.
Instead of writing SQL queries directly inside business logic, repositories handle persistence.
Example:
interface CustomerRepository
{
public function findById(int $id): ?Customer;
public function save(Customer $customer): void;
}
Advantages:
-
Cleaner code
-
Easier testing
-
Better abstraction
-
Database independence
7. Services
Some business operations do not naturally belong to a single entity. In such cases, domain services are used.
Example:
class PaymentService
{
public function process(Order $order)
{
// payment logic
}
}
Services handle domain operations involving multiple entities.
8. Factories
Factories simplify the creation of complex domain objects.
Example:
class OrderFactory
{
public static function create(Customer $customer): Order
{
return new Order($customer);
}
}
Factories help maintain consistency during object creation.
Layered Architecture in DDD
DDD applications are commonly divided into layers.
Presentation Layer
Handles user interaction.
Examples:
-
Controllers
-
API endpoints
-
Views
Application Layer
Coordinates tasks and workflows.
Responsibilities:
-
Managing use cases
-
Calling domain services
-
Handling transactions
Domain Layer
Contains the core business logic.
Includes:
-
Entities
-
Value objects
-
Aggregates
-
Domain services
-
Repositories interfaces
Infrastructure Layer
Handles technical details.
Examples:
-
Database access
-
Email sending
-
External APIs
-
File storage
This separation keeps the business logic independent from technical implementation.
Example Project Structure in PHP
src/
│
├── Domain/
│ ├── Entity/
│ ├── ValueObject/
│ ├── Repository/
│ └── Service/
│
├── Application/
│
├── Infrastructure/
│
└── Presentation/
This structure improves scalability and maintainability.
DDD with Laravel
Laravel can be adapted for DDD even though it follows MVC by default.
Developers often create separate domain folders:
app/
├── Domain/
├── Application/
├── Infrastructure/
└── Http/
Benefits in Laravel:
-
Better separation of concerns
-
Easier testing
-
Scalable architecture
-
Cleaner business logic
Many enterprise Laravel projects use DDD principles for large systems.
Advantages of DDD in PHP
Better Code Organization
Business logic remains structured and centralized.
Improved Maintainability
Changes become easier because domain rules are isolated.
Scalability
Large projects become manageable through modular design.
Easier Communication
Business experts and developers share the same terminology.
Higher Testability
Domain logic can be tested independently.
Challenges of DDD
Complexity
DDD introduces many layers and patterns, which may feel overwhelming for beginners.
Longer Initial Development Time
Planning the domain model requires deep business analysis.
Not Ideal for Small Projects
Simple applications may not need the full DDD structure.
When to Use DDD in PHP
DDD is suitable when:
-
The application contains complex business logic
-
Multiple teams work on the same project
-
The project is long-term and scalable
-
Business rules frequently evolve
-
Maintainability is critical
It may not be necessary for small CRUD-based applications with simple workflows.
Real-World Example
Consider an online shopping platform.
Without DDD:
-
Business logic gets scattered in controllers and models.
With DDD:
-
Ordermanages order rules -
PaymentServicehandles payments -
InventoryServicemanages stock -
Moneyacts as a value object -
OrderRepositoryhandles persistence
This creates a clean, maintainable architecture.
Conclusion
Domain-Driven Design in PHP is a powerful architectural approach for developing complex and scalable applications. By focusing on the business domain and organizing code around real-world concepts, DDD improves maintainability, readability, and collaboration between technical and non-technical teams.
Although DDD adds complexity and requires careful planning, it becomes extremely valuable for enterprise-level applications where business rules are large and continuously evolving. Using entities, value objects, repositories, services, and layered architecture allows PHP developers to build systems that are flexible, testable, and easier to scale over time.