DDD - Совокупный корень с большим количеством детей

10

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

Я работаю над проектом, который включает в себя концепции счетов и транзакций (в финансовом смысле). Счет может иметь много транзакций против него.

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

Однако, когда я пришел применить это в коде, я сразу же столкнулся с проблемой. Во многих ситуациях для меня не особенно полезно постоянно иметь список каждой транзакции в учетной записи. Я заинтересован в том, чтобы иметь возможность делать такие вещи, как расчет баланса на Счете и применение инвариантов, таких как кредитный лимит, но я также хочу иметь возможность легко работать с подмножеством Транзакций (например, отображать те, которые попадают в диапазон дат).

В последнем случае, если бы я использовал a, TransactionRepositoryя мог бы эффективно получить доступ только к тем нужным объектам, не загружая весь (потенциально очень большой) список. Однако это позволило бы вещам, отличным от Учетной записи, работать с Транзакциями, что означает, что я нарушил концепцию Учетной записи как совокупного корня.

Как люди справляются с такой ситуацией? Принимаете ли вы просто влияние на память и производительность загрузки потенциально огромного числа дочерних элементов для совокупного корня?

krixon
источник

Ответы:

9

Я бы рекомендовал быть осторожным с правилом «не может существовать без» . Это говорит о концепции композиции в дизайне UML / OO и, возможно, было одним из предписанных подходов к проектированию Агрегатов в первоначальной синей книге DDD (не уверен в этом), но с тех пор в значительной степени пересмотрено. Возможно, было бы лучше увидеть ваши агрегаты с точки зрения границы согласованности транзакций .

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

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

guillaume31
источник
Спасибо, я буду читать о согласованности границ. Я думаю, что ваше предложение сделать Transaction своим собственным корнем может быть хорошим; как вы говорите, у меня не так много инвариантов, охватывающих несколько транзакций.
Криксон
7

tl; dr - нарушать правила, если нужно. DDD не может решить все проблемы; на самом деле идеи, которые он дает, - это хороший совет и хорошее начало, но действительно плохой выбор для некоторых бизнес-задач. Считайте, что это подсказка о том, как делать вещи.


По вопросу загрузки всех потомков (транзакции) с родителем (аккаунтом) - похоже, вы столкнулись с проблемой n + 1 (что-то для Google), которую решили многие ORM.

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

Но, похоже, вы уже знаете, что, упомянув, что вы можете использовать TransactionRepository для решения проблемы.

Чтобы «спрятать» эти данные, чтобы их мог использовать только Аккаунт, вам даже не нужно хранить их там, где кто-либо другой не знает, как их десериализовать, как в публичной реляционной таблице. Вы можете хранить его вместе с «документом» счета в БД документов. В любом случае, если кто-то изо всех сил старался, он все равно мог видеть данные. И «работать» с этим. И когда ты не смотришь, они будут!

Таким образом, вы можете настроить разрешения, но затем вам нужно запустить «учетную запись» как отдельный процесс.

Вы действительно понимаете, что DDD и чистое использование объектной модели иногда загоняют вас в угол. Честно говоря, конечно, вам не нужно использовать «состав» / агрегатный корень, чтобы извлечь выгоду из принципов разработки DDD. Это всего лишь одна вещь, которую вы можете использовать, когда у вас есть ситуация, которая вписывается в ее ограничения.

Кто-то может сказать «не рано оптимизировать». Здесь в этом случае, однако, вы знаете ответ - будет достаточно транзакций, чтобы найти метод, который навсегда сохранит их в учетной записи.

Реальный ответ - начать вставать SOA. На моем рабочем месте мы смотрели видео «Распределенные вычисления» Уди Даана и купили nServiceBus (просто наш выбор). Создайте сервис для учетных записей - со своим собственным процессом, очередями сообщений, доступом к базе данных отношений, которую только он может видеть, и ... альтом, вы можете жестко закодировать операторы SQL в программе и даже добавить пару сценариев транзакций Cobol (шучу) конечно), но серьезно относитесь к большему разделению проблем, чем когда-либо мог себе представить самый умный сноб OO / Java.

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

Это имеет недостаток, конечно. Вы не можете просто RPC (веб-сервис, SOAP или REST) ​​входить и выходить из сервисов и между ними, или вы получаете антипаттерн SOA, называемый «узел», из-за временной связи. Вы должны использовать инверсию коммуникационного шаблона, то есть Pub-Sub, который так же, как обработчики событий и средства сбора событий, но (1) между процессами (которые вы можете поместить на отдельные машины, если они перегружаются на одном).

реальная проблема в том, что вы не хотите, чтобы служба, которая должна получать данные от другой службы, «блокировала» или ждала - вы должны запустить и забыть сообщение и позволить обработчику, находящемуся в другом месте вашей программы, забрать его для завершения обработки. Это означает, что вы должны делать свою логику по-другому. nServicebus автоматизирует шаблон 'saga', чтобы помочь с этим, но, в конце концов, вам нужно разработать другой стиль кодирования. Вы все еще можете сделать все это, просто нужно сделать это по-другому!

Книга Арнона Ротем-Гал-Оз "Образцы SOA" дает ответы на многие вопросы. Включая использование «шаблона активного сервиса» для периодической репликации данных из внешних сервисов на ваш собственный при возникновении необходимости (потребовалось бы много RPC, или ссылка ненадежна / отсутствует в экосистеме публикации / подписки).

Только для предварительного просмотра, UIs же должна RPC в сфере услуг. Отчеты генерируются из базы данных отчетов, которая подается базами данных служб. Некоторые люди говорят, что отчеты не нужны и что проблема должна быть решена другим способом. Будьте скептичны к этому разговору.

В конце концов, не все вещи могут быть должным образом классифицированы в одну услугу. Мир не работает на коде равиоли! Так что вам придется нарушать правила. Даже если вам это никогда не понадобится, новые разработчики проекта сделают это, когда вы покинете его. Но не волнуйтесь, если вы сделаете то, что можете, 85%, которые следуют правилам, сделают программу более удобной в обслуживании.

Вау, это было долго.

FastAl
источник
Спасибо за подробный ответ, я определенно прочту кое-что о SOA.
Криксон