К списку постов

Нотация описания архитектуры приложения

Мотивация

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

Понятно, что на словах это не объяснишь и приходится прибегать к дополнительным инструментам в виде доски и маркера или же при текущих реалиях - online доски типа Miro. Но даже в этом случае обсуждение сваливается в хаотично нарисованные квадратики, круги или прямоугольники с текстом, как-то соединённые стрелками.

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

А что есть?

Осознав проблемы я первым делом начал искать готовые варианты. Вот несколько из них:

  • Диаграмма классов - иногда бывает удобной для анализа уже существующего кода, но не применима для изначального проектирования, где мы оперируем более верхне уровневыми объектами.
  • c4model - уже ближе к теме и предлагает 4 уровня детализации схемы программного обеспечения. Но часто из всех 4-х уровней подходит 3-й, когда мы планируем новый сервис, а на 3-м уровне стандартов описания немного. Отлично подходит для верхнего уровня описания системы, но при детализации на компоненты не имеет детальных стандартов.

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

  • Разбить создание его на задачи.
  • Проверить корректность деления на модули, чтобы сократить их связанность.
  • Упростить коммуникацию в команде при обсуждении деталей проектирования приложения.
  • Использовать её для документирования архитектуры как монолита, так и микросервисов.

Требования к нотации

Перед тем, как переходить к описанию предлагаемой нотации следует сформулировать требования, которым она должна удовлетворять:

  • Она должна на верхнем уровне описывать отделимые части системы, которые далее я буду назвать «компоненты».
  • Связи между ними должны описать зависимости и поток вызовов, но не быть слишком усложнены. По связям должно быть сразу понятно наличии архитектурных ошибок.
  • Схема должна подходить как для описания монолитного приложения, так и микросервисной архитектору.
  • Фокус должен быть на описании нашей архитектуры, максимально абстрагировавшись от внешних систем.

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

Структура диаграммы

В первую очередь давайте перечислим все возможные блоки, которые могут использоваться в диаграмме:

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

Компоненты

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

Это может быть:

  • Контроллер, которые обрабатывает входящие запросы.
  • Сервис, отвечающий за бизнес логику работы с платежами.
  • Репозиторий, взаимодействующий с базой данных.
  • Обработчик event событий при использовании event sourcing.
  • Бизнес entity пользователя, содержащие поля для него и методы работы.

Фактически все, что вы можете выделить в виде класса с инкапсулированной логикой - это компонент.

Для того чтобы максимально полно описать компонент можно указать следующие параметры:

  • Имя - название компонента, отражающие его суть или даже название класса
  • Тип - чтобы понять что это контроллер или репозиторий
  • Пояснение - если требуется дополнительное описание.

Примеры:

Компоненты

Внутренние связи

Неразрывной стрелкой показываются связи между компонентами системы. При этом, направление стрелки указывает направление зависимости (вызова методов). Если комплект UserContoller требует вызова метода из UserService:

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

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

На стрелке можно дополнительно указать название метода, который будет вызываться, если это важно:

Внутренние связи

Внешние связи

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

В описании можно дать:

  • Название команды
  • Тип запроса
  • Название внешнего сервиса.

Примеры:

Внешние связи

Модули

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

Например, репозиторий работы с пользованием, entity пользователя и контроллер, который обрабатывает запросы на добавления пользователя в базу.

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

Модульности может меняться от одного архитектурного подхода к другому. Например, для Nest.js или Angular уже есть такое понятие как модуль, а для React модулем может выступать папка, в которой группированы те или иные компоненты. Для С# это может быть Namespace или логически выделенный кусок приложения.

Модули имеют лишь название и группируют внутри себя компоненты отображая свои границы:

Модули

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

Приложения

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

Приложения

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

Передаваемые объекты

При передаче данных между компонентами системы, может возникнуть необходимость описать их структуру. Обычно такие объекты носят название DTO (data transfer object). Они обозначаются прямоугольниками с закруглёнными на 50% углами. Можно добавить не только название DTO, но и его тип: event, query, command.

Передаваемые объекты

Детализация

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

Так же, если в рамках вашей архитектуры вам важно детально описать поля базы данных, или какой-либо объекта вы тоже можете воспользоваться детализацией.

При этом если для вашего обсуждения детализация не нужна, не тратьте на неё время.

Детализация

Пример

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

У нас есть API, в которое приходят запросы по кампаниями и подписчикам. Мы планируем реализовать микросервисную архитектуру с централизованным API.

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

Пример

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

Использование

Вы можете использовать эту нотацию в любом удобном для вас инструменте, например draw.io или visio. Но для удобства я подготовил для вас готовую библиотеку в Figma, которая позволит быстро рисовать диаграммы, добавляя компоненты из библиотеки. Вы можете скачать её тут. После открытия ссылки нажмите на Duplicate и получите готовую библиотеку у себя в figma. Перейдя на вкладку Assets вы получите готовые компоненты, которые можете перетаскивать и изменять по своему желанию.

Так же в этом файле есть описанный выше пример, составленный из компонент, где вы можете рассмотреть его подробнее.

Надеюсь эта нотация пригодиться и вам!