Визуализация архитектуры с помощью C4 модели и PlantUML
Руководитель Python практики в Usetech
Приветствую, коллеги. Меня зовут Мстислав, я являюсь руководителем Python практики в Usetech. В этой статье я раскрою нюансы работы с PlantUML и C4 моделью. Вся информация является выжимкой из нашего ежедневного опыта. Также необходимо упомянуть, что на эту тему я проводил воркшоп на TechLeadConf 2022, так что при желании, вы всегда можете найти видео.
Какие проблемы мы решаем?
Если в команде нет принятых стандартов по отрисовке диаграмм, то при необходимости изобразить графически те или иные нюансы системы, происходит отрисовка диаграммы ad hoc, без учёта каких либо стандартов оформления, общепринятых нотаций.
В итоге в течении жизни проекта, у нас копятся изображения с различными фигурами, с различными стрелочками, всё это подкрашивается различными цветами, ну и в качестве вишенки на торте — легенды ко всем этим художествам, в подавляющем большинстве случаев, нет.
Давайте чётко сформулируем вытекающие из этого проблемы:
Связи между элементами часто неоднозначны;
Значения и предназначения элементов неоднозначны. При обсуждении архитектуры вы, вероятно, использовали слова типа компонент, модуль, подсистема, система и каждый с высокой вероятностью подразумевал под этим что-то своё;
Слишком много или наоборот слишком мало деталей в вашей диаграмме;
Уровни абстракции смешиваются;
Часто не предоставляется никакого контекста.
Эти проблемы приводят к тому, что понимание диаграмм есть только у людей которые были вовлечены в их отрисовку, да и то понимание существует довольно короткий промежуток времени, потому что из-за отсутствия стандарта и легенды запомнить, что ты имел в виду месяц назад под красным квадратиком не всегда возможно.
Помимо этого, модификация отрисованных диаграмм, которые обычно сохраняются в виде изображений, фактически невозможна из-за отсутствия исходников или, даже если они есть, то постоянной смены инструментов.
Как поможет C4 и PlantUML?
С4 предлагает нам заранее обдуманное и очень ограниченное количество абстракций. Это позволит всей команде пользоваться едиными абстракциями, а за счёт их небольшого количества снижать когнитивную нагрузку. В этой статье я рассмотрю не все абстракции, а только границы, системы, контейнеры, персоны и связи.
Чем же нам поможет PlantUML? Используя его вам не придётся задумываться о том, как оформить вашу диаграмму. Всё-таки мы не художники, а инженеры.
Не нужно иметь тяжёлых, сложных инструментов для отрисовки — много сред разработки уже поддерживает PlantUML, т.е. скорее всего вам как разработчику даже не придётся устанавливать и осваивать новый инструмент. Помимо этого существует много онлайн-редакторов, да и в крайнем случае всегда можно поднять PlantUML сервер у себя внутри компании или на компьютере с помощью одной команды и докера.
Большим плюсом PlantUML является то, что это plain text, а следовательно версионирование ваших диаграмм происходит с помощью Git или любой другой системы контроля версий.
Что такое архитектура?
Чтобы быть на одной волне, я хочу пояснить, как я трактую понятие архитектуры в этой статье.
Прежде всего, это важные штуки. Вы должны понимать, что такое важные штуки и убедить людей вокруг себя, что они важны. Помимо этого, в будущем их будет сложно изменить, а потому приняты они должны быть на ранних этапах разработки решения.
Говоря об архитектуре, стоит отметить, что как архитектор вы должны хорошо уметь структурировать информацию. Например, сделать из 40 страниц документации одну простую модель.
Что такое PlantUML?
PlantUML — это язык, инструмент, позволяющий создавать диаграммы с помощью текстового описания. Рендерить это текстовое описание можно большим количеством инструментов.
Преимущества PlantUML:
Как я писал ранее, PlantUML облегчает поддержку диаграмм — это простой, бесплатный инструмент, опенсорсный и кроссплатформенный.
PlantUML имеет низкий порог входа и это позволяет всем членам команды, например, аналитикам, разработчикам, тестировщикам рисовать диаграммы последовательности, диаграммы активности и прочие широко известные диаграммы. Вся команда при помощи единого инструмента в едином оформлении принимает участие в отрисовке, не тратя время на лишние споры и обсуждения.
Внедрение PlantUML позволяет посмотреть эволюцию того или иного архитектурного решения т.е. версионирование, потому что PlantUML, как и говорил ранее, это банальный PlainText документ.
Внедрение PlantUML позволяет рендерить PlantUML документ не прибегая к дополнительному шагу в виде экспорта изображения в Confluence, Gitlab, Sphinx, потому что большинство подобных систем поддерживают рендеринг PlantUML. При минимальной настройке этих систем. Это предотвращает расхождение изображений и PlantUML документа.
Теперь давайте поговорим про С4.
С4 модель
C4 модель разработал Саймон Браун, вдохновившись моделью представления архитектуры 4+1 и UML. Он отмечал, что нотация вторична, первичны абстракции. Неважно, какие изображения вы рисуете, главное, чтобы вы оперировали едиными абстракциями.
Сам Саймон Браун предложил 4 уровня абстракции: системы, контейнеры, компоненты и код. Я предлагаю рассмотреть только уровень систем и контейнеров.
Важно пояснить, что название «контейнеры» появилось до того, как Docker стал популярен. Так что когда мы говорим «контейнеры», мы не имеем в виду докер, мы имеем в виду некую абстракцию, за которой может скрываться база данных, шина данных, скрипт. То есть что-то, составляющее часть системы. На самом деле, контейнером может быть и докер контейнер.
Также важно понимать, что C4 прямо не связан с PlantUML. PlantUML — это лишь инструмент, который позволяет нам рисовать, в том числе и C4 диаграммы.
Оперируя предложенными абстракциями и оформлением вы можете даже при помощи Paint, но тогда теряются плюсы plain text.
C4 модель: Системы
Давайте поговорим про системы в С4.
Если рассматривать диаграмму, которая состоит исключительно из систем и персон, то это позволит увидеть систему в меньшем масштабе и как она вписывается в среду вокруг неё.
Диаграмма на уровне систем отвечает на вопросы:
Какую систему мы разрабатываем?
Кто использует систему?
Как она вписывается в текущую среду?
Основной фокус на этом уровне должен быть на акторах и на системах, а не на технологиях, протоколах и прочих низкоуровневых деталях.
Диаграмма на уровне систем позволяет:
Увидеть какая система добавляется в текущую среду;
Без опасений непонимания показывать её коллегам с меньшим уровнем технического погружения;
Понять, с кем вам вероятно нужно пойти переговорить для того, чтобы договориться о межсистемных взаимодействиях.
На изображении ниже представлена диаграмма. Давайте посмотрим на неё и представим, что мы разрабатывали систему, которая:
Имеет веб интерфейс со списком сообщений и возможностью отправки ответа на них;
Эта система умеет публиковать отправленные ответы в Одноклассники и ВКонтакте.
На текущий момент мы видим, что есть всего одна персона, которая работает с системой — это оператор. Серым цветом отмечены внешние системы, голубым — наша система.
Однако система публикаций разработана так хорошо, что к ней хочет подключиться, допустим, AMO CRM. На текущий момент она умеет публиковать только ответы на email, но хочет уметь публиковать ответы в ОК и ВК. Давайте посмотрим, как она выглядит сейчас, нарисуем её рядом с нашей системой (на изображении ниже).
Добавился второй актор — это оператор AMO CRM. Теперь давайте свяжем нашу систему и AMO CRM.
В итоге мы видим довольно очевидные вещи, но это может натолкнуть на полезные мысли. К примеру, руководитель нашего проекта, посмотрев на диаграмму, может понять, что мы можем попробовать забрать у них отправку сообщений на email, если научим нашу систему обработки сообщений с ним работать.
Пойдём дальше и передадим эту диаграмму архитекторам, разработчикам, чтобы они углубились в детали. Но предварительно давайте поговорим о контейнерах и связях/взаимодействиях между ним.
C4 модель: Контейнеры
Контейнеры представляют собой некое приложение, хранилище данных. Под хранилищем данных вы можете подразумевать файл, файловую систему или базу данных и т.д.
Контейнеру принято:
Давать понятное название;
Указывать общий список используемых технологий. К примеру для разрабатываемого вами ПО вы можете указать название, версию языка и основного фреймворка. Для СУБД вы можете просто указать название СУБД и её версию, например, Postgres 13;
Необходимо давать краткое описание контейнера и описывать его основную ответственность.
Диаграмма контейнеров позволяет ответить на следующие вопросы:
Какие технологии в системе используются?
Как распределены задачи в системе?
Как и какие контейнеры взаимодействуют друг с другом?
Для взаимодействий принято указывать:
Цель взаимодействия — пишет, читает, отправляет, создаёт, получает — всё это зависит от контекста;
Протокол, механизм взаимодействий: HTTP, AMQP, Kafka, REST API, SOAP и т.д.
При необходимости стиль коммуникации: синхронный, асинхронный, пакетный, т.е. когда мы сразу отправляем большое кол-во объектов;
В каких-то ситуациях можно указать порт, если это кажется важным.
Не совсем очевидный нюанс — это направление связи, т.е. в какую сторону вы направляете стрелочку. Тут многие путаются, потому что, к примеру, в HTTP протоколе каждый реквест подразумевает респонс, а значит связь должна быть двунаправленной, но это не так.
Нас интересует вопрос: кто является инициатором запроса, а кто получателем? Поэтому стрелочка идёт от того, кто посылает запрос.
Итак, вернёмся к проектированию. Ранее мы хотели передать диаграмму архитекторам, разработчикам, чтобы они продумали детали.
Что им необходимо будет сделать? Во-первых, раскрыть детали системы обработки сообщений. Во-вторых, добавить технических нюансов в связь между AMO CRM и системы обработки сообщений.
Технические нюансы обсуждаемой системы
Вкратце расскажу технические нюансы системы:
Все сообщения, требующие публикации, сохраняются в Kafka;
Для каждой соц. сети свой топик;
Из соответствующих топиков читают контейнеры занимающиеся публикацией;
Так как у нас отсутствует внешний API — обратите внимание на связь между оператором и формой обратной связи. Там указано, что используется внутренний API, а проектировать внешний API пока рано (т.к. мы решаем одну конкретную задачу и в целом экспериментируем). Делаем PoC для интеграции с AMO CRM максимально просто: создаём адаптер для запросов из AMO CRM, конвертируем входные данные в нужный нам формат и просто пишем в соответствующий кафка топик;
Мы раскрыли нюансы ОК и ВК. К примеру, в ОК мы работаем не через официальный API, а через мобильный веб-интерфейс, то и выбор технологий в контейнере публикаций ответов в ОК становится понятным;
Мы указали по какому протоколу происходит взаимодействие между AMO CRM и системой обработки сообщений.
Теперь перейдём к нюансам самой диаграммы:
Первое — появились границы. В частности мы видим границы (пунктирные линии) у трёх систем — система обработки сообщений, ОК и ВК. Таким образом мы показываем, что углубились в систему, т.е. увеличили масштаб;
У контейнеров внутри системы обработки сообщений появилась информация о выбранных технологиях;
У связей между контейнерами появились протоколы и нюансы взаимодействия (пример с внутренним REST API);
Некоторые контейнеры внутри системы обработки сообщений я отметил серым, т.е. якобы это внешние контейнеры. Я сделал это для того, чтобы при первом взгляде было понятно, какой контейнер добавляется, какие контейнеры изменяются, а какие остаются нетронутыми. В нашем случае добавляется лишь один контейнер — это адаптер.
Теперь предлагаю разобрать исходный код этой диаграммы, который доступен по ссылке.
C4 + PlantUML: Основы
Начнём с первой и последней строки. Так начинается и заканчивается любая PlantUML диаграмма.
Ранее я говорил, что PlantUML прямо не связан с С4, а лишь позволяет писать код, который рендерится в C4 диаграмму. Но на самом деле я немного слукавил. Из коробки мы не нарисуем C4 диаграмму, но подключив файл, в котором определены процедуры, отрисовывающие соответствующие элементы, мы сможем их использовать для отрисовки C4 диаграммы. Подключение файла видно на второй строке. Подключать вы можете и локальные файлы, т.е. доступ в интернет для отрисовки C4 диаграмм не обязателен.
Посмотрите на строки с 8 по 11. Я считаю необходимым давать диаграмме алиас, версионировать диаграмму с помощью calver и давать человеческое название или краткое описание. Это помогает в коммуникациях, при организации базы знаний: у вас появляется возможность сослаться на более или менее уникальное имя диаграммы (алиас).
Разберём границы, системы, и внешние системы.
Как создать границу вы можете увидеть с 13 по 15 строки. Вызывается процедура System_Boundary, первым параметром передаётся алиас, вторым человекочитаемое название границы. После фигурной скобки идёт определение контейнеров или систем внутри этой границы. На всякий случай напомню, что границы — это пунктирные линии;
На строке 14 вы видите вызов процедуры System_External. Параметры те же, что и процедуры System — алиас, имя. В итоге вы видите серый прямоугольник, который подразумевает под собой внешнюю систему. Чтобы определить не внешнюю систему, т.е. чтобы она была синей, просто удалите постфикс _Ext.
Теперь поговорим о контейнерах. Контейнеры тоже могут быть внешними — тут всё просто — аналогичный постфикс »_Ext». Контейнеры могут изображать БД, Очередь сообщений (посмотрите на 26 строку — кафка изображена в виде очереди), а могут быть просто прямоугольниками (например, 24 строка).
Процедура Container (и ей подобные) принимают в себя следующие параметры: алиас, человекочитаемое имя, технологии, описание контейнера.Связи. Связи определяются процедурой Rel. Она принимает в себя инициатора, получателя, описание связи, протокол. Посмотрите на 30 строку.
Связи внутри границ системы определяются внутри границ. Межсистемные связи определяются вне границ. Например, посмотрите на строку 40. Вы могли заметить, что я не указал протокол. Это потому, что я крупными мазками изобразил взаимодействие внешних систем и мне не нужно знать по какому протоколу происходит взаимодействие.
C4 + PlantUML: наводим красоту
Теперь поговорим о большой боли: попытке сделать диаграммы более или менее презентабельными. Почему это боль? Потому что при наличии относительно большого количества контейнеров, систем и связей, позиционирование элементов может быть довольно неочевидным.
Начнём с увеличения допустимого размера текста, который будет отображаться без переноса. Для этого добавим строку `skinparam wrapWidth 300`. Разницу вы можете увидеть на изображении в пункте 1.
Следующим шагом предлагаю включить легенду, которая по умолчанию будет отображаться в правом нижнем углу. Это позволит убрать лишний текст типа `container` из отрисованных элементов, тем самым мы сэкономим место на диаграмме.
На изображении вы можете видеть, что используется процедура Rel_U вместо Rel, но она принимает те же параметры, что и Rel.
U означает Up. Аналогично существуют постфиксы L, R и D (lleft, right и down). В нашем случае Rel Up говорит, что связь между AMO CRM и адаптером AMO CRM должна происходить сверху вниз.
На диаграмме слева вы видите использование Rel_Up и AMO CRM (находится сверху), а справа вы видите использование обычной процедуры Rel. Без Rel Up диаграмма стала длиннее.
К сожалению, подобное позиционирование срабатывает не всегда. Если не сработало с одним контейнером или системой, пробуйте позиционировать другие. Ищите оптимальные варианты. Изменение буквально одной буквы в одной связи может полностью перевернуть вашу диаграмму как в хорошем, так и в плохом смысле.
Также есть процедуры для изменения направления отрисовки. Познакомимся с ними.
Слева вы видите диаграмму после вызова процедуры LAYOUT_LANDSCAPE () т.е. отрисовка слева направо.
Справа вы видите диаграмму после вызова процедуры LAYOUT_TOP_DOWN (), т.е. отрисовка сверху вниз. Отрисовка сверху вниз включена по умолчанию.
То, что вы видите, не означает, что отрисовка слева направо всегда лучше. Просто для текущей диаграммы она лучше подходит.
Нельзя не упомянуть форму связей. Ранее вы видели форму связей по умолчанию: в этом случае формы связей могут закругляться при необходимости.
В случае установки типа линий ortho, связи будут максимально прямыми с минимальным количеством углов.
На первый взгляд выглядит довольно неплохо, но я замечал, что при этом стиле связей положение описаний становится неочевидным и иной раз сложно понять, к какой связи относится то или иное описание.
Тип линий polyline — это ломаная. С ломаной намного меньше проблем с положением описания связей.
Важное уточнение: я специально использую отрисовку сверху вниз, т.к. она для данной диаграммы выглядит хуже, а на плохих кейсах намного более заметны различия в стиле стрелок.
На этом я бы хотел закончить про красоту, Однако существует ещё много возможностей по изменению легенды, перекрашиванию связей, контейнеров, добавлению иконок для контейнеров и так далее, но это не слишком критично.
Также имейте в виду, что под капотом PlantUML используется пакет утилит для визуализации графов — graphviz и различные настройки отображения вы можете узнать, почитав документацию именно к этому пакету.
Подводя итог, можно сказать, что это большая часть из того, что может понадобиться вам в работе. Конечно, существует ещё довольно много нюансов и возможностей, но даже с уже имеющимися знаниями вы можете покрыть большинство своих задач.
Спасибо за ваше внимание!