Domain Driven Design Aggregates with Laravel
Tags: php, laravel, webdev, tutorial
Published: 2021-09-20 02:07:18

---
title: Domain Driven Design Aggregates with Laravel
published: true
description: Secure data model using aggregates and value objects, and connect them seamlessly to your business use-cases.
tags: ["php","laravel","webdev","tutorial"]
//cover_image: https://direct_url_to_image.jpg
---

Lately, I've been focusing on finding ways to bring [Laravel][1] and [Domain Driven Design][2] closer together. Because I love 😍 Laravel, but its architecture sucks 🤮.

So today, we're going to look at **how to implement [aggregates][3] using Laravel & [Eloquent][4]**.

Let's get started!

## What's an aggregate?

> A DDD aggregate is a cluster of domain objects that can be treated as a single unit. An example may be an **order** and its **line-items**, these will be separate objects, but it's useful to treat the **order** (together with its **line items**) as a single aggregate.
> — Martin Fowler, [DDD_Aggregate][13]

> Aggregates are not just clusters of data and behavior. The primary reason for the pattern is to protect business invariants for a single aggregate instance in a single transaction.
> — Vaughn Vernon

## What's an invariant?

> Invariants are generally business rules/enforcements/requirements that you impose to maintain the integrity of an object at any given time.
> — Nikesh Shetty, [Designing the DDD way — Introduction][12]

## Example

Let's implement an `OrderAggregate` that embodies the following invariants:

- customers make oders,
- an order consists of a collection of items,
- an order is uniquely identified,
- an order must have a creation timestamp,
- an order must have a shipment address,
- it must be possible to calculate the total of an order, which is the sum of its items,
- all items should have the same currency,

```php
namespace Domain\Model;

use App\Models\Address;
use App\Models\Amount;
use App\Models\Currency;
use App\Models\CustomerId;
use App\Models\LineItem;
use App\Models\Order;

class OrderAggregate
{
    /** @param LineItem[] $lineItems */
    public function __construct(
        private Order $root,
        private CustomerId $customerId,
        private Address $shipmentAddress,
        private Currency $currency,
        private array $lineItems = []
    ) {
        $this->root = $root;
        $this->customerId = $customerId;
        $this->shipmentAddress = $shipmentAddress;
        $this->curreny = $currency;
        $this->lineItems = $lineItems;
    }

    public function getRoot(): Order
    {
        return $this->order;
    }

    public function createdAt(): \DateTime
    {
        return $this->root->created_at;
    }

    public function getCustomerId(): CustomerId
    {
        return $this->customerId;
    }

    public function getShipmentAddress(): Address
    {
        return $this->shipmentAddress;
    }

    public function getTotal(): Amount
    {
        $total = 0;

        foreach ($this->getLineItems() as $item) {
            $total += $item->unit_price->getValue() * $item->quantity;
        }

        return new Amount($total, $this->currency);
    }

    /** @return LineItem[] */
    public function getLineItems(): array
    {
        return $this->lineItems;
    }

    /** @throws \DomainException If $item currency is not compatible with this order */
    public function addLineItem(LineItem $item): void
    {
        if (! $this->curreny->isEqualTo($item->unit_price->currency)) {
            throw new  \DomainException(
                "Unable to add item: invalid currency",
            );
        }

        $this->lineItems[] = $item;
    }
}
```

**Note:** if you need help with value objects and Eloquent I wrote an [article on the topic][14]

## What can we put in an aggregate?

> An aggregate will have one of its component objects be the aggregate root. Any references from outside the aggregate should only go to the aggregate root. The root can thus ensure the integrity of the aggregate as a whole.
> — Martin Fowler

Beyond that, they may contain:

+ Entities
+ Collections, Lists, Sets, etc.
+ [Value objects][6]
+ Value-typed properties (integers, strings, booleans etc.)

You may think of it as a *document* holding **ALL** the data necessary to a given transaction (or use case.)

**Tip:** Eloquent makes it easy to implement lazy-loading in your aggregates. In the above example, we could restructure the `getLineItems` method so that it loads when it's used:

```php
public function getLineItems(): array
{
    return $this->getRoot()->items()->get()->toArray();
}
```

## Can they have commands?

**Yes.** And they should.

You are not supposed to do:

```php
$car->getEngine()->start();
```

But rather:

```php
$car->start();
```

Forcing the exposure of aggregate's internal structure is *bad design* 👎

## How do I persist/retrieve them?

You're going to use the [Repository Pattern][5]:

```php
namespace App\Repositories;

use App\Models\Order;
use App\Models\OrderAggregate;

class OrderRepository
{
    public function find(int $id): OrderAggregate
    {
        $model = Order::with('items')->findOrFail($id);

        return new OrderAggregate(
            $model,
            $model->customer_id,
            $model->shipment_address,
            $model->currency,
            $model->items->toArray()
        );
    }

    public function store(OrderAggregate $order): void
    {
        \DB::transaction(function () use ($order) {
            $order->getRoot()
                ->fill([
                    'customer_id' => $order->getCustomerId(),
                    'shipment_address' => $order->getShipmentAddress(),
                    'currency' => $order->getCurrency(),
                ])
                ->save();

            foreach ($order->getLineItems() as $item) {
                $item->order()->associate($order->getRoot())->save();
            }
        });
    }
}
```

## Rules for making your aggregates pretty 💅

From the awesome [article series by Vaughn Vernon][7] 🤩

**Rule #1: Keep them small.** It is tempting to cram one giant aggregate with anything every use case present and future might need. But it's a terrible design. You're going to run into performances and concurrency issues (when several people are working on the same aggregate at the same time).

It's better to have several representations of order, depending on the broader context, than one. For instance, an order from a cart display page's point-of-view is not the same as from a billing system.

> If we are going to design small aggregates, what does “small” mean? The extreme would be an aggregate with only its globally unique identity and one additional attribute, which is not what's being recommended [...].
> Rather, limit the aggregate to just the root entity and a minimal number of attributes and/or value-typed properties. The correct minimum is the ones necessary, and no more.
> Smaller aggregates not only perform and scale better, they are also biased toward transactional success, meaning that conflicts preventing [an update] are rare. This makes a system more usable.
> — Vaughn Vernon

**Rule #2: Model true invariants in consistency boundaries.** It sounds barbaric, but it's pretty simple; it means that, within a single transaction, there is no way one could *break* the aggregate consistency (its compliance to business rules.)

In other words, it should be impossible to create a bugged version of an aggregate from calling its methods.

One implication of this rule is that a transaction should only commit a *single aggregate*, since it's not possible by design to guarantee the consistency of several aggregates at once.

> A properly designed aggregate is one that can be modified in any way required by the business with its invariants completely consistent within a single transaction.
> And a properly designed bounded context modifies only one aggregate instance per transaction in all cases. What is more, we cannot correctly reason on aggregate design without applying transactional analysis.
> Limiting the modification of one aggregate instance per transaction may sound overly strict. However, it is a rule of thumb and should be the goal in most cases. It addresses the very reason to use aggregates.
> — Vaughn Vernon

**Rule #3: Don't Trust Every Use Case.** Don't blindly assemble your aggregates based on what the use case specification dictates. They may contain elements that contradict the existing model or force you into committing several aggregates in a single transaction or worse, to model a giant aggregate that fits in a single transaction.

Apply your judgment here and keep in mind that sometimes, the business goal can be achieved using [*eventual consistency*][8].

> The team should critically examine the use cases and challenge their assumptions, especially when following them as written would lead to unwieldy designs.
> The team may have to rewrite the use case (or at least re-imagine it if they face an uncooperative business analyst).
> The new use case would specify eventual consistency and the acceptable update delay.
> — Vaughn Vernon

## Conclusion

Murphy's law states:

> Anything that can possibly go wrong, does.

An aggregate is a tactic to mitigates that problem. Within its well-designed boundaries, nothing can go wrong. You can say goodbye to those pesky `ifs` laying around in your code, handling those cases *that are not supposed to happen but happen anyway.*

Don't allow your model to grow beyond your control. Stop using raw data, [POPOs][9], and unguarded Laravel models whose state is uncertain everywhere in your application. Use aggregates instead 👍 and connect your model to the actual business your app is supposed to carry.

---

## Thanks for reading

I hope you enjoyed reading this article! If so, please leave a ❤️ or a 🦄 and [consider subscribing!][11] I write posts on PHP, architecture, and Laravel monthly.

A huge thanks to [Vaughn Vernon][10] for his review and his articles on [DDD][2] 🙏

[1]: https://laravel.com/docs/8.x
[2]: https://martinfowler.com/bliki/DomainDrivenDesign.html
[3]: https://martinfowler.com/bliki/DDD_Aggregate.html
[4]: https://laravel.com/docs/8.x/eloquent
[5]: https://martinfowler.com/eaaCatalog/repository.html
[6]: https://dev.to/bdelespierre/using-value-objects-in-laravel-models-44la
[7]: https://www.dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_1.pdf
[8]: https://en.wikipedia.org/wiki/Eventual_consistency
[9]: https://stackoverflow.com/questions/41188002/what-does-the-term-plain-old-php-object-popo-exactly-mean
[10]: https://vaughnvernon.com/
[11]: https://dev.to/bdelespierre
[12]: https://nikeshshetty.medium.com/designing-the-ddd-way-introduction-9acd910e418
[13]: https://martinfowler.com/bliki/DDD_Aggregate.html
[14]: https://dev.to/bdelespierre/using-value-objects-in-laravel-models-44la