DDD: Составление агрегата для финансовой системы

Итак, контекст задачи - финансовая система. Пользователь имеет 4 счёта: Основной (Primary); Холдинговый (Holding); Гаранийный (Guarantee) и Страховой (Escrow). Об этом ему знать не обязательно. Для внешнего мира есть основной счёт и гарантийный депозит. Два оставшихся счёта нужны для обеспечения целостности системы, но это в контексте вопроса не важно.

Важно следующее. Имеется контекст FundsManagement с командой ShowMyFunds. Ответ содержит такое тело:

{
    "balance": "1504.00",
    "reserve": "0.00",
    "status": "ACTIVE"
}

С точки зрения пользователя счёт всего один и мне не хотелось бы чтоб внешний мир знал что у меня творится под капотом. Поэтому ответ такой немногословный. При этом бэкофис знает что там множество счетов - он с ними работает.

Чтоб подвести к такому ответу нужно правильным образом организовать контекст, но тут у меня случился брейнлаг. Уже который день я мечусь между несколькими вариантами исполнения и никак не могу выбрать верное решение. Всё упирается в агрегат, который я до конца не понимаю. Не понимаю что даже поставить в его корень. К примеру, им может быть вот такой Account:

final class Account extends AggregateRoot
{
    public Money $balance;
    public Money $reserve;
    public Status $status;

    public function __construct(...) {...}
}

Дело в том, что этот счёт будет собираться из нескольких других счетов. Бизнесовая сущность не имеет свойства Money $reserve. Её приходится брать из другого счёта. Этот агрегат состоит как бы из двух счетов, а логика сборки уходит в инфраструктуру. Логика не простая. К примеру, статус и баланс берутся из PRIMARY счёта, а резерв из HOLDING. Правильно ли это? Я не уверен.

Другой вариант может выглядеть так

final class Account extends AggregateRoot
{
    public Money $balance;
    public Type $type;
    public Status $status;

    public function __construct(...) {...}
}

В таком случае мне нужно доставать сразу несколько агрегатов по типу и собирать ответ на уровне приложения. К примеру, в классе ShowMyFundsCommandResult. Правильно ли это? Будто бы да, но тогда контекст лишается возможности перевода между счетами т.к. по канонам DDD в рамках одного действия допустимо изменять только один аггрегат. В случае перевода меняются два, что этот принцип нарушает.

Третий вариант на данный момент реализован в коде и в качестве корня имеет сущность самого пользователя. Вроде бы вся логика упакована как нужно, но меня терзают смутные сомнения. Нужен кто-то кто хорошо понимает что такое агрегат и кто может дать мне совет.

Заранее благодарен.

final class User extends AggregateRoot
{
    public array $accounts;

    public function __construct(Identifier $id, array $accounts)
    {
        parent::__construct($id);

        foreach ($accounts as $account) $this->accounts[
            $account->type->name] = $account;
    }

    private function getAccount(Type $type): Account
    {
        $resultOrNull = $this->accounts[$type->name] ?? null;

        if (!$resultOrNull) throw new ActionProhibitedError();

        return $resultOrNull;
    }

    public function getActualBalance(): Money
    {
        return $this->getAccount(Type::PRIMARY)->balance;
    }

    public function getReserveBalance(): Money
    {
        return $this->getAccount(Type::HOLDING)->balance;
    }

    public function getActualStatus(): Status
    {
        return $this->getAccount(Type::PRIMARY)->status;
    }
}

Сущность аккаунта

final class Account extends AbstractEntity
{
    public Money $balance;
    public Type $type;
    public Status $status;
    public \DateTime $updatedAt;

    public function __construct(...) {...}
}

И уровень приложения

readonly class ShowMyFundsCommandResult extends AbstractCommandResult
{
    public string $balance;
    public string $reserve;
    public string $status;

    public function __construct(User $user)
    {
        $this->balance = $user->getActualBalance()->formatToMajorStyle();
        $this->reserve = $user->getReserveBalance()->formatToMajorStyle();
        $this->status = $user->getActualStatus()->name;
    }
}

Ответы (0 шт):