1

I'm stuck with a specific scenario regarding Aggregates and not breaking business invariants.

I've two entities, let's call them Order and OrderItem. Both entities belong to an aggregate with Order as Aggregate root.

When I need to update one specific OrderItem I do it through the Aggregate root:

class Order
{
    private Collection $orderItems;

    public function __construct()
    {
        $this->orderItems = new Collection;
    }

    public function updateOrderItemPrice(OrderItemId $id, int $newPrice): void
    {
        foreach ($this->orderItems as $orderItem) {
            if ($orderItem->id()->equals($id) {
                $orderItem->updatePrice($newPrice);
                break;
            }
        }  
    }
}

While this solution works fine for small collections, it can lead to a huge performance penalty when we're talking about thousands (or tens of thousands) of records due the ORM (Doctrine in my case) will try to load all the order items in memory when you're about to iterate it.

Is there any pattern or good practice to solve this specific scenario?

Alex29
  • 1,211
  • 2
  • 10
  • 20
  • Do you mean a single order can have tens of thousands of OrderItems? – Francesc Castells Aug 24 '22 at 14:11
  • Yes. Order and OrderItem are just examples to make the problem more approachable. I want to know how to handle situations where you have then of thousands of related entities. – Alex29 Aug 24 '22 at 15:30
  • I have never encountered an aggregate like this. In most cases, I'd say there is something wrong with the aggregate design which causes this situation. But if the design is correct, then you don't have many options. Either load all related entities and deal with the memory issue or validate the invariants in the DB directly. – Francesc Castells Aug 24 '22 at 21:39
  • Not sure if tags as domain-driven-design are correct for this question, as it is more an implementation details than a design one. The easier thing that came to my mind is create an interface in the domain layer exposing this operation, implement it using some low level API that gives you more performance – rascio Aug 27 '22 at 12:32

1 Answers1

0
class OrderItemId {
    public function __construct(private readonly int $value)
    {
    }

    public function getValue(): int
    {
        return $this->value;
    }
}

class OrderItem {
    
    public function __construct(private readonly OrderItemId $id)
    {
    }

    public function getId(): OrderItemId
    {
        return $this->id;
    }
}

class OrderItemCollection {
    private array $items = [];
    
    private function add(OrderItem $item): void {
        $this->items[$item->getId()->getValue()] = $item;
    }
    
    public function has(OrderItemId $id): bool
    {
        return array_key_exists($id->getValue(),$this->items);
    }
    
    public function get(OrderItemId $id): OrderItem
    {
        return $this->items[$id->getValue()];
    }
}

class Order
{
    private OrderItemCollection $orderItems;

    public function __construct()
    {
        $this->orderItems = new OrderItemCollection;
    }

    public function updateOrderItemPrice(OrderItemId $id, int $newPrice): void
    {
        if($this->orderItems->has($id))
        {
            $this->orderItems->get($id)->updatePrice($newPrice);
        }
    }
}
Alex Khonko
  • 121
  • 7
  • I can't perform direct selects. I'm working with Entities in the Domain layer here. Doctrine is in charge of retrieving the collection and I can't specify chunks. This doesn't even solve the time problem where I have to iterate through all items looking for the correct one. – Alex29 Aug 23 '22 at 18:36