Представим типичное Django-приложение для бронирования авиабилетов. В нём можно выделить несколько ограниченных контекстов (bounded contexts), каждый из которых отвечает за свою часть домена. Например:
Каждый контекст имеет свой собственный модельный словарь и логику, изолированные от других. Это значит, что общие термины (например, «рейс», «бронирование», «пользователь») могут иметь немного разный смысл в разных контекстах, и у каждого контекста – своя база данных или свои модели в Django, соответствующие его области ответственности. Подход Domain-Driven Design (DDD) рекомендует разделять систему на такие контексты, чтобы каждая часть оставалась управляемой и понятной. Однако при этом возникает вопрос: как эти контексты интегрировать друг с другом, чтобы система работала целикомmedium.com?
Ниже рассмотрены распространённые паттерны интеграции ограниченных контекстов (паттерны контекстных карт) и примеры их применения в нашем приложении бронирования авиабилетов. Каждый паттерн описывает характер взаимоотношений между контекстами и подход к обмену данными между ними.
Суть паттерна: Партнерство означает тесное сотрудничество между двумя (или более) контекстами. Команды, разрабатывающие эти контексты, имеют общие цели и взаимозависимость результатов. Проще говоря, успех одного контекста невозможен без успеха другого, поэтому изменения планируются и проводятся совместноdeviq.com. Интеграция при партнерстве происходит «на лету»: если одна команда меняет интерфейс или модель, она согласует изменение с другой командой заранее, и та оперативно адаптируется под негоmedium.com. Здесь нет жёсткого диктата одного контекста над другим – обе стороны договариваются о решениях и быстро устраняют любые проблемы интеграции через общение.
Применение в системе бронирования: Представим, что контексты Бронирование и Каталог рейсов работают в режиме партнерства. Они тесно связаны бизнес-логикой: если модуль расписания рейсов даст сбой или предоставит неправильные данные, модуль бронирования не сможет оформить билеты (и наоборот, сбой в бронировании ударит по успеху продаж рейсов). Таким образом, неуспех одного ведёт к неуспеху другого, заставляя их действовать согласованноdeviq.com. На практике команды этих контекстов будут плотно взаимодействовать. В Django-проекте это может означать, что оба контекста находятся в одном монолитном приложении (как разные Django-приложения внутри проекта) и разработчики синхронно вносят изменения. Например, если модель рейса изменилась (скажем, добавилось новое поле, влияющее на доступность мест), команда «Каталог рейсов» сообщит об этом команде «Бронирование» заранее, и та обновит свой код, зависящий от этой модели. Благодаря этому релизы обоих модулей координируются – изменения выкатываются одновременно, не нарушая работу системы. Паттерн партнерства хорошо работает, если команды находятся в тесном контакте (например, в одной локации или активно коммуницируют онлайн) и готовы совместно решать проблемы интеграции в реальном времени.
Суть паттерна: Общее ядро – это подход, при котором два или несколько ограниченных контекстов разделяют некоторую часть модели или кода. Этот общий компонент (будь то библиотека кода, набор моделей или схема базы данных) является единым для обоих контекстовddd-practitioners.com. Все изменения в общем ядре должны строго контролироваться и согласовываться между командами, чтобы никто в одностороннем порядке не сломал совместимостьmedium.com. Shared Kernel позволяет избежать дублирования важных понятий в разных контекстах и гарантирует их согласованность, но взамен вводит тесную связь: изменения в общем коде мгновенно отражаются на всех контекстах, его разделяющихmedium.commedium.com.
Применение в системе бронирования: Допустим, контексты Бронирование и Оплата решили использовать общее ядро для совместных доменных понятий. Например, оба контекста оперируют сущностью «Заказ» (бронирование) и договорились вынести некоторые общие аспекты заказа в общую библиотеку. Это может быть разделяемая Django-модель или модуль, включающий, скажем, логику расчёта итоговой цены заказа с учётом тарифов и налогов, которая нужна и при бронировании, и при проведении оплаты. Оба контекста импортируют эту библиотеку, чтобы работать с заказом единообразно. Благодаря этому нет дублирования – например, статус оплаты заказа трактуется одинаково и в модуле бронирования, и в модуле оплаты. Однако, если потребуется изменить структуру заказа (например, добавить новый статус или поле), то изменения в общей части нужно согласовать заранее и одновременно обновить обе части системыmedium.com. В терминах Django, общее ядро может быть реализовано как отдельное приложение (например, common
), устанавливаемое в INSTALLED_APPS, или как внешняя общая библиотека, подключаемая к проектам обоих контекстов. Важно ограничить размер общего ядра – хранить там только действительно общие и стабильные вещи (например, справочники аэропортов или валют, базовые сущности и утилиты), чтобы минимизировать частоту изменений и конфликты при совместной работеmedium.com. При правильном использовании паттерна Shared Kernel оба контекста выигрывают за счёт согласованности, но остаются риск крепкой зависимости: любая ошибка или изменение в общем коде затронет сразу несколько частей системы.
Суть паттерна: «Конформист» – это модель отношений, при которой один контекст добровольно принимает модель и правила другого контекста без попыток их изменитьmedium.com. Чаще всего такое происходит, когда имеется чёткое разделение на downstream и upstream: один контекст полагается на данные/функциональность другого, но не имеет влияния на его дизайн или возможности. Если команда «верхнего» контекста не идёт навстречу или это сторонняя система, то «нижний» контекст просто подстраивается (конформируется) под предоставляемый интерфейс как естьmedium.com. Проще говоря, downstream-контекст использует язык и модели upstream-контекста, даже если они ему не идеально подходят, без переговоров – отсюда и термин "Conformist"medium.com.
Применение в системе бронирования: Предположим, модуль Маркетинг хочет использовать данные о бронированиях (например, для рассылки специального предложения клиентам, которые недавно купили билеты). Контекст «Маркетинг» – вспомогательный для бизнеса, и команда «Бронирования» не планирует под него подстраивать свой API специально. В таком случае контекст маркетинга выступает конформистом: он берет те данные и тот интерфейс, что предоставляет контекст бронирования, и использует их «как есть». Например, если система бронирования предоставляет только REST API с определёнными полями или выгрузку CSV отчётов, то маркетинг будет работать напрямую с этими структурами. Он не требует изменений в контексте бронирования, а вместо этого конформируется под его язык (например, принимает понятия вроде идентификатора брони, структуры данных билета ровно в том формате, как их выдаёт контекст бронирования). С точки зрения Django, это может выглядеть как прямое чтение маркетинговым приложением таблиц базы данных бронирования или вызов публичных методов/API другого приложения. Минус такого подхода – контекст «Маркетинг» становится сильно зависимым от модели «Бронирования» (если она изменится, придётся корректировать маркетинг). Однако зачастую конформист – самый простой путь интеграции в ситуациях, когда «ведущий» контекст диктует правила (например, интеграция с большим сторонним сервисом)medium.com. В нашем случае маркетинговая команда может решить, что проще подстроиться под существующие данные бронирования, чем требовать изменений или разрабатывать сложный адаптер.