How do I inject behaviour into an aggregate?

Viewed 52

I’m working on an aggregate where certain behaviours can be performed by multiple roles within the application. But before that happens fairly complex validation occurs. It’s this validation that differs per role. Typically it means different configuration settings are checked to determine if the action can be performed.

So, as an example: lets say i have an Order to which i can add OrderLines. If i have role Employee i might be allowed to order up to € 100,- and if i have role Purchaser i might be allowed to order up to € 1000,-.

You could solve this by providing the user instance to the addOrderLine method but that leaks the user context into the ordering context. The next logical thing, and this is what I’ve been doing, is in to inject that validation logic into the method call. I’m calling those methods policies and instantiate the right policy in the application service as i have the relevant user info available there:

<?php
class Order {
    public function addItem(OrderPolicy $policy, Item $item, int $amount) {
        if (!$policy->canPurchase($item->getPrice() * $amount))
            throw new LimitExceededException();

        /* add item here */
}

class OrderService {
    public function addItem(User $user, $orderId, $itemId, int $amount) {
        $order = $this->orderRepo->getForUser($user, $orderId);
        $item = $this->itemRepo->get($itemId);
        $policy = $this->getOrderPolicyFor($user);

        $order->addItem($policy, $item, $amount);
    }
}

class PurchaserOrderPolicy
{
    function canPurchase($amount) {
        return ($amount <= 1000);
    }
}

This seems fine, but now it seems to me my ordering context has logic based on user roles (the policy classes) its not supposed to know about.

Does DDD offer any other ways of dealing with this? Maybe Specification? Does this seem fine? Where do the policy classes belong?

1 Answers

It seems that you have two subdomains/systems involved here: ordering system and buying policies system. You need to keep thing separated, your gut was correct. This means that the validation about the maximum order value is checked in an eventual consistent manner relative to the actual item adding. You could do this before (you try to prevent an invalid order) or after (you try to recover from an invalid order).

If you do it before then the application service could orchestrate this and reject the order.

If you do it after then you could implement this as a process and have a ApplyPoliciesToOrdersSaga that listen to the ItemWasAddedToTheOrder event (if you have an event-driven architecture) or that runs as a scheduled task/cron job and checks all orders against the policies (in a classical architecture).

Related