SYS://VISION.ACTIVE
VIEWPORT.01
LAT 28.0222° N
SIGNAL.NOMINAL
VISION Loading
Back to Blog

Domain-Driven Design in Laravel: Strategic and Tactical Patterns

Shane Barron

Shane Barron

Laravel Developer & AI Integration Specialist

What is Domain-Driven Design?

DDD is an approach to software development that prioritizes understanding and modeling the business domain. It's not about technology—it's about creating software that reflects how the business actually works.

Strategic Design: Bounded Contexts

Large systems should be divided into bounded contexts, each with its own model and language.

// E-commerce might have:
// - Catalog Context: Products, Categories, Pricing
// - Orders Context: Orders, LineItems, Checkout
// - Inventory Context: Stock, Warehouses, Reservations
// - Shipping Context: Shipments, Carriers, Tracking

// Each context has its own "Product" concept
namespace App\Catalog\Domain;
class Product { /* rich catalog data */ }

namespace App\Inventory\Domain;
class Product { /* stock-focused data */ }

Tactical Patterns

Entities

class Order
{
    private OrderId $id;
    private array $lineItems = [];
    private OrderStatus $status;

    public function addItem(Product $product, int $quantity): void
    {
        $this->guardAgainstModificationWhenPlaced();
        $this->lineItems[] = new LineItem($product, $quantity);
        $this->recalculateTotal();
    }

    private function guardAgainstModificationWhenPlaced(): void
    {
        if ($this->status !== OrderStatus::Draft) {
            throw new OrderCannotBeModifiedException();
        }
    }
}

Value Objects

final class Money
{
    public function __construct(
        public readonly int $amount,
        public readonly string $currency
    ) {
        if ($amount < 0) {
            throw new InvalidArgumentException('Amount cannot be negative');
        }
    }

    public function add(Money $other): self
    {
        if ($this->currency !== $other->currency) {
            throw new CurrencyMismatchException();
        }

        return new self($this->amount + $other->amount, $this->currency);
    }

    public function equals(Money $other): bool
    {
        return $this->amount === $other->amount
            && $this->currency === $other->currency;
    }
}

Aggregates

// Order is an aggregate root
// LineItems are only accessed through Order
class Order // Aggregate Root
{
    private array $lineItems; // Part of aggregate

    public function getLineItems(): array
    {
        return $this->lineItems; // Return copies, not references
    }

    // All modifications go through the root
    public function updateItemQuantity(LineItemId $id, int $quantity): void
    {
        $item = $this->findItem($id);
        $item->updateQuantity($quantity);
        $this->recalculateTotal();
    }
}

Domain Events

class OrderPlaced implements DomainEvent
{
    public function __construct(
        public readonly OrderId $orderId,
        public readonly CustomerId $customerId,
        public readonly Money $total,
        public readonly DateTimeImmutable $occurredAt
    ) {}
}

class Order
{
    private array $domainEvents = [];

    public function place(): void
    {
        $this->status = OrderStatus::Placed;
        $this->domainEvents[] = new OrderPlaced(
            $this->id,
            $this->customerId,
            $this->total,
            new DateTimeImmutable()
        );
    }

    public function pullDomainEvents(): array
    {
        $events = $this->domainEvents;
        $this->domainEvents = [];
        return $events;
    }
}

Repositories

interface OrderRepository
{
    public function findById(OrderId $id): ?Order;
    public function save(Order $order): void;
    public function nextIdentity(): OrderId;
}

class EloquentOrderRepository implements OrderRepository
{
    public function save(Order $order): void
    {
        DB::transaction(function () use ($order) {
            $model = OrderModel::findOrNew($order->id()->value());
            $model->fill($this->toArray($order));
            $model->save();

            // Dispatch domain events
            foreach ($order->pullDomainEvents() as $event) {
                event($event);
            }
        });
    }
}

Conclusion

DDD provides tools for modeling complex domains. Use bounded contexts to manage complexity, value objects for immutable concepts, aggregates for consistency boundaries, and domain events for decoupled communication.

Share this article
Shane Barron

Shane Barron

Strategic Technology Architect with 40 years of experience building production systems. Specializing in Laravel, AI integration, and enterprise architecture.

Need Help With Your Project?

I respond to all inquiries within 24 hours. Let's discuss how I can help build your production-ready system.

Get In Touch