Прогнозы модели чтения CQRS: насколько сложным является слишком сложное преобразование данных - PullRequest
0 голосов
/ 25 апреля 2020

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

Позвольте мне использовать надуманный пример для объяснения .

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

Отборочная отборочная накладная может быть сгенерирована из заказа (или группы заказов) без предоставления какой-либо дополнительной информации из какого-либо внешнего источника или пользователя , Приемлемо ли тогда, что отборочная квитанция может быть представлена ​​исключительно как считанная модель?

Итак:

PlaceOrderCommand -> OrderPlacedEvent
OrderPlacedEvent -> PickingSlipView

Затем менеджер склада может просмотреть отборочную накладную, выбрать линии, которые они хотели бы отправить, а затем выполнить команду PrepareShipment. Событие ShipmentPrepared затем обновит исходный заказ и удалит соответствующие строки из PickingSlipView.

Я знаю, что это игрушечный пример, но у меня есть концептуально похожий случай использования, когда коллега считает, что PickingSlip должен быть доменом. сущность / агрегат сама по себе, так как она концептуально отличается от порядка. Таким образом, у вас есть команды PlaceOrder, GeneratePickingSlip и PrepareShipment.

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

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

Итак (и без углубления в проблемное пространство складов и доставки) ...

Это то, что я предлагаю законный вариант использования для читать модель?

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

Мне кажется, что мой подход проще и я избегаю ненужной сложности, но я новичок в CQRS и, возможно, поэтому что-то пропущено.

Редактировать - альтернативный пример

Предоставить еще один пример для изучения:

У нас есть книга записей для категорий, где каждая запись информация о товарах и их местонахождении. Книга записей заполняется внешней системой и содержит номера SKU, сопоставленные с доступными местоположениями:

Book of Record (Electronics)
SKU#    Location1     Location2    Location3   ...    Location 10
XXXX    Introduce     Remove       Introduce   ...    N/A
YYYY    N/A           Introduce    Introduce   ...    Remove

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

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

Существуют различные типы задач: один план задач предназначен для человека, который находится на месте, чтобы добавить или удалить товар с полок. Назовите это задачей AllocateStock. Задача другого типа существует для регионального супервизора, управляющего несколькими местоположениями, для проверки того, что стеллажи должным образом соответствуют рекомендациям магазина, скажем, задача CheckDisplay. Для размещения акций нас интересуют как введенные, так и удаленные SKU. Для проверки дисплеев нас интересуют только недавно представленные SKU и т. Д. c.

. Мы изучаем два варианта:

Опция 1

Человек, создающий задачи, имеет представление (модель чтения), которое позволяет ему выбрать книгу рекордов. Скажем, они выбирают электронику и моду. Затем они выбирают одно из нескольких мест. Затем они могут отправить команду, например:

GenerateCheckDisplayTasks(TaskPlanId, List<BookOfRecordId>, List<Locations>)

Затем команды будут организовывать прохождение записей, отфильтровывать ненужные нам места, обрабатывать только «введенные» элементы и создавать соответствующие CheckDisplayTasks для каждый SKU в TaskPlan.

Вариант 2

Другими вариантами является смещение фильтрации на модель чтения перед генерацией задач.

Когда добавляется книга записей, поддерживается модель представления для каждого типа задач. Данные могут быть транспонированы и будут включать только соответствующую информацию. ie. CheckDisplayScopeView может проецировать книгу рекордов на:

Category                       SKU     Location
Electronics (BookOfRecordId)   XXXX    Location1
Electronics (BookOfRecordId)   XXXX    Location3
Electronics (BookOfRecordId)   YYYY    Location2
Electronics (BookOfRecordId)   YYYY    Location3
Fashion     (BookOfRecordId)   ...     ... etc

При создании задач представление позволяет пользователю выбрать категорию и места, для которых он хочет создать задачи. Возможно, они выбирают категорию Электроника и Местоположение 1 и 3.

Команда теперь:

GenerateCheckDisplayTasks(TaskPlanId,  List<BookOfRecordId, SKU, Location>)

Где команда теперь больше не отвечает за логи c, необходимые для фильтрации местоположения, Удаленные и Н / Д предметы и c.

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

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

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

Я пытаюсь определить, возлагает ли вариант 2 слишком большую ответственность на модель чтения, или это поведение фильтрации более применимо там.

Извините Я попытался использовать пример PickingSlip, так как думал, что это будет более узнаваемая проблемная область, но теперь понимаю, что существуют коннотации, которые go наряду с концепцией, которая могла запутать воду.

1 Ответ

1 голос
/ 26 апреля 2020

Ответ на ваш вопрос, на мой взгляд, во многом зависит от того, как вы разрабатываете свой домен, а не от того, как вы внедряете CQRS. То, как вы это представляете, кажется, что все эти операции и агрегаты находятся в одном и том же ограниченном контексте, но на первый взгляд, я бы подумал, что их 3 (назвать сложно!):

  1. Управление заказами или Продажи, где размещаются заказы
  2. Складские операции, где товары упаковываются для отправки
  3. Отгрузки, когда посылки помещаются в грузовики и отправляются

Когда Заказ - Placed в Управлении заказами, Склад реагирует и запускает рабочий процесс Упаковки. На этом этапе в хранилище должны быть все данные, необходимые для выполнения его логики c, без необходимости больше Order.

Затем менеджер склада может просмотреть отборочную накладную, выбрать линии, которые они хотели бы отправить, и затем выполнить команду PrepareShipment.

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

Событие ShipmentPrepared обновит исходный заказ и удалит соответствующие строки из PickingSlipView.

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

Действуя как посредник между двумя командами

Агрегаты выполняют команды, они не находятся между ними.

Просмотр этого под другим углом означает, что вам нужен этот агрегат: PrepareShippingCommand необходимо создать агрегат (Доставка), и, согласно Уди Даану, вы не должны создавать корни агрегатов (из воздуха). Вместо этого другие совокупные корни создают их. Таким образом, кажется справедливым сказать, что должна быть некоторая совокупность, которая гарантирует применение политик для создания отправлений.

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


ОБНОВЛЕНИЕ после обновления вопроса

Я прочитал пару раз обновленный вопрос и несколько раз обновил свой ответ , но каждый раз получал ответы, очень точно указывающие c на ваш пример, и я, скорее всего, упускаю много деталей, чтобы быть полезными (хотя я был бы рад обсудить это на другом канале). Поэтому я хочу go вернуться к первому предложению вашего вопроса, чтобы добавить важный пропущенный мной комментарий:

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

На мой взгляд, считанные модели являются одноразовыми. Они не являются единственным источником правды. Они представляют собой данные для простого удовлетворения текущих потребностей запросов. Когда эти запросы требуют изменения, старые модели чтения удаляются, а новые создаются на основе данных моделей записи.

Так что, только исходя из этого, я бы рекомендовал не готовить модель чтения, чтобы облегчить командные операции.

Я думаю, что ваше решение здесь:

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

Если я правильно понимаю, то, что вы должны здесь сделать, это не создать модель представления, а создать Aggregate (или несколько). Затем этот агрегат может получать команды, применять бизнес-правила и изменять состояние. Таким образом, вместо того, чтобы доменная служба считывала данные из «умных» моделей чтения и собирала их все вместе, у вас есть агрегат, который инкапсулирует необходимые данные и бизнес-логику c.

Надеюсь, это имеет смысл. Это широкий топи c, и мы могли бы говорить об этом часами, наверное.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...