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:

  • Order manages order rules

  • PaymentService handles payments

  • InventoryService manages stock

  • Money acts as a value object

  • OrderRepository handles 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.