Автор:Мстислав Казаков, руководитель практики Python в Usetech.

В этой статье я объясню нюансы работы с PlantUML и моделью C4. Вся информация является квинтэссенцией нашего повседневного опыта.

Какие проблемы мы решаем?

Если в коллективе нет принятых стандартов рисования диаграмм, то когда необходимо графически изобразить те или иные нюансы системы. Схемы рисуем ad hoc, без учета каких-либо норм проектирования, общепринятых обозначений.

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

Четко сформулируем возникшие проблемы:

1. Связи между элементами часто неоднозначны;
2. Значения и цели элементов неоднозначны. При обсуждении архитектуры вы, вероятно, использовали такие слова, как компонент, модуль, подсистема, система, и каждый, вероятно, имел в виду что-то свое;
3. Слишком много или слишком мало деталей на вашей диаграмме;
4. Перепутаны уровни абстракции;
5. Часто контекст не предоставляется.

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

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

Чем помогают C4 и PlantUML?

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

Так как же нам помогает PlantUML? С ним вам не нужно думать о том, как спроектировать диаграмму. В конце концов, мы инженеры, а не художники.

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

Большим плюсом PlantUML является то, что это обычный текст, поэтому управление версиями ваших диаграмм осуществляется с помощью Git или любой другой системы контроля версий.

Что такое архитектура?

Чтобы быть на той же странице, я хочу уточнить, как я интерпретирую концепцию архитектуры в этой статье.

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

Говоря об архитектуре, стоит отметить, что как архитектор вы должны уметь структурировать информацию. Например, из 40 страниц документации сделать одну простую модель.

Что такое PlantUML?

PlantUML — это язык, инструмент, позволяющий создавать диаграммы с использованием текстового описания. Это текстовое описание может быть отображено многими инструментами.

Преимущества PlantUML

Как я уже писал ранее, PlantUML упрощает поддержку диаграмм — это простой бесплатный инструмент с открытым исходным кодом и кроссплатформенный.

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

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

PlantUML позволяет визуализировать документ PlantUML без необходимости выполнять дополнительный этап экспорта изображения в Confluence, GitLab, Sphinx, поскольку большинство систем поддерживают визуализацию PlantUML с минимальной настройкой. Это предотвращает несоответствие между изображениями в вашей базе знаний и исходным кодом PlantUML в вашем репозитории.

Теперь поговорим о C4.

Модель C4

Модель C4 была разработана Саймоном Брауном на основе модели представления архитектуры 4+1 и UML. Он указывал, что обозначения вторичны, а абстракции первичны. Неважно, какие образы вы рисуете, главное, чтобы вы оперировали одними и теми же абстракциями.

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

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

Также важно понимать, что C4 не имеет прямого отношения к PlantUML. PlantUML — это просто инструмент, который позволяет нам создавать диаграммы C4.

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

Модель C4: системы

Давайте поговорим о системах в C4.

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

Схема системного уровня отвечает на следующие вопросы:

1. Какую систему мы разрабатываем?
2. Кто использует систему?
3. Как работает это вписывается в текущую среду?

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

Диаграмма системного уровня позволяет:

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

Изображение ниже представляет собой схему. Давайте посмотрим на это и представим, что мы разрабатываем систему, которая:

1. Имеет веб-интерфейс со списком сообщений и возможностью отправки ответа на них;
2. Данная система способна публиковать отправленные ответы в Facebook и Twitter;
3. На данный момент мы видим, что с системой работает только один человек — оператор. Серым цветом обозначены внешние системы, синим — наша система.

Модель C4: системы №2

Однако система публикации настолько хорошо спроектирована, что некоторые внешние системы, такие как, например, AMO CRM, хотят подключиться к нам и использовать в качестве системы доставки сообщений. На данный момент AMO CRM знает только, как публиковать ответы на электронную почту, но хочет иметь возможность публиковать ответы в Facebook и Twitter. Давайте посмотрим, как это выглядит сейчас, нарисуем его рядом с нашей системой (на изображении ниже).

Модель C4: системы №3

Добавлен второй актор — оператор AMO CRM. Теперь подключим нашу систему и AMO CRM.

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

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

Модель C4: контейнеры

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

1. Дайте понятное имя;
2. Укажите общий список используемых технологий. Например, для разрабатываемого вами программного обеспечения вы можете указать имя, языковую версию и базовый фреймворк;
3. Вы должны дать краткое описание контейнера и описать его основная ответственность.

Контейнерная диаграмма позволяет ответить на следующие вопросы:

1. Какая технология используется в системе?
2. Как распределяются обязанности в системе?
3. Как и какие контейнеры взаимодействуют друг с другом?

Обычно для отношений между элементами указывается:

1. Цель общения — пишет, читает, отправляет, создает, получает — все зависит от контекста;
2. Протокол, механизм коммуникации: HTTP, AMQP, Kafka, REST API, SOAP и т.д.
3. Стиль коммуникации при необходимости: синхронный, асинхронный, пакетный, т.е. когда мы отправляем сразу много объектов;

В некоторых ситуациях мы можем указать порт, если это кажется важным.

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

Нас интересует вопрос: кто инициатор запроса, а кто получатель? Так что стрелка идет от того, кто отправляет запрос.

Итак, вернемся к дизайну. Раньше мы хотели отдать схему архитекторам, разработчикам, чтобы они продумали детали.

Что им нужно будет сделать? Во-первых, раскрыть детали системы обработки сообщений. Во-вторых, добавьте технические нюансы во взаимоотношения между AMO CRM и системой обработки сообщений.

Технические нюансы обсуждаемой системы

Кратко расскажу о технических нюансах системы:

— Все посты, которые необходимо опубликовать, сохраняются в Kafka;

— Для каждой социальной сети есть своя тема;

— Из соответствующих тем читаются контейнеры, занимается публикацией;

— Так как у нас нет внешнего API — обратите внимание на связь между оператором и формой обратной связи. Там написано, что используется внутренний API, а внешний API проектировать пока рано (потому что решаем одну конкретную задачу и вообще экспериментируем). Делаем PoC для интеграции с AMO CRM максимально простым: создаем адаптер для запросов от AMO CRM, конвертируем входные данные в нужный нам формат и просто пишем в соответствующий топик Kafka;

— Раскрыли нюансы Facebook и Twitter;

— Мы указали, какой протокол используется для взаимодействия между AMO CRM и системой обработки сообщений.

Модель C4: контейнеры №2

Теперь перейдем к нюансам самой диаграммы:

— Во-первых — границы появились. В частности, мы видим границы (штриховые линии) в 3-х системах — системе обработки сообщений, Facebook и Twitter. Таким образом, мы показываем, что углубились в систему, т.е. увеличили масштаб;

— Контейнеры в системе обработки сообщений содержат информацию о выбранных технологиях;

— У ссылок между контейнерами появились протоколы и нюансы взаимодействия (пример с внутренним REST API);

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

Теперь предлагаю разобрать исходный код этой схемы

C4 + PlantUML: основы

1 @startuml
2 !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
3
4 skinparam wrapWidth 300
5 LAYOUT_WITH_LEGEND()
6 LAYOUT_LANDSCAPE()
7 
8 title
9 <b>FeedbackPublicationArch-simple v2022.11.02</b>
10<i>Feedback system: publication</i>
11 end title
12 
13 System_Boundary(facebook, "Facebook") {
14   System_Ext(facebook_api, "Internal API")
15 }
16 
17 System_Boundary(twitter, "Twitter") {
18   System_Ext(twitter_api, "Mobile Interface")
19 }
20 
21 Person(operator, "Operator")
22 
23 System_Boundary(message_processing_system, "Message processing system") {
24  Container(amo_adapter, "AMO CRM Adapter", "Python 3.10, FastAPI", "Receive data for publication from AMO CRM and push it into message queue")
25  Container(feedback, "Feedback system form", "Python 3.8, Django 3, React", "Create responses, push responses into message queue")
26  SystemQueue(response_queue, "Kafka", "Publication responses")
27  Container(fb_publsher, "FB Publisher", "Python", "Publish responses on Facebook")
28  Container(tw_publsher, "TW Publisher", "Java, Selenium", "Publish responses on Twitter")
29 
30  Rel(feedback, response_queue, "Produce", "kafka")
31  Rel(amo_adapter, response_queue, "Produce", "kafka")
32  Rel(fb_publsher, response_queue, "Consume", "kafka")
33  Rel(tw_publsher, response_queue, "Consume", "kafka")
34  Rel(operator, feedback, "Response to publication", "Internal REST API")
35 }
36 
37 System_Ext(amo, "AMO CRM", "Browse income messages, send responses")
38 System_Ext(email, "Email")
39 Person_Ext(amo_operator, "AMO CRM operator")
40 
41 Rel(amo_operator, amo, "Response to income messages")
42 Rel(amo, email, "Send response to email")
43 Rel_U(amo, amo_adapter, "Send response on Facebook and Twitter")
44 
45 Rel_L(fb_publsher, facebook_api, "Publication", "HTTP")
46 Rel_L(tw_publsher, twitter_api, "Publication", "HTTP")
47 @enduml

1. Начнем с первой и последней строки. Так начинается и заканчивается любая диаграмма PlantUML.

2. Ранее я писал, что PlantUML не имеет прямого отношения к C4, а только позволяет писать код, который отображается в диаграмме C4. Но на самом деле я немного соврал. Из коробки мы не будем рисовать диаграмму C4, но, подключив файл, определяющий процедуры, отрисовывающие соответствующие элементы, мы можем использовать их для рендеринга диаграммы C4. Вы можете увидеть подключение файла во второй строке. Также можно подключить локальные файлы, т.е. для рисования диаграмм С4 доступ в интернет не нужен.

3. Посмотрите на строки с 8 по 11. Я считаю необходимым дать диаграмме псевдоним, версию диаграммы с помощью CalVer и дать человеческое имя или краткое описание. Это помогает в общении, в организации базы знаний: вы можете ссылаться на более или менее уникальное имя диаграммы (псевдоним).

4. Давайте разберем границы, системы и внешние системы.

4.1. Вы можете увидеть, как создать границу со строк 13 по 15. Вызывается процедура System_Boundary, первый параметр — псевдоним, второй — понятное человеку имя границы. После фигурных скобок у нас есть определение контейнеров или систем внутри границы. На всякий случай напомню, что границы — пунктирные линии;

4.2. В строке 14 вы видите вызов процедуры System_Ext. Параметры те же, что и у процедуры System — псевдоним, имя. В результате вы видите серый прямоугольник, который подразумевает внешнюю систему. Чтобы определить не внешнюю систему, т.е. сделать ее синей, достаточно удалить постфикс _Ext.

5. Теперь поговорим о контейнерах. Контейнеры тоже могут быть внешними — тут все просто — аналог постфикса «_Ext». Контейнеры могут представлять базу данных, очередь сообщений (посмотрите на строку 26 — Kafka изображена в виде очереди) или они могут быть просто прямоугольниками (например, строка 24).

Процедура Контейнер (и подобные) принимает следующие параметры: псевдоним, понятное человеку имя, технология, описание контейнера.

6. Отношения. Ссылки определяются процедурой Rel. Он принимает инициатора, получателя, описание ссылки, протокол. Посмотрите на строку 30.

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

C4 + PlantUML: сделать его презентабельным

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

1. skinparam wrapWidth 300

Начнем с увеличения допустимого размера текста, который будет отображаться без переноса. Для этого добавьте строку `skinparam wrapWidth 300`. Вы можете увидеть разницу на изображении в точке 1.

2. LAYOUT_WITH_LEGEND()

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

C4 + PlantUML: сделать его презентабельным #2

На изображении видно, что вместо Rel используется процедура Rel_U, но она принимает те же параметры, что и Rel.

Rel_U(amo, amo_adapter, «Отправить ответ на Facebook и Twitter»)

U означает вверх. Точно так же есть постфиксы L, R и D (левый, правый и нижний). В нашем случае Rel Up говорит, что обмен данными между AMO CRM и адаптером AMO CRM должен осуществляться сверху вниз.

На первой диаграмме вы видите использование Rel_Up и AMO CRM (находится вверху), а на второй — использование обычного Rel. Без Rel Up диаграмма длиннее.

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

C4 + PlantUML: сделать его презентабельным #3

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

LAYOUT_LANDSCAPE()

На первом вы видите диаграмму после вызова процедуры LAYOUT_LANDSCAPE(), т.е. она рисуется от первого ко второму.

LAYOUT_TOP_DOWN()

На втором вы видите диаграмму после вызова процедуры LAYOUT_TOP_DOWN(), т.е. она рисуется сверху вниз. Рисование сверху вниз включено по умолчанию.

То, что вы видите, не означает, что рисование с первого на второе всегда лучше. Просто это лучше для текущей схемы.

C4 + PlantUML: сделать его презентабельным #4

Давайте посмотрим на форму ссылок. Ранее вы видели форму ссылок по умолчанию: в этом случае формы ссылок могут округляться по мере необходимости.

тип линии skinparam ортогональный

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

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

полилиния типа линии skinparam

С ломаной гораздо меньше проблем с расположением описания ссылки.

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

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

Также имейте в виду, что под капотом PlantUML используется пакет утилит для визуализации графиков — graphviz и различные настройки отображения вы можете узнать, ознакомившись с документацией именно к этому пакету.

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