DDD: действительно ли нужно загружать все объекты в агрегат? (Проблемы с производительностью)

В DDD репозиторий загружает всю совокупность - мы либо загружаем все это, либо ничего. Это также означает, что следует избегать ленивой загрузки.

Моя забота о производительности. Что, если это приведет к загрузке в память тысяч объектов? Например, совокупность для Customer возвращается с десятью тысячами Orders.

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

Ответ 1

Взгляните на этот Эффективный агрегатный дизайн из трех статей от Вернона. Я нашел их весьма полезными, чтобы понять, когда и как вы можете создавать небольшие агрегаты, а не агрегаты с большим кластером.

ИЗМЕНИТЬ

Я хотел бы привести несколько примеров для улучшения моего предыдущего ответа, не стесняйтесь делиться своими мыслями о них.

Во-первых, быстрое определение агрегата (взято из шаблонов, принципов и практик книги по дизайну домена Скотта Милле)

Объекты и объекты значения взаимодействуют для формирования сложных отношений, которые удовлетворяют инвариантам внутри модели домена. При работе с большими взаимосвязанными ассоциациями объектов часто бывает сложно обеспечить согласованность и concurrency при выполнении действий против объектов домена. Проект, основанный на домене, имеет шаблон Aggregate для обеспечения согласованности и определения границ транзакций concurrency для графиков объектов. Большие модели разделяются инвариантами и группируются в агрегаты объектов и объектов значений, которые рассматриваются как концептуальное целое.

Перейдем к примеру, чтобы увидеть определение на практике.

Простой пример

В первом примере показано, как определение Aggregate Root помогает обеспечить согласованность при выполнении действий с объектами домена.

С учетом следующего бизнес-правила:

Конкурсные торги на аукционе всегда должны быть выставлены до окончания аукциона. Если выигрышная ставка помещается после окончания аукциона, домен находится в недопустимом состоянии, потому что инвариант был поврежден, и модель не смогла правильно применить правила домена.

Здесь есть совокупность, состоящая из Аукциона и Ставок, где Аукцион представляет собой совокупный корень.

Если мы скажем, что Bid также является разделенным корневым агрегированным корнем, у вас будет BidsRepository, и вы можете легко сделать:

var newBid = new Bid(money);
BidsRepository->save(auctionId, newBid);

И вы сохранили заявку без передачи определенного бизнес-правила. Однако, если Аукцион является единственным Агрегатным Корнем, вы применяете свой дизайн, потому что вам нужно сделать что-то вроде:

var newBid = new Bid(money);
auction.placeBid(newBid);
auctionRepository.save(auction);

Поэтому вы можете проверить свой инвариант в методе placeBid, и никто не может пропустить его, если он хочет разместить новую заявку.

Здесь довольно ясно, что состояние Bid зависит от состояния аукциона.

Сложный пример

Вернемся к вашему примеру Приказов, связанных с Клиентом, похоже, что нет инвариантов, которые заставляют нас определять огромный агрегат, состоящий из Клиента и всех ее Ордеров, мы можем просто сохранить связь между обоими объектами через ссылку идентификатора, Делая это, мы не загружаем все Заказы при извлечении Клиента, а также устраняем проблемы concurrency.

Но скажем, что теперь бизнес определяет следующий инвариант:

Мы хотим предоставить клиентам карман, чтобы они могли взимать плату с денег, чтобы покупать товары. Поэтому, если Клиент теперь хочет купить продукт, ему нужно иметь достаточно денег, чтобы сделать это.

Сказанное так, карман - это VO внутри корневого агрегата клиента. Теперь кажется, что наличие двух разделенных Aggregate Roots, один для Customer и другой для Order не лучший для удовлетворения нового инварианта, потому что мы могли бы сохранить новый порядок, не проверив правило. Похоже, мы вынуждены рассматривать Клиента как корень. Это повлияет на нашу производительность, масштабируемость и concurrency проблемы и т.д.

Решение? Возможная последовательность. Что делать, если мы разрешаем клиенту покупать продукт? то есть, имея Агрегированный корень для ордеров, мы создаем порядок и сохраняем его:

var newOrder = new Order(customerId, ...);
orderRepository.save(newOrder);

мы публикуем событие при создании заказа, а затем проверяем асинхронно, если у клиента достаточно средств:

class OrderWasCreatedListener:
    var customer = customerRepository.findOfId(event.customerId);
    var order = orderRepository.findOfId(event.orderId);
    customer.placeOrder(order); //Check business rules
    customerRepository.save(customer);

Если бы все было хорошо, мы выполнили наши инварианты, сохраняя наш дизайн так, как мы хотели вначале модифицировать только один Агрегатный корень за запрос. В противном случае мы отправим электронное письмо клиенту с сообщением о недостаточности выпуска средств. Мы можем продвинуться вперед, добавив альтернативные варианты электронной почты, которые она может купить с ее текущим бюджетом, а также побудить ее зарядить карман.

Учтите, что пользовательский интерфейс может помочь нам избежать того, чтобы клиенты платили без достаточного количества денег, но мы не можем слепо доверять интерфейсу.

Надеемся, что вы найдете оба примера полезными, и дайте мне знать, если вы найдете лучшие решения для открытых сценариев: -)

Ответ 2

В таких случаях, может ли это означать, что мне нужно перепроектировать и пересмотреть мои агрегаты?

Почти наверняка.

Драйвер для агрегатного дизайна - это не структура, а поведение. Нам все равно, что "пользователь имеет тысячи заказов". Мы заботимся о том, какие части состояния нужно проверять при попытке обработать изменение - какие данные вам нужно загрузить, чтобы знать, действительно ли изменение действительно.

Как правило, вы поймете, что изменение порядка не зависит (или не должно) от состояния других заказов в системе, что является хорошим показателем того, что два разных заказа не должны быть частью один и тот же агрегат.