Связь между двумя ограниченными контекстами в DDD
У меня есть несколько разных ограниченных контекстов в домене. Проверка операции CRUD строится в каждом ограниченном контексте.
Например, я могу создать объект с именем GAME, только если человек, создавший его, является лидером группы.
У меня есть два ограниченных контекста (BC) в этом примере. Одна из них - Game BC, а другая - BC пользователя. Чтобы решить эту проблему, в Игре BC я должен выполнить вызов службы домена, такой как IsGroupLeader(), для Пользователя BC, прежде чем приступить к созданию Игры.
Я не думаю, что этот тип связи рекомендуется DDD. У меня может быть сущность User также в Game BC, но я не хочу, потому что одна и та же сущность User используется по-разному в другом контексте в другом BC.
Мои вопросы:
Должен ли я использовать события домена, когда Game BC должен отправить событие пользователю BC с запросом статуса пользователя? При таком подходе я делаю не синхронный вызов, как IsGroupLeader, а событие is_group_leader. Затем Game BC должен ждать, пока User BC обработает событие и вернет статус. Game BC создаст объект Game только после того, как пользователь BC обработает событие.
Является ли CQRS решением моей проблемы?
Любая идея приветствуется.
5 ответов
При интеграции BC, у вас есть несколько вариантов. Причиной того, что вызов внешнего BC не рекомендуется, является то, что он требует, чтобы оба BC работали одновременно. Тем не менее, это часто вполне приемлемо и проще, чем альтернатива. Альтернатива состоит в том, чтобы Game BC подписывался на события от User BC и сохранял локальные копии необходимых ему данных, которые в этом случае являются информацией о том, является ли пользователь лидером группы. Таким образом, когда Game BC необходимо определить, является ли пользователь лидером группы, ему не нужно вызывать User BC, он просто читает локально сохраненные данные. Задача этой управляемой событиями альтернативы заключается в синхронизации событий. Вы должны убедиться, что Game BC получает все соответствующие события от пользователя BC. Другая проблема связана с возможной согласованностью, поскольку BC могут быть немного не синхронизированы в любой данный момент времени.
CQRS несколько ортогональна этой проблеме.
Вот как я бы рассуждал об этом.
Я бы сказал, что Game BC не знает о "пользователях", однако может знать о "игроках".
Если Game BC зависит от активного / текущего игрока, то его следует передать в BC при создании экземпляра Game BC.
например.
Player currentPlayer = GetPlayerSomehow...();
GameBC gameBC = new GameBC(currentPlayer);
gameBC.DoStuff();
Теперь ваши два BC до сих пор разделены, вы можете проверить их отдельно и т. Д.
И чтобы все это работало, вы просто делаете что-то вроде:
User currentUser = GetCurrentUser();
Player currentPlayer = new Player();
currentPlayer.IsGroupLeader = currentUser.IsGroupLeader;
GameBC gameBC = new GameBC(currentPlayer);
gameBC.DoStuff();
Это служит антикоррупционным слоем между UserBC и GameBC, вы можете перемещать и проверять желаемое состояние из UserBC в состояние, необходимое для вашего GameBC.
И если вашему GameBC необходимо получить доступ ко многим пользователям, вы все равно можете передать какой-то картографический сервис в игру BC, которая выполняет такое преобразование внутри страны.
Я думаю, что ты почти там. Близко к хорошему решению. Я не уверен, что вы должны разделить эти два на два до н.э. Ваш пользователь Aggregateroot (?) И игра могут принадлежать одному BC и зависеть друг от друга. Пользователь "имеет" Членство "в одной или нескольких" Играх "(просто угадывая ваши отношения сущности). Но сейчас я просто мозговой штурм. Попробуйте следовать:) Различные подходы следующие:
Во-первых, в GameBC есть метод Create(), который фактически принимает UserMembership в качестве параметра. Создать (UserMembership). Затем вы через UserMembership сущность знаете, какое членство и пользователя это. Если принято, игра создана. Если не выдается исключение или Game получает сообщение о нарушении правила, это зависит от того, какой подход вы хотите сообщить клиенту. Координация может быть выполнена на прикладном уровне без утечки знаний о предметной области.
Второй Вы делаете как один из других ответов. Вы вызываете CreateGameEvent в методе Game.Create(UserId). Это событие перехватывается EventHandler (зарегистрированным IoC при запуске приложения), который находится на уровне приложения и ищет UserMembership через репозиторий. Небольшая утечка знаний в области - это то бизнес-правило, которое знает, кому разрешено делать то, что проверено на прикладном уровне. Это можно решить, разрешив CreateGameEventHandler принять UserId и RuleRef (может быть строкой "CAN_CREATE_GAME" или enum) и разрешив объекту UserPermission проверить разрешение. Если не. Исключение выбрасывается и ловится на прикладном уровне. Недостатком может быть то, что вы не хотите, чтобы строки ссылок на разрешения были жестко закодированы в методе Create.
Третье... продолжается там, где заканчивается второй подход. Вы знаете, что GameBC может не подходить для поиска разрешений пользователей, если вы следуете принципу SRP. Но действие запускается вокруг этого метода как-то. Альтернативой может быть Create (пользователь GroupLeader). ИЛИ вы можете использовать Game.Create (Пользователь-пользователь), а затем выполнить проверку того, что Пользователь имеет тип GroupLeader. Create(GroupLeader) говорит вам, что вам нужно для вызова этого метода.
Последнее Может быть альтернатива, которая мне больше нравится сейчас, когда пишу это. Когда вы хотите создать сущность, я обычно позволяю этому методу Create (Сохранить) находиться в хранилище. Интерфейс IGameRepository расположен рядом с Game Entity в проекте сборки домена. Но Вы также можете создать GameFactory, которая отвечает за запуск жизненного цикла Game Entity. Здесь также можно поместить метод Create... GameFactory.Create(GroupLeader) { return new Game.OwnerUserId = GroupLeader.Id; } Затем вы просто сохраняете его IGameRepository.Save(Game)
Затем у вас есть интуитивно понятный и самоописываемый способ сказать другим разработчикам, что "у вас должен быть экземпляр GroupLeader для создания игры".
Наконец, я надеюсь, что вы понимаете, что вы знаете домен, и вы поймете, что подходит вам лучше всего. Будьте прагматичны и не переходите на хардкор Эрика Эвана. Есть так много разработчиков, которые застряли в "религии" в том, как все будет сделано. Размер проекта, деньги, время и зависимости от других систем и т. Д. Также влияют на то, насколько хорошо вы можете быть строгими в выполнении DDD.
Удачи.
Чтобы справиться с проблемами, с которыми вы сталкиваетесь, мы используем ограниченные роли - модель моделирования, которая появилась на протяжении многих лет и доказала свою эффективность. Ограниченные контексты определяются после семантических единиц, которые часто в корпоративных организациях могут быть сопоставлены с конкретными ролями.
Это должно быть очевидно, учитывая, что разные роли сталкиваются с разными проблемами и, следовательно, говорят на нескольких (или полностью) разных языках.
Таким образом, мы всегда моделируем роли, которые взаимодействуют с нашим приложением, как точку соединения между прикладными требованиями (инфраструктура, постоянство, пользовательский интерфейс, локализация и т. Д.) И бизнес-правилами (домен), и мы кодируем их в различных модулях (или сборках или пакеты).
Что касается вашего второго вопроса, CQRS может быть способом кодирования типа взаимодействий между BC, которые вы описываете, но мне не нравится это в этом конкретном контексте.
Я думаю, что мне, возможно, придется следовать другому подходу, когда я сделаю сущность User частью Game BC (эта же сущность также является частью User BC). Я буду использовать репозиторий для чтения флага IsGroupLeader из БД в игре BC. Таким образом, зависимость от пользователя BC удаляется, и связь с пользователем BC не требуется.
Как вы думаете?
Я бы предложил передать роль пользователя и пользовательской информации в Game bounded context service
а также иметь GroupLeader
value object
внутри игры BC. таким образом вы всегда можете узнать ктоgroup_leader
.