PHP - Domain-Driven Design (DDD) Implementation in PHP
Introduction
As software applications grow in size and complexity, organizing code becomes increasingly important. Traditional development approaches often lead to tightly coupled code where business rules are scattered across controllers, models, and database queries. This makes the application difficult to understand, modify, and maintain. Domain-Driven Design (DDD) is a software development approach that focuses on organizing an application around the core business domain rather than technical components.
In PHP, Domain-Driven Design helps developers create applications that accurately represent business processes while keeping the code modular, reusable, and maintainable. Instead of treating the database as the center of the application, DDD places business logic at the heart of the system.
What is Domain-Driven Design?
Domain-Driven Design is a methodology introduced by Eric Evans in his book Domain-Driven Design: Tackling Complexity in the Heart of Software. The main idea is to model software based on real-world business concepts.
The "domain" refers to the specific area of business that the application solves. For example:
-
Banking systems
-
Hospital management
-
E-commerce platforms
-
Hotel booking systems
-
Educational portals
DDD encourages developers and business experts to work together so that the software reflects actual business requirements.
Why Use Domain-Driven Design?
Domain-Driven Design provides several advantages for modern PHP applications.
Better Organization
Business logic is separated from presentation and database code.
Easier Maintenance
Since responsibilities are clearly defined, modifying one part of the system rarely affects others.
Improved Collaboration
Developers and business experts use the same terminology, reducing misunderstandings.
Greater Flexibility
The application becomes easier to extend when business requirements change.
Better Testing
Business rules can be tested independently without depending on databases or user interfaces.
Core Building Blocks of DDD
Domain
The domain contains all business rules and logic.
Example:
In an online shopping application, the domain includes:
-
Product
-
Customer
-
Order
-
Payment
-
Shipping
Each object represents an important business concept.
Entity
An Entity is an object that has a unique identity and changes over time.
Example:
class Customer
{
private int $id;
private string $name;
public function __construct($id, $name)
{
$this->id = $id;
$this->name = $name;
}
public function getId()
{
return $this->id;
}
}
Even if a customer's name changes, the customer remains the same because the ID never changes.
Value Object
A Value Object has no unique identity. It is defined entirely by its values.
Example:
class Address
{
public function __construct(
public string $city,
public string $state,
public string $country
) {}
}
If two Address objects contain identical values, they are considered equal.
Aggregate
An Aggregate is a group of related objects treated as a single unit.
Example:
Order Aggregate
Order
├── OrderItem
├── Payment
└── ShippingAddress
The Order acts as the Aggregate Root, controlling access to its related objects.
Repository
Repositories provide a way to retrieve and store domain objects without exposing database details.
Example Interface
interface ProductRepository
{
public function findById(int $id);
public function save(Product $product);
}
Repository Implementation
class MysqlProductRepository implements ProductRepository
{
public function findById(int $id)
{
// Database query
}
public function save(Product $product)
{
// Save to database
}
}
The business layer interacts only with the repository, not directly with SQL.
Service
Sometimes business logic does not naturally belong to a single entity.
Example
Money Transfer
A transfer involves:
-
Sender
-
Receiver
-
Transaction
Instead of placing this logic inside one entity, create a service.
class TransferService
{
public function transfer(Account $from, Account $to, float $amount)
{
// Transfer logic
}
}
Factory
Factories simplify the creation of complex objects.
Example
class OrderFactory
{
public function create(Customer $customer)
{
return new Order($customer);
}
}
Instead of creating objects manually throughout the application, a factory centralizes the creation process.
Domain Events
A Domain Event represents something important that has happened in the business.
Examples
-
OrderPlaced
-
PaymentCompleted
-
ProductReturned
-
InvoiceGenerated
Example
class OrderPlaced
{
public function __construct(
public Order $order
) {}
}
Other parts of the application can listen for this event and perform additional tasks.
Bounded Context
Large applications often contain multiple business areas.
Example
An e-commerce platform may have:
-
Inventory
-
Sales
-
Shipping
-
Customer Support
-
Billing
Each section has its own business rules and models.
DDD recommends separating them into individual bounded contexts.
Example Folder Structure
src/
Sales/
Inventory/
Billing/
Shipping/
Customer/
Each module evolves independently.
Ubiquitous Language
Developers and business experts should use the same vocabulary.
Example
Instead of:
Table_Order
Use:
PurchaseOrder
Instead of:
UserTable
Use:
Customer
Consistent terminology reduces confusion during development.
Typical DDD Folder Structure in PHP
src/
Domain/
Entities/
ValueObjects/
Repositories/
Services/
Events/
Application/
Commands/
Queries/
Services/
Infrastructure/
Database/
Repository/
Cache/
Presentation/
Controllers/
Views/
Each layer has a clearly defined responsibility.
Example: Online Bookstore
Suppose customers purchase books online.
Domain Objects
Book
Customer
Order
OrderItem
Payment
Invoice
Business Rule
A customer cannot place an order if the cart is empty.
Instead of checking this rule in a controller, the Order entity enforces it.
class Order
{
private array $items = [];
public function placeOrder()
{
if (count($this->items) == 0)
{
throw new Exception("Cart cannot be empty.");
}
echo "Order placed successfully.";
}
}
The business rule remains protected regardless of how the order is created.
Advantages of DDD in PHP
-
Organizes code around business requirements.
-
Makes applications easier to maintain and extend.
-
Reduces duplication of business logic.
-
Encourages reusable and modular components.
-
Simplifies unit testing.
-
Supports large-scale enterprise applications.
-
Improves collaboration between developers and domain experts.
-
Makes business rules explicit and centralized.
-
Enhances scalability as applications grow.
-
Facilitates long-term evolution of complex software systems.
Challenges of DDD
-
Requires a thorough understanding of the business domain.
-
Introduces additional layers and classes, increasing the initial complexity.
-
May be unnecessary for very small or simple projects.
-
Demands disciplined coding practices and clear team communication.
-
Can take more time to design before development begins.
Best Practices for Implementing DDD in PHP
-
Focus first on understanding the business domain before writing code.
-
Keep business logic inside domain entities and services rather than controllers.
-
Use repositories to abstract database operations.
-
Create value objects for concepts that are defined by their values rather than identity.
-
Organize the project into bounded contexts for large applications.
-
Use domain events to decouple related business processes.
-
Maintain a ubiquitous language shared by developers and business stakeholders.
-
Keep the domain layer independent of frameworks and infrastructure.
-
Write unit tests for domain logic to ensure business rules remain correct.
-
Refactor the domain model as business requirements evolve.
Conclusion
Domain-Driven Design is a powerful architectural approach for building complex PHP applications that closely reflect real-world business processes. By emphasizing the domain, encapsulating business rules within domain models, and separating concerns through entities, value objects, repositories, services, and bounded contexts, DDD produces software that is easier to understand, maintain, and expand. Although it introduces additional design effort, its long-term benefits make it an excellent choice for enterprise applications and projects with evolving business requirements.