Архитектурные паттерны DDD: Анти-коррупционный слой, Open Host Service, Shared Kernel, Customer-Supplier
В рамках курса Domain-Driven Design архитектурные паттерны выступают как инструменты для устойчивого распределения ответственности между контекстами, минимизации зависимости доменного языка и упрощения эволюции системы. Анти-коррупционный слой (ACL) обеспечивает защиту модели от внешних влияний, Open Host Service (OHS) устанавливает четко очерченную открытую границу между контекстами, Shared Kernel предоставляет минимальный общий язык и код, а паттерн Customer-Supplier задаёт контрактную основу для сотрудничества между контекстами. Вместе они формируют системную ткань, на основе которой достигается управляемая изменяемость, устойчивость к внешним изменениям и ясная коммуникация между командами.
Изложение ориентировано на техническую реализацию: архитектурные принципы, протоколы взаимодействия, схемы интеграции и примеры реализации. Рассматриваются как концептуальные основы, так и конкретные подходы к модульности, контрактам и эволюции доменной модели.
- ACL: как отделить доменную логику от внешних изменений и как строить переводчики между моделями.
- OHS: как публиковать открытые контракты и поддерживать совместимость версий.
- Shared Kernel: какие элементы включать и как управлять зависимостями между контекстами.
- Customer-Supplier: как выстраивать формальные контракты и минимизировать двусторонние зависимости.
- Интеграционные контракты и управление изменениями: тестирование контрактов, эволюция моделей и процессы обновления сервисов.
Краткое содержание главы
- Архитектурные принципы ACL, OHS, Shared Kernel и Customer-Supplier как инструменты стратегического проектирования в DDD.
- Контракты и управление изменениями: версияция, совместимость, контрактное тестирование и практики регистров контрактов.
- Реализация ACL и OHS в реальных сценариях: адаптеры, преобразование моделей, контракт-центричное проектирование.
- Управление совместной эволюцией через Shared Kernel и договоренности между контекстами.
Анти-коррупционный слой (Anti-Corruption Layer)
Анти-коррупционный слой служит защитой границы между контекстами, предотвращая проникновение чужой модели в доменную логику. ACL не просто «перекладывает» данные; он выполняет трансляцию смыслов, адаптацию моделей и согласование ограничений между двумя языками: внешним и доменным. Главные цели ACL - сохранение целостности доменной модели, снижение риска рассогласований и упрощение замены внешних систем без воздействия на внутреннюю логику.
Архитектурно ACL реализуется через три уровня:
- граница трансляции, где внешние сущности приводятся к внутренним концепциям;
- адаптеры, которые управляют взаимодействием и защищают агрегацию доменной модели;
- слой тестирования, который валидирует соответствие контрактов и сценариев между контекстами.
ACL предполагает наличие явной контрактной границы: что отправляется во внешний мир и как это преобразуется обратно. Это не только техническое разделение, но и управляемая договоренность между командами, ответственными за контексты.
Реализация ACL: подходы и шаги
- Идентифицировать точки входа и выхода между контекстами, где существует риск изменения внешнего языка.
- Определить анти-коррупционные контракты - минимальный набор понятий из внешнего мира, которые необходимы доменному контексту.
- Реализовать адаптеры и трансляторы, которые переводят внешние сущности в внутренние Value Objects и наоборот.
- Управлять схемами данных через изолированный набор маппингов, избегая прямого копирования полей без смысла.
- Ввести контрактное тестирование, где потребители и поставщики подтверждают соответствие контрактам.
/** * Пример упрощенной адаптации ACL на уровне доменного слоя. * LegacyCustomer - внешний представитель клиентов, DomainCustomer - внутренний язык. */ interface ILegacyToDomainMapper { ## DomainCustomer toDomain(LegacyCustomer lc); LegacyCustomer toLegacy(DomainCustomer dc); } class LegacyCustomer { String legacyId; String fullName; String externalStatus; } class DomainCustomer { String id; String name; CustomerStatus status; } enum CustomerStatus { ACTIVE, INACTIVE } class ACLAdapter { private final ILegacyToDomainMapper mapper; // вызов внешнего сервиса public DomainCustomer fetchAndMap(String id) { LegacyCustomer lc = externalLegacyService.find(id); return mapper.toDomain(lc); } }ACL требует дисциплины в поддержке и расширении трансляторов: новые внешние системы порождают новые адаптеры, но граница остается неизменной. В этом контексте важна роль калиброванных изменений и регламентов по обновлению контрактов, чтобы не нарушать доменную логику.
Принципы проектирования ACL
- ограничение зоны влияния: внутренняя модель должна понимать только свои доменные понятия.
- минимизация эффекта изменений: внешняя система может меняться, но переводчик держит изменения внутри адаптеров.
- явная история изменений: регистрируйте преобразования в контрактном уровне, чтобы команды знали, какие внешние изменения они должны поддерживать.
- устойчивость к несовместимостям: предусмотреть обработку ошибок и устойчивые «паузы» между контекстами.
Риски и управление ими
ACL не должен превращаться в монолитную точку задержек. Риски включают чрезмерную сложность трансляторов, избыточный объем кросс-контекстной логики и задержки в эволюции контекстов. Эффективное управление предполагает выделение минимального набора трансляций, тестирование на уровне контрактов и регулярную рестаурирование контрактной базы.
Open Host Service
Open Host Service - это паттерн, который призван обеспечить открытые и стабильные границы между контекстами, позволяя внешним потребителям безопасно и предсказуемо взаимодействовать с сервисами внутреннего контекста. OHS акцентирует внимание на контрактности интерфейсов, поддержке независимости развивающихся контекстов и управлении версиями интерфейсов. В рамках OHS host-сервис становится «хостом» для определенных доменных возможностей, а потребитель - клиентом, который опирается на хорошо задокументированные контракты.
Ключевые идеи:
- контрактная открытость: спецификация API или событий должна быть доступна заранее и разворачиваться независимо от реализации.
- контрактное тестирование: для каждого контракта следует иметь тесты согласования между потребителем и поставщиком.
- управление версиями: поддержка нескольких версий контрактов, плавная деградация и механизм миграции потребителей.
Контракт и контракт-first разработка
Open API или аналогичный контракт становится единственным источником истины для интеграции. Контракты публикуются и поддерживаются вне зависимости от реализации сервиса. Такой подход упрощает параллельную разработку команд и облегчает обособление Release Train.
openapi: 3.0.0
info:
title: Host Order Service Open Contract
version: 1.0.0
paths:
/orders/{orderId}:
get:
summary: Retrieve order by ID
parameters:
- **name**: orderId
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
components:
schemas:
Order:
type: object
properties:
orderId:
type: string
customerId:
type: string
total:
type: number
status:
type: string
Версии контрактов должны быть явно задокументированы, с четкой политикой по добавлению новых полей, деприкации старых полей и поддержке параллельной работы нескольких версий клиента и сервиса. В реальных системах это достигается через версионированные эндпоинты, отдельные пространства имен контрактов и механизм маршрутизации по версии.
Эволюция контракта и совместимость
- совместимость по умолчанию: новые версии должны быть backward-compatible, чтобы существующие потребители не ломались.
- плавная деградация: удаление полей и возможностей** - только после уведомлений и миграций.
- контрактное тестирование: проверка соответствия контрактов для всех участвующих сторон.
- роль документирования: контракт** - это источник правды, который отражает консенсус между командами.
Примеры реализации и практики
Open Host Service часто реализуется через REST или gRPC интерфейсы, а также через событийные каналы (Kafka, NATS) для асинхронной интеграции. Важно, чтобы сложности реализации не приводили к «лишним» зависимостям между контекстами: сервис-поставщик не должен зависеть от деталей потребителя, и наоборот.
Shared Kernel
Shared Kernel представляет собой минимальный набор общих доменных понятий и кода, который используется несколькими контекстами. При правильной реализации он снижает дублирование и упрощает коммуникацию, но несет риски чрезмерной связности и узкой эволюционной свободы. Поэтому структура Shared Kernel должна быть ограничена и управляться через строгие правила.
Элементы Shared Kernel обычно включают:
- общие значения, такие как Money, Email, Identifier, Currency;
- базовые универсальные правила валидации и поведения для кросс-контекстной передачи данных;
- совместно используемые сущности, которые действительно отражают единый смысл в разных доменах.
Пример ограниченного набора в Shared Kernel
package com.company.sharedkernel;
public final class Money {
private final int amount;
private final String currency;
// конструктор, геттеры, equals, hashCode
}
public final class Email {
private final String address;
// валидация формата
}
Важно придерживаться следующих правил:
- ограничить surface area: не выносить в Shared Kernel элементы, которые сильно зависят от контекста;
- поддерживать версионирование: любые изменения должны проходить через процесс согласования и выпуска нового артефакта;
- обеспечивать совместимость: обновления должны быть совместимыми для уже зависимых контекстов или сопровождаться миграцией.
Управление зависимостями и эволюция
Shared Kernel - тонкое место архитектуры. Он должен служить как «мост» между контекстами, но не превратиться в узкое место изменений. Команды ответственные за Kernel должны иметь отдельный процесс управления изменениями, регистр контрактов и тестовую базу, которая подтверждает совместимость изменений с каждым контекстом.
Риски и способы минимизации
- чрезмерная зависимость между контекстами - ограничить набор элементов и регулярно пересматривать набор компонентов.
- узкая эволюционная свобода - внедрить процесс голосования о внесении изменений и требования к обратной совместимости.
- проблемы сборки и выпуска - использовать монорепозитории, автоматизированные сборки и тесты, ограничение прав доступа к изменениям.
Customer-Supplier
Паттерн Customer-Supplier формирует формальный контракт и управляет взаимной зависимостью между двумя контекстами. В этом паттерне один контекст (Customer) задает сторону потребителя услуг, другой (Supplier) - производителя услуг, и связь между ними строится на строгой контрактной основе. Принципы: явная спецификация контрактов, границы ответственности, независимое тестирование совместимости и эволюционный подход к изменениям.
Ключевые элементы:
- контракт как первый класс: документируем сигнатуры API, форматы сообщения, семантику состояний и ожидаемое поведение.
- версионирование интерфейсов: поддержка нескольких версий контрактов, чтобы избежать мгновенной несовместимости.
- тестирование контракта: контрактные тесты подтверждают соответствие между клиентом и поставщиком.
- управление изменениями: регистр изменений, влияние на потребителей, план миграции.
Реализация и примеры
Контракт может быть описан как последовательность ожиданий потребителя и решений поставщика. Это может быть REST API, сообщение в очереди или событийная архитектура. Пример контракта на уровне API:
{
"endpoint": "/orders",
"method": "POST",
"request": {
"customerId": "C-789",
"items": [
{"sku": "XYZ", "qty": 1}
]
},
"response": {
"orderId": "O-001",
"status": "CREATED"
}
}
Другой пример - использование событий: контракты на публикуемые события включают схему событий, обязательные поля и допустимую последовательность изменений. Такой подход обеспечивает асинхронную интеграцию, снижая жесткую связанность между контекстами.
Контроль изменений и регламент
- регистр контрактов: каждому контракту присваивается номер версии и срок поддержки.
- контрактное тестирование: тесты выполняются в CI и включают симуляцию сценариев потребителя.
- управление совместным изменением: когда поставщик хочет изменить контракт, он публикует уведомление, а потребители получают уведомление и имеют план миграции.
Роли и управление процессами
- технические руководители контекстов участвуют в согласовании изменений контрактов.
- команды разработки должны следовать принципам контрактной разработки и тестирования.
- выделение ответственности: клиенты и поставщики ответственны за соответствие своим ролям и контрактам.
Интеграционные контракты и управление изменениями
Эта часть главы рассматривает cross-cutting аспекты контрактной архитектуры и изменений в составе DDD-подхода. Эффективная интеграция требует не только определения контрактов, но и систематизации процессов их эволюции, регулярного тестирования и прозрачного управления версиями.
- Версионирование контрактов: применяйте явное семантическое версионирование (Major, Minor, Patch). Major-изменения - совместные изменения, которые ломают обратную совместимость; Minor - добавления без удаления старого поведения; Patch - исправления без функциональных изменений.
- Совместимость и миграции: планируйте миграции для потребителей контракта, зафиксируйте стратегию deprecation и коммуникацию с командами клиентов и поставщиков.
- Контрактное тестирование: применяйте Pact, Spring Cloud Contract или эквивалентные инструменты для автоматизации тестирования взаимной совместимости между потребителем и поставщиком.
- Документация контрактов: держите актуальные спецификации в открытом доступе, чтобы команды могли планировать изменения без недоразумений.
- Эволюционные стратегии: для внешних сервисов предусмотреть временные окна и обратную совместимость, чтобы потребители могли мигрировать.
- Контроль качества контрактов: внедрите графики интеграций, сценарии регрессионного тестирования и мониторинг изменений в контрактах.
- Governance и регистры: храните регистры контрактов, решения по эволюции и историю изменений, распределяя ответственность между командами.
- Безопасность и соответствие: учитывайте требования безопасности, аудита и соответствия в контрактах, особенно когда контракты обращаются к чувствительным данным.
- Инструменты и практики: используйте инфраструктуру как код для конфигураций контрактов, автоматизированные пайплайны деплоя и тестирования, чтобы ускорить надежную эволюцию контрактов.
- Роль архитекторов и команд: архитекторы устанавливают принципы контрактной архитектуры; команды эксплуатации и разработчики должны внедрять и поддерживать их в повседневной работе.
Key takeaways
- Анти-коррупционный слой (ACL) защищает доменную модель, минимизируя влияние внешних изменений через адаптеры и переводчики.
- Open Host Service устанавливает открытые, версионируемые контракты и обеспечивает контрактное взаимодействие между контекстами.
- Shared Kernel - мощный инструмент для снижения дублирования, но требует строгого управления зависимостями и эволюции.
- Customer-Supplier фокусируется на формальных контрактах и управлении изменениями, что снижает двустороннюю зависимость и повышает предсказуемость.
- Управление контрактами и изменениями включает версионирование, контрактное тестирование и регистры изменений, что поддерживает устойчивый прогресс архитектуры.
FAQ
- Что такое Anti-Corruption Layer и зачем он нужен в DDD?
ACL - это слой границы между контекстами, который переводит внешнюю модель в внутреннюю доменную модель и наоборот. Он защищает доменный язык от чужих концептов, предотвращает рассогласование и упрощает эволюцию каждого контекста независимо. Без ACL изменения во внешних системах могут немедленно затронуть бизнес-правила внутри контекста, привести к сложной миграции и потере целостности доменной модели.
- В чем различие между ACL и Open Host Service?
ACL ориентирован на защиту доменной модели при интеграции с внешними системами и включает адаптеры и трансляцию. OHS - это архитектурный контракт и открытая граница, через которую сервисы внутри контекста предоставляют свои возможности внешним потребителям. OHS фокусируется на контрактности, открытости и управляемом доступе, тогда как ACL обеспечивает корректное преобразование концепций и защиту доменной лексики.
- Какие элементы следует включать в Shared Kernel и как его поддерживать?
Shared Kernel должен включать ограниченное количество общих понятий и кода, которые действительно необходимы для нескольких контекстов, например Money, Email, Identity. Важно держать его узким, управлять версиями и иметь регламент по добавлению элементов. Регулярно пересматривайте surface-area и избегайте переноса специфической бизнес-логики, которая не применяется во всех контекстах.
- Как выстроить эффективные контракты Customer-Supplier?
Контракт должен быть явным документом: сигнатуры API, форматы данных, семантика состояний и ожидаемое поведение. Версии контрактов должны поддерживать совместимость, а контрактные тесты - проверять соответствие между потребителем и поставщиком. Контракты рекомендуется хранить в регистре изменений и поддерживать уведомлениями о выпусках новых версий.
- Какие техники применяются для управления изменениями контрактов?
Основные техники: семантическое версионирование, тестирование контрактов (contract tests), регистра контрактов, де-факто соглашения о приемке и план миграции. В случае изменения контракта требуется заранее уведомлять потребителей, предоставлять альтернативные версии и проводить миграцию по плану.
- Какие риски связаны с ACL и как их минимизировать?
Риски включают избыточную сложность трансляторов, чрезмерное уплотнение внешних моделей и задержки из-за обособления адаптеров. Чтобы минимизировать риски, ограничьте число переводов, поддерживайте тестовую базу, документируйте контрактные ожидания и избегайте зависимости от деталей внешних систем в доменной логике.
- Какие протоколы и форматы обычно применяются для OHS?
Чаще всего используют REST или gRPC для синхронных API, а для асинхронной интеграции - сообщения в очередях (Kafka, NATS). В любом случае контракт OpenAPI або аналогичный контракт должен описывать точки доступа, форматы запросов и ответов, сигнатуры ошибок и требования к безопасному доступу.
- Какие инструменты поддерживают контрактное тестирование в DDD?
Популярные решения включают Pact и Spring Cloud Contract, которые позволяют создавать контрактные тесты между потребителями и поставщиками. Эти инструменты помогают автоматизировать проверки соответствия контрактам в CI/CD и снижают риск неожиданной несовместимости после развёртываний.
- Как выбрать между ACL и другими паттернами интеграции?
ACL выбирается, когда внешние системы обладают несовместимыми моделями или когда требуется строгая изоляция доменной модели. Если внешнее взаимодействие можно описать в открытом контракте без сложной трансляции, можно начать с OHS. Выбор зависит от риска эволюции внешних систем, вероятностей рассогласований и скорости изменений в бизнес-требованиях.
- Как обеспечить устойчивую эволюцию между контекстами в долгосрочной перспективе?
Важно внедрить регулярные ревью контрактов, регистр изменений и контрактное тестирование. Разделяйте ответственность между командами за контракты и реализуйте процессы governance. Используйте документацию и регистры, чтобы команды могли планировать обновления, минимизируя бизнес-риски и простои.



