DDD одна транзакция на агрегат - случай отслеживания - PullRequest
1 голос
/ 16 мая 2019

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

Естественно иметь следующую реализацию:

A.createB() {

  if (total> x) 
        raise an error

  total++
  return new B()
}

Но это нарушило бы правило изменения двух агрегатов: создание B и изменение A.

Если я попытаюсь соблюдать это правило, я бы: 1. Создайте B в A и создайте событие, подобное BCreated. 2. Обновите общее количество A в следующей транзакции, обработав событие BCreated.

Для меня в этом конкретном примере это выглядит как странный обходной путь, поскольку после вызова метода createB () для A я оставляю его в несовместимом состоянии.

Я что-то упустил?

Ответы [ 3 ]

0 голосов
/ 16 мая 2019

Я что-то упустил?

Вы ничего не пропускаете просто , нет.

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

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

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

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

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

0 голосов
/ 17 мая 2019

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

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

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

Чтобы обойти проблему, вам нужно «зарезервировать» создание на первом шаге, используя, скажем, некоторые идентификатор корреляции .Затем его можно сохранить в транзакции A:

    // begin tx (application layer)
    if (A.Reserve(id))
    {
        // we're good
        bus.Send(new RegisterBCommand
                 {
                    Id = id,
                    TheIdForA = theId
                    // other properties
                 }); // perhaps using Shuttle.Esb
    }
    // commit tx (application layer)

На следующем шаге будет зарегистрирована сущность B и, возможно, будет опубликована BRegisteredEvent, которая может продолжить процесс.

Еще один момент: вы обычнобудет иметь A.CreateB() только если A и B живут в одном и том же ограниченном контексте.Другим способом достижения чего-то слегка похожего было бы использование ограниченного интеграцией контекста (скажем, вашей оркестрации BC), а затем использование CreateB() в качестве метода расширения для A, где A и B находятся в отдельных BC, но уровень оркестровки использует оба домена,Другой путь - простой завод или просто добавление его в службу приложения / домена.

0 голосов
/ 16 мая 2019

Ради простоты я бы отнесся к этим совокупным изменениям как к единице работы. Единственный совет, что вы должны иметь дело с условиями гонки. Теперь, если вы хотите увидеть возможное решение в качестве примера, вам нужно смоделировать агрегат, моделирующий транзакцию изменений A и B: BCreation A.requestBCreation изменения Состояние и отправка события (BCreationAllowed) BCreation реагирует на, а затем BCreation отправляет команду для создания B и обрабатывает ее событие домена последствий, скажем, BCreated или BCreationRejected. Совокупный BCreation слушает любое из этих событий и так далее. Это может быть сложным и непростым решением. Вам также придется иметь дело с условиями гонки и синхронизировать совокупность процессов. Все было бы намного проще, если бы вы использовали модель актера.

...