Влад Хононов

Что такое предметно-ориентированное проектирование?


(What is Domain-Driven Design?)

Глава 6. Архитектурные паттерны
Выбор архитектурного паттерна — это критически важное проектировочное решение. Правильно выбранный паттерн поддержит реализацию функциональных и нефункциональных требований системы.

В этой главе мы рассмотрим три архитектурных паттерна и их применение: паттерн многослойной архитектуры, порты и адаптеры, а также CQRS-паттерн.
Паттерн многослойной архитектуры
Паттерн многослойной архитектуры организует код системы вокруг трех технических аспектов:

Слой представления
Представляет/определяет пользовательский интерфейс

Слой бизнес-логики
Реализует бизнес-логику

Слой доступа к данным
Обеспечивает доступ к механизмам хранения данных (базам данных и другим инфраструктурным компонентам)

Слои ссылаются друг на друга только в одном направлении, как показано на рисунке 6-1.

Рисунок 6-1. Многослойная архитектура

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

Терминология
Архитектура портов и адаптеров относится к пользовательскому интерфейсу системы, коду доступа к данным и всем остальным инфраструктурным аспектам просто как к «инфраструктурному слою».

Зависимости
Бизнес-логика, вместо того чтобы находиться между техническими аспектами, в архитектуре портов и адаптеров занимает центральное место. Она не зависит напрямую ни от каких инфраструктурных компонентов системы.

Слой приложения
Слой приложения реализует внешний вид публичного интерфейса системы. Он описывает все операции, выполняемые системой и оркестрирует бизнес-логику системы для их выполнения. Это показано на рисунке 6-2.

Рисунок 6-2. Архитектура портов и адаптеров

Интеграция инфраструктурных компонентов
Основная цель архитектуры портов и адаптеров — отделить бизнес-логику системы от её инфраструктурных компонентов.
Вместо того чтобы напрямую ссылаться и вызывать инфраструктурные компоненты, слой бизнес-логики определяет «порты», которые должны быть реализованы слоем инфраструктуры. Слой инфраструктуры реализует «адаптеры»: конкретные реализации интерфейсов портов для работы с разными технологиями. Слой приложения предоставляет адаптеры для портов бизнес-логики через внедрение зависимостей.

Например, вот возможное определение порта и конкретного адаптера для шины сообщений:

public interface IMessaging {
void Publish(Message payload);
void Subscribe(Message type, Action callback);
}
public class SQSBus : IMessaging {
...
}
Варианты использования
Отделение бизнес-логики от всех технических аспектов делает архитектуру портов и адаптеров отличным вариантом для бизнес-логики, реализованной с использованием паттерна модели предметной области.
Разделение ответственности на команды и запросы (CQRS)
Разделение ответственности на команды и запросы (CQRS) — это архитектурный паттерн, который позволяет представлять данные в нескольких постоянных моделях. Давайте посмотрим, почему нам может понадобиться такое решение и как его реализовать.
Многовариантное моделирование
Во многих случаях может быть сложно (а иногда даже невозможно) смоделировать работу системы так, чтобы удовлетворить все её потребности. Например, для реализации обработки транзакций в реальном времени (OLTP), оперативной аналитической обработки (OLAP) и поиска оптимальны разные модели.

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

Давайте посмотрим, как CQRS позволяет использовать несколько механизмов хранения, для представления разных моделей данных системы.
Реализация
Как подсказывает название паттерна, нам нужно разделить обязанности моделей системы. Существуют два типа моделей: модель выполнения команд и модели чтения.
Модель выполнения команд
CQRS выделяет одну модель для выполнения операций, которые изменяют состояние системы (команды системы) — другими словами, OLTP.

Модель выполнения команд также является единственной моделью, представляющей собой строго согласованные данные — источник истины системы.
Модели чтения (проекции)
Система может определить столько моделей, сколько необходимо для представления данных пользователям или другим системам. Эти модели доступны только для чтения. Ни одна из операций системы не может напрямую изменять данные моделей чтения.
Проекция моделей чтения
Чтобы модели чтения работали, система должна проецировать изменения из модели выполнения команд во все свои модели чтения. Этот концепт показан на рисунке 6-3.

Рисунок 6-3. Архитектура CQRS

Давайте рассмотрим два способа создания проекций: синхронные и асинхронные.
Синхронные проекции
Синхронные проекции получают изменения данных OLTP с помощью модели обновляющейся подписки:

  • Механизм проекции делает запрос в базу данных OLTP о записях, которые были добавлены или обновлены после последнего обработанной контрольной точки.
  • Механизм проекции использует обновлённые данные для восстановления/обновления моделей чтения системы.
  • Механизм проекции хранит контрольную точку последней обработанной записи. Это значение будет использоваться во время следующей итерации для получения записей, добавленных или изменённых после последней обработанной записи.
Этот процесс показан на рисунке 6-4.

Рисунок 6-4. Синхронное проецирование моделей чтения с помощью обновляющейся подписки

Чтобы обновляющаяся подписка работала, модель выполнения команд должна контролировать все добавленные или обновлённые записи. Механизм хранения также должен поддерживать запросы записей на основе контрольной точки.

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

Метод синхронной проекции позволяет легко добавлять новые проекции, а также пересоздавать существующие с нуля. В последнем случае вам нужно всего лишь обнулить контрольную точку; движок проекции будет сканировать записи и восстанавливать проекции с самого начала.
Асинхронные проекции
В сценарии асинхронных проекций модель выполнения команд публикует все совершённые изменения на шине сообщений. Механизмы проекций системы могут следить за опубликованными сообщениями и использовать их для проецирования моделей чтения, как показано на рисунке 6-5.

Рисунок 6-5. Асинхронное проецирование моделей чтения

Трудности
Несмотря на очевидные преимущества асинхронного метода проекции в плане масштабирования и производительности, он более подвержен трудностям распределённых вычислений. Если сообщения обрабатываются в неправильном порядке или дублируются, в модели чтения будут проецироваться несогласованные данные.

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

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

Одним из распространённых заблуждений о системах, построенных на основе CQRS, является то, что команда может изменять только данные, а данные можно получать для отображения только через модель чтения. Такой подход создает случайные сложности и приводит к плохому пользовательскому опыту. Команда может возвращать данные, если они получены из модели выполнения команд.
Варианты использования
Паттерн CQRS может быть полезен для приложений, которым необходимо работать с одними и теми же данными в нескольких моделях.

Более того, CQRS отлично подходит для моделей предметной области, основанных на событиях. Модель, основанная на событиях не может запрашивать записи на основе состояний агрегатов, но CQRS позволяет делать это, проецируя состояния в базы данных, доступные для запроса.
Заключение
Многослойная архитектура декомпозирует кодовую базу на основе технических аспектов. Поскольку этот паттерн связывает бизнес-логику с реализацией доступа к данным, он подходит для систем, основанных на паттерне активной записи.

Архитектура портов и адаптеров инвертирует отношения: она ставит бизнес-логику в центр и отделяет её от всех инфраструктурных зависимостей. Этот паттерн подходит для бизнес-логики, реализованной с использованием паттерна модели домена.

Паттерн CQRS представляет одинаковые данные в нескольких моделях. Хотя этот паттерн обязателен для моделей предметной области, основанных на событиях, его также можно использовать в любых системах, которым необходимо работать с несколькими неизменяемыми моделями.