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

предметно-ориентированное проектирование и моделирование микросервисов
предметно-ориентированное проектирование и моделирование микросервисов

Предметно-ориентированный подход (DDD, Domain-Driven Design) в проектировании программного обеспечения является наиболее эффективным способом моделирования бизнес-процессов. Проектирование, в свою очередь, можно разделить на стратегическое и тактическое. Для аналогии, представьте себе процесс написания картины: сперва вы создаете набросок крупными мазками, а затем прорабатываете детали.

Стратегическое проектирование

Стратегическое проектирование призвано разделить работу по степени важности. В рамках теории DDD, стратегическое проектирование начинается с выделения ограниченных контекстов и построения единого языка.

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

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

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

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

Грамотное определение допустимых и неуместных концепций единого языка в рамках границ ограниченного контекста является задачей нетривиальной и требует детального погружения в литературу. Я постараюсь вкратце осветить этот вопрос.

За каждым именованным элементом единого языка скрываются связанные с ним концепции. При попытке учесть их все, модель очень быстро превращается в большой ком грязи (big ball of mud). Что следует оставить внутри границ смыслового ядра, а что исключить, поместив за его пределами? Я рекомендую задавать себе следующий вопрос - является ли данная концепция неотъемлемой частью моделируемой предметной области? Если нет - смело выносите наружу. Скорее всего, эти внешние компоненты при их скоплении образуют свои ограниченные контексты, с которыми будет взаимодействовать ваше смысловое ядро.

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

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

Тактическое проектирование

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

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

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

Сущность моделирует индивидуальный объект, имеющий уникальную идентичность. Сущность может быть изменчива во времени.

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

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

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

Перечислю наиболее полезные правила проектирования агрегатов:

  1. Бизнес-инварианты должны обеспечиваться в рамках агрегата.

  2. Проектируйте небольшие агрегаты. Руководствуйтесь принципом единой ответственности (SRP, single responsibility principle).

  3. Ссылайтесь на другие агрегаты исключительно посредством идентификаторов. Уникальный идентификатор агрегата защищает его от изменений в рамках внешней транзакции, поскольку нет прямой ссылки на объект. Вторым плюсом способа обращения к агрегату по идентификатору является удобство хранения такого агрегата в различных хранилищах - от реляционной базы данных, до key-value и S3.

  4. Обновляйте другие агрегаты, руководствуясь принципом итоговой согласованности (eventual consistency). Когда транзакция агрегата завершается, ее состояние сохраняется вместе с генерацией события предметной области (domain event).

Отдельно стоит упомянуть события предметной области. Заинтересованный агрегат подписывается на получение событий предметной области, издаваемых другим агрегатом. Данные агрегаты могут располагаться как в одном ограниченном контексте, так и в разных. Имена событий предметной области должны быть утверждением о произошедшем, т.е. быть глаголом в прошедшем времени. Каждое такое имя должно ясно и кратко сообщать, что именно произошло. Например, событие ProductCreated информирует нас о том, что продукт был создан.

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

Инструменты проектирования

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

Слева направо: пользовательская роль (actor, светло-желтый), команда (command, синий), агрегат (aggregate, желтый), событие предметной области (domain event, оранжевый), процесс (process,  сиреневый)
Слева направо: пользовательская роль (actor, светло-желтый), команда (command, синий), агрегат (aggregate, желтый), событие предметной области (domain event, оранжевый), процесс (process, сиреневый)

Событийный штурм - это методика быстрого проектирования, предполагающая вовлечение разработчиков и экспертов предметной области в быстро меняющийся процесс изучения событий и бизнес-процессов.

Какие ключевые особенности можно выделить для событийного штурма?

  1. Событийный штурм можно организовать быстро и дешево - достаточно собрать в одном месте разработчиков и экспертов предметной области. Вам потребуются считанные часы вместо недель и месяцев для формирования смыслового ядра и единого языка.

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

  3. Это очень визуальный подход. Он не требуется предварительного кодирования гипотез. Все, что вам потребуется - разноцветные стикеры и стены для их размещения.

  4. Этот подход сосредоточен на бизнес-процессах, а не на технических деталях реализации.

  5. Идентификация проблем, связанных с моделью и знаниями о бизнес-процессах будет происходить на самых ранних этапах. Устраняя их, вы будете приходить к новым открытиям и точкам роста.

  6. Не стоит ограничиваться одним сеансом событийного штурма. Ровно, как и затягивать один сеанс больше, чем на несколько часов. Лучше повторять сеансы с интервалом в несколько дней. Фиксируйте достижения и возвращайтесь к ним на следующих сеансах событийного штурма. За несколько таких итераций вы усовершенствуете ваше смысловое ядро и достигнете глубочайшего понимания описанных в модели бизнес-процессов.

Что вам потребуется для событийного штурма?

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

  2. Запаситесь большим количеством стикеров разных цветов. Желательны: оранжевый (события), желтый (роли и агрегаты), синий (команды), сиреневый (процессы), красный (проблемы), розовый (ограниченный контекст) и зеленый (представления).

  3. Обеспечьте каждого участника черным маркером с тонким кончиком.

  4. Найдите широкую (порядка 10 метров) стену. Можно купить широкий рулон бумаги и закрепить его скотчем на стену. На бумаге стикеры держатся лучше.

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

  1. Для стикеров событий предметной области (domain events) наиболее распространенным цветом является оранжевый.

  2. Создание событий предметной области должно быть сосредоточено на бизнес-процессах, а не на данных и их структурах.

  3. Пишите на стикерах имена событий предметной области. Имя должно быть глаголом в прошедшем времени. Например, OrderCreated.

  4. Размещайте стикеры на вашей поверхности моделирования в хронологическом порядке слева направо.

  5. Одновременные события предметной области можно размещать одно под другим. Это подчеркнет, что их обработка происходит параллельно.

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

  7. Иногда результатом события предметной области является процесс, который необходимо запустить. Он может состоять из одного или нескольких шагов. Стикер процесса должен быть отдельного цвета (общепринят сиреневый). Проведите стрелку от события предметной области к процессу (от оранжевого стикера к сиреневому).

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

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

  1. Имя команды должно быть глаголом в повелительном наклонении. Например, CreateOrder.

  2. Наиболее распространенным цветом для стикеров команд является синий.

  3. Размещайте стикеры команд слева от стикеров событий предметной области. Они образуют пары команда/событие.

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

  5. Если существует определенная пользовательская роль (actor), которая выполняет действие, ее следует отразить в виде ярко-желтого стикера в левом нижнем углу стикера команды. На ярко-желтом стикере роли следует изобразить схематичную фигурку человека и название роли. Например, фигурка может символизировать роль "Покупатель продукта", который выполняет команду.

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

  7. В процессе создания команд у вас могут возникнуть новые события предметной области.

  8. Одна команда может вызывать несколько событий предметной области. Разместите эту команду слева от событий предметной области, которые она вызывает.

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

  1. Предпочтительный цвет стикера агрегата - желтый.

  2. Агрегат должен именоваться именем существительным, ясно отражающим соответствующую концепцию.

  3. Стикер агрегата следует располагать немного позади и выше пары стикеров команда и событие предметной области.

  4. Если агрегаты встречаются на оси времени неоднократно, просто продублируйте их. Помните, что главное - моделирование бизнес-процесса, а оно протекает во времени.

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

В процессе событийного штурма, скорее всего, вы обнаружите многочисленные модели и потоки событий предметной области между ними. Приведу некоторые рекомендации по работе с ними:

  1. Границы ограниченных контекстов удобно выделять сплошной линией маркера, а подобласти внутри - пунктиром.

  2. Именовать ограниченные контексты рекомендуется с помощью розовых стикеров. Например, "Отдел продаж" или "Бухгалтерия".

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

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

Заключение

В этой статье мы обсудили предметно-ориентированный подход к проектированию и методы выделения границ. Статья не претендует на полноту изложения и призвана улучшить навыки разделения систем на отдельные компоненты.

Для углубления в тему Domain-Driven Design рекомендую следующие книги:

  1. Vaughn Vernon, Domain-Driven Design Distilled, Addison-Wesley Professional, 2016 (ISBN 0134434420)

  2. Vaughn Vernon, Implementing Domain-Driven Design, Addison-Wesley Professional, 2013 (ISBN 9780321834577)

  3. Eric Evans, Domain-Driven Design: Tackling Complexity in the Heart of Software, Addison-Wesley Professional, 2003 (ISBN 0321125215)

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

Комментарии (2)


  1. nronnie
    04.07.2023 17:22

    Всё очень хорошо и толково написано, но вот это я не совсем понял:


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

    Возможно, имелось в виду "в рамках одной транзакции базы данных"? Потому что избежать необходимости обновления нескольких агрегатов в рамках одной бизнес-транзации ну никак, по-моему, не выйдет.


    1. solonkov Автор
      04.07.2023 17:22

      Большое спасибо за отличный вопрос!
      Нет, здесь не имеется в виду транзакция базы данных. Транзакция базы данных - это уровень реализации. Агрегат определяет границы согласованности транзакций. Когда транзакция уровня агрегата передается в базу данных для сохранения, все составные части в пределах этого агрегата должны быть непротиворечивыми и соотвествовать бизнес-правилам. Под транзакцией здесь подразумевается способ изоляции изменений агрегата и поддержки бизнес-инвариантов. Бизнес-инварианты должны поддерживаться с целью обеспечения согласованности последующих (во времени) бизнес-операций. Не имеет значения, как будет осуществлен контроль этого требования - с помощью атомарной транзакции базы данных или других средств, - состояние агрегата должно быть безопасным, гарантирующим правильные переходы состояний.
      Возвращаясь к вопросу о необходимости обновления нескольких агрегатов в рамках одной "бизнес-транзакции". Здесь скорее речь об итоговой согласованности. Методы ее достижения отличаются для разных типов интеграции. Я приведу пример для интеграции с рассылкой сообщений (другие варианты подробно описаны в книге Implementing Domain-Driven Design в рамках связывания контекстов). Обеспечить итоговую согласованность в случае асинхронной передачи данных можно посредством публикации событий предметной области. Агрегат после изменения своего внутреннего состояния публикует событие, которое получают и обрабатывают агрегаты-потребители. Цепочка этих действий должна приводить к итоговой согласованности в системе. Разумеется, доставка события в таком случае должна быть гарантированной.
      В заключении хочу подчеркнуть мысль о том, что явного запрета на одновременное обновление нескольких агрегатов здесь не предполагается. Важно обеспечить два типа согласованности: транзакционную согласованность агрегата и итоговую согласованность. Если мы хотим выполнить одновременное обновление нескольких агрегатов для достижения итоговой согласованности, то важно убедиться, что внутренняя (транзакционная) согласованность каждого из них не будет нарушена. Каким образом будет обеспечен тот или иной уровень согласованности - вопрос реализации.