На одном из проектов мы хотели попробовать использовать C4 модели для описания архитектуры приложения. Посмотрели разные инструменты (Structurizr, C4-PlantUML, IcePanel, ...), нам не зашло и решили сделать свой инструмент моделирования. В этой статье я расскажу что и как у нас получилось.

Почему не подошли существующие инструменты:
Хотелось бы, чтобы модели можно было описывать текстом, но при этом была возможность подвигать фигуры на диаграмме вручную. Пока для C4 у нас только диаграммный редактор и подвигать фигуры можно, но текстовый редактор скорее всего будет только в следующей статье
Хотелось бы удобный древовидный навигатор по моделям. Я думаю, это получилось
Хотелось бы поддержку не только C4, но и других видов моделей (данные, процессы, требования, ...). Сейчас есть модели классов, EPC, VAD и при необходимости можно добавлять новые нотации
Хотелось бы, чтобы модели соответствовали международным стандартам моделирования и не были вещью в себе. Это получилось, у нас модели соответствуют стандарту OMG MOF и могут использоваться в любых других инструментах, которые так же реализуют этот стандарт
Хотелось бы генерировать документы и код по моделям, преобразовывать модели, автоматизировать разную рутину. Частично реализовали, но пока только для продвинутых пользователей
Хотелось бы полноценный репозиторий моделей с управлением доступом, версионированием, API. Получилось, но пока без версионирования
Хотелось бы описывать архитектурные правила для валидации C4 и других моделей. Это пока не сделали
Хотелось бы чтобы всё это было бесплатно и без искусственных ограничений на сложность моделей и т. д. Это есть
Хотелось бы развернуть репозиторий локально внутри компании. Есть Docker образы
Хотелось бы Single Sign‑On и в целом возможность встраивания в ландашафт информационных систем компании. Single Sign‑On поддерживается через Keycloak или аналогичную систему, а для интеграции есть API
Архитектура нашего приложения на C4
Немного опишу нотацию C4 на случай если вы не сталкивались с ней раньше. Но чтобы это не было очередное руководство в стиле «добавили несколько квадратиков, смотрите какая замечательная архитектура получилась», попробую применить всё это к архитектуре нашего собственного приложения и сформулировать комментарии и вопросы, которые при этом возникали.
C4 — это метод описания архитектуры приложений. Он позволяет всем участникам проекта понять что за приложение разрабатывается, кто будет им пользоваться, с какими другими системами оно будет взаимодействовать. Также C4 позволяет провалиться в детали реализации: это монолитное или микросервисное приложение, какие технологии используются и т. д.
По ссылке доступен проект с описанием архитектуры нашего приложения. В предыдущих статьях можно посмотреть описание инструмента в целом, как в нём работать с моделями.
Диаграммы контекста (уровень 1)
При использовании подхода C4 первое что вы делаете это описываете контекст использования вашего приложения:

На скриншоте:
Слева — древовидный навигатор по модели: системы содержат контейнеры, которые содержат компоненты
Справа — форма свойств: у корневого объекта (ландшафт информационных систем) два атрибута (название и описание), у других объектов могут быть дополнительные атрибуты (например, используемые технологии, вид контейнера и т. д.)
По центру — редактор диаграмм
На диаграмме контекста описывается следующее:
Разрабатываемая система (синий прямоугольник)
Пользователи системы (человечки):
Разработчики метамоделей определяют правила моделирования
Разработчики моделей создают модели
Бизнес‑пользователи используют модели
Сторонние системы, с которыми наше приложение будет интегрироваться (серые прямоугольники):
Система управления учётными данными пользователей. У нас это Keycloak
Поставщики моделей. Например, 1С может предоставлять модель организационной структуры предприятия
Потребители моделей. Например, система исполнения процессов может получать из инструмента моделирования модели процессов
Диаграммы контейнеров (уровень 2)
На втором уровне проваливаемся внутрь разрабатываемой системы:

На диаграмме контейнеров описывается следующее:
Само приложение изображаем пунктирной линией. Причём в репозитории (или в навигаторе по моделям) информационная система это физически один объект, просто на разных диаграммах (на уровне 1 и на уровне 2) он изображается по‑разному
Внутри системы показываем контейнеры, из которых она состоит. Каждый контейнер — это отдельно развертываемая и запускаемая часть приложения. Часто пишут, что их не нужно путать с Docker контейнерами, но в нашем случае они совпадают. На мой субъективный взгляд эта схема выглядит слишком технической, мне хотелось бы описать сначала функциональные блоки (например, подсистема управления метамоделями, редактор моделей, подсистема управления доступом, подсистема хранения моделей, подсистема версионирования моделей и т. д.), затем для каждого функционального блока описать функции, которые он позволяет выполнять. Но вместо этого мы сразу проваливаемся в технику — в 3-уровневую архитектуру. В принципе, так и задумано, это назначение архитектурных моделей. Но лично мне сначала хотелось бы увидеть более верхнеуровневые модели, ощущение что одного C4 недостаточно. Контейнеры изображаются синими прямоугольниками:
Веб‑интерфейс
API сервер
База данных
Снаружи системы показываем пользователей. Мне не хотелось снова дублировать все 3 категории пользователей на этой диаграмме. Можно было бы добавить нового обобщенного пользователя, но на уровне 1 его не было и довольно странно, если бы он вдруг появился на уровне 2. Видимо с пользователями та же история что и с контейнерами. Нужно разбивать их на категории исходя не из бизнес‑смысла, а из того с какими контейнерами они взаимодействуют? Если с одним и тем же, значит уже на уровне 1 должен быть только один пользователь?
Снаружи системы показываем внешние системы с уровня 1 (это те же самые объекты, которые можно перетащить на диаграмму из навигатора). Для них мы можем описать уже более детальные связи, с какими именно контейнерами они взаимодействуют
Диаграммы компонентов (уровень 3)
На третьем уровне проваливаемся внутрь контейнеров.
Компоненты фронтенда:

Компоненты бэкенда:

На диаграмме компонентов описывается следующее:
Исходный контейнер изображаем пунктирной линией
Внутри показываем компоненты в виде синих прямоугольников. Есть разные мнения по поводу того что такое компонент. Они могут быть разбиты по организационным единицам кода (jar, dll, assembly, folder,...) или нет. В нашем приложении организационные единицы выделены вполне осмысленно и они могли бы изображаться как компоненты, но как на одной диаграмме показать сотню компонентов?.. В руководствах по C4 обычно приводятся примеры ещё более мелких компонентов (контроллеры, сервисы, репозитории) — да, их тысячи хоть в сколько‑нибудь крупном приложении! Как их все показать на диаграмме? В итоге, я нарисовал какие‑то компоненты выше, но без удовольствия
Также на всех уровнях можно группировать объекты
Снаружи показываем другие контейнеры
Диаграммы кода (уровень 4)
На четвертом уровне проваливаемся внутрь компонентов и описываем вещи, уже близкие к коду: классы, возможно диаграммы последовательностей, модель данных и другие технические детали.
На этом уровне у нас модель данных. Вот, основные сущности:

И базовые сущности:

Модель данных может быть удобно редактировать в текстовом виде:

У нас достаточно богатая платформо независимая система типов, чтобы генерировать из одной модели SQL скрипты, Java код и т. д. Также поддерживается локализация, чтобы по модели можно было генерировать документацию (если вы откроете ссылку в фоновой вкладке, то Mermaid диаграмма может не сгенерироваться сразу, в этом случае можно обновить страницу, пока не разобрались в чём причина):

На скриншоте выше Mermaid‑диаграмма, сгенерированная по модели данных. И она гораздо менее читаемая, чем диаграмма в начале этого раздела. Конечно в будущем в документы нужно будет вставлять исходные диаграммы.
Как это сделано
Метамодель
Чтобы добавить новый язык моделирования нужно описать для него метамодель. К сожалению готовой метамодели не было, поэтому мы смотрели справочник по Structurizr DSL и для основных понятий создали метаклассы:

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

Например, есть интерфейс «Владелец объектов внутри информационной системы» (SoftwareSystemItemOwner). Это может быть:
Информационная система (SoftwareSystem)
Группа контейнеров (ContainerGroup)
И есть другой интерфейс «Элемент информационной системы» (SoftwareSystemItem). Это может быть:
Группа контейнеров (ContainerGroup)
Контейнер (Container)
Группа контейнеров упомянута в обоих списках. Это позволяет добавлять в модель произвольное количество уровней вложенности для групп контейнеров. Можете добавлять контейнеры сразу в информационную систему, а можете добавить несколько штук вложенных групп и уже туда добавить контейнер.
Отдельная тема как хранить связи между объектами в моделях с несколькими уровнями вложенности. Представьте, что у вас в BPMN две дорожки, в каждой по одному объекту, которые связаны между собой. Какой из дорожек должна принадлежать связь? Чтобы не запариваться с этим обычно связи лежат в корне модели. А что если связаны два объекта внутри одной дорожки? Вроде логично и связь хранить в ней же. А что если мы потом перетащим один из объектов или оба в другую дорожку? Словом, и такие связи обычно тоже хранятся в модели на верхнем уровне. Но для C4 такой подход не работает, в нём слишком много уровней вложенности, и если все связи вытаскивать в корень, то там будет хаос. Поэтому на уровне метамодели у нас определено несколько типов объектов кроме корневого ландшафта информационных систем, в которых могут храниться связи:

Такая метамодель уже достаточно формальная в отличие от исходного описания. Её можно продвигать в качестве стандарта по аналогии с BPMN, UML, SysML, ... Можно использовать в разных инструментах моделирования, соответствующих стандарту OMG MOF. Да, и в принципе, если модель соответствует достаточно строгой метамодели, то по ней удобно генерировать документы, код, выполнять разные методы анализа.
Правила отображения объектов на диаграмме
Для C4 у нас три типа фигур:

Для каждого класса объектов может быть задана базовая фигура. А ещё в зависимости от значений атрибутов объекту могут назначаться динамические (условные) классы. Например, у контейнера может быть вид «не определен» (в этом случае он отображается как прямоугольник) или «база данных» (в этом случае он отображается как цилиндр). Ещё есть признак это уже существующий контейнер (в этом случае он отображается серым цветом) или новый (в этом случае он отображается синим цветом). Всего возможно 4 комбинации: синие прямоугольники, синие цилиндры, серые прямоугольники, серые цилиндры. А вообще их могло бы быть и больше. Например, для связей в моделях классов уже 6 комбинаций по направлению связи (прямая или обратная) и по виду (ассоциация, композиция или агрегация). Очень удобно, что не нужно для каждого варианта рисовать отдельную картинку:

Достигается это следующим образом. В каждом SVG с помощью CSS-классов указывается значения каких визуальных атрибутов можно менять:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 200 100"
width="200" height="100" preserveAspectRatio="none">
<g class="fill stroke stroke-width stroke-dasharray"
fill="white" stroke="black" stroke-width="1">
<path d="M 3 13 V 87 A 97 10 0 0 0 197 87 V 13"
vector-effect="non-scaling-stroke" />
<ellipse cx="100" cy="13" rx="97" ry="10"
vector-effect="non-scaling-stroke" />
</g>
</svg>Заключение
В статье мы немного рассказали про C4. Вообще по нему более чем достаточно руководств, но возможно у вас при его использовании возникают такие же вопросы как и в этой статье. И вы можете поделиться своей болью или успехом.
Также мы рассказали про метамодель C4, про подходы, которые можно использовать для моделей с несколькими уровнями вложенности. Если вы разрабатываете инструменты моделирования, то возможно нашли для себя что‑то интересное или хотите чем‑то поделиться. Если вы считаете, что в тексте слишком много рекламы и слишком мало пользы, то, пожалуйста, разверните это немного, чтобы мы понимали как улучшить статьи.
