C4-диаграммы: практический гид по визуализации архитектуры
Четыре уровня масштабирования архитектурных диаграмм — от общей картины до кода. Когда какой использовать, примеры на Structurizr DSL и типичные ошибки.
Большинство архитектурных диаграмм бесполезны. Они либо показывают всё сразу (и ничего не понятно), либо устаревают на следующий день. C4 — это способ рисовать диаграммы так, чтобы они реально помогали.
Проще говоря
C4 — модель визуализации архитектуры на четырёх уровнях масштаба: Context → Container → Component → Code. Как карта Google Maps: сначала страна, потом город, потом улица, потом дом. Ниже — что именно рисовать на каждом уровне, когда остановиться и где команды обычно ошибаются.
Зачем вообще C4
Классическая проблема: архитектор рисует одну диаграмму, на которой одновременно пользователи, серверы, классы и базы данных. Получается «карта всего», которую никто не может прочитать.
C4 решает это через zoom-уровни. Каждый уровень отвечает на свой вопрос:
| Уровень | Вопрос | Аудитория |
|---|---|---|
| Context | Что делает система и кто с ней работает? | Все: бизнес, менеджмент, новые разработчики |
| Container | Из каких запускаемых частей состоит система? | Разработчики, DevOps, архитекторы |
| Component | Что внутри конкретного контейнера? | Разработчики команды |
| Code | Как устроен конкретный компонент? | Автор компонента |
Level 1: System Context
Самый верхний уровень. Показывает систему как чёрный ящик: кто ей пользуется и с чем она интегрируется.
Что рисуем:
- нашу систему (одна коробка),
- пользователей (персоны),
- внешние системы (платёжки, email-провайдеры, CRM).
Что НЕ рисуем:
- технологии, базы данных, очереди — всё это внутри.
Пример: онлайн-магазин
flowchart LR
customer[Покупатель]
admin[Менеджер]
store([Online Store\nСистема заказов и каталога])
payment[[Payment Gateway]]
delivery[[Delivery Service]]
email[[Email Provider]]
customer -->|Оформляет заказ| store
admin -->|Управляет каталогом| store
store -->|Платежи · HTTPS/API| payment
store -->|Доставка · HTTPS/API| delivery
store -->|Уведомления · SMTP| email
Когда использовать: onboarding новых людей, разговор с бизнесом, kick-off проекта. Это единственный уровень, который стоит показывать не-разработчикам.
Level 2: Container
Раскрываем нашу систему. Показываем из каких запускаемых единиц она состоит: приложения, сервисы, базы данных, очереди, SPA.
Контейнер в C4 — это не Docker-контейнер. Это отдельно деплоимая/запускаемая штука.
Пример
flowchart LR
customer[Покупатель]
subgraph store[Online Store]
spa[Web App\nReact SPA]
api[API Server\nGo REST]
worker[Order Worker\nGo Async]
db[(PostgreSQL)]
queue[(RabbitMQ)]
end
payment[[Payment Gateway]]
customer -->|HTTPS| spa
spa -->|HTTPS/JSON| api
api -->|SQL| db
api -->|AMQP publish| queue
worker -->|AMQP consume| queue
worker -->|SQL update| db
api -->|HTTPS payment| payment
Когда использовать: при обсуждении инфраструктуры, при принятии решений о технологиях, при планировании деплоя. Это самый полезный уровень для большинства команд.
Level 3: Component
Раскрываем один контейнер. Показываем его внутренние компоненты: сервисы, контроллеры, репозитории.
Пример: API Server изнутри
flowchart LR
subgraph api[API Server]
auth[Auth Middleware\nJWT + Rate Limit]
catalog[Catalog Service]
orders[Order Service]
payments[Payment Client]
repo[Repository Layer]
end
db[(PostgreSQL)]
queue[(RabbitMQ)]
payment[[Payment Gateway]]
auth --> catalog
auth --> orders
orders --> payments
orders -->|OrderCreated · AMQP| queue
payments -->|HTTPS API| payment
catalog --> repo
orders --> repo
repo -->|SQL| db
Когда использовать: при проектировании внутренней структуры сервиса, при рефакторинге. Обычно нужен только для сложных контейнеров.
Level 4: Code
Самый детальный уровень — UML-диаграммы классов, ER-схемы. По сути, это уже не архитектурная диаграмма, а документация кода.
classDiagram
direction LR
class OrderService {
-repo: OrderRepository
-payments: PaymentClient
-events: EventPublisher
+CreateOrder(req)
+CancelOrder(id)
+GetOrder(id)
}
class OrderRepository {
<<interface>>
+Save(order)
+FindByID(id)
+UpdateStatus(id, status)
}
class PostgresOrderRepo {
-db
+Save(order)
+FindByID(id)
+UpdateStatus(id, status)
}
class PaymentClient
class EventPublisher
OrderService --> OrderRepository : uses
OrderService --> PaymentClient : charges
OrderService --> EventPublisher : publishes
PostgresOrderRepo ..|> OrderRepository
Когда использовать: почти никогда. Серьёзно. Этот уровень устаревает быстрее, чем вы его нарисуете. Единственный случай — сложная доменная модель, где диаграмма реально помогает разобраться.
Structurizr DSL: диаграммы как код
Mermaid хорош для быстрых набросков. Для серьёзной работы с C4 лучше использовать Structurizr DSL — модель описывается один раз, диаграммы генерируются автоматически.
workspace {
model {
customer = person "Покупатель" "Оформляет заказы"
admin = person "Менеджер" "Управляет каталогом"
store = softwareSystem "Online Store" "Онлайн-магазин" {
spa = container "Web App" "SPA для покупателей" "React"
api = container "API Server" "REST API и бизнес-логика" "Go" {
authMiddleware = component "Auth Middleware" "JWT, rate limiting"
catalogService = component "Catalog Service" "CRUD каталога"
orderService = component "Order Service" "Управление заказами"
paymentClient = component "Payment Client" "Интеграция с оплатой"
repoLayer = component "Repository Layer" "Доступ к данным"
}
worker = container "Order Worker" "Обработка заказов" "Go"
db = container "PostgreSQL" "Хранение данных" "PostgreSQL 16" "database"
queue = container "RabbitMQ" "Очередь событий" "RabbitMQ" "queue"
}
paymentGw = softwareSystem "Payment Gateway" "Обработка платежей" "external"
deliveryService = softwareSystem "Delivery Service" "Доставка" "external"
customer -> spa "Использует" "HTTPS"
admin -> spa "Управляет каталогом" "HTTPS"
spa -> api "Вызывает" "HTTPS/JSON"
api -> db "Читает/пишет" "SQL"
api -> queue "Публикует события" "AMQP"
worker -> queue "Подписан" "AMQP"
worker -> db "Обновляет статусы" "SQL"
api -> paymentGw "Платежи" "HTTPS"
worker -> deliveryService "Создаёт доставки" "HTTPS"
}
views {
systemContext store "Context" {
include *
autolayout lr
}
container store "Containers" {
include *
autolayout lr
}
component api "Components" {
include *
autolayout lr
}
}
}
Преимущества Structurizr DSL:
- одна модель → много диаграмм,
- хранится в git рядом с кодом,
- автоматическая валидация связей,
- можно генерировать в PNG, SVG, PlantUML.
Когда какой уровень рисовать
Не нужно рисовать все четыре уровня для каждой системы. Вот простое правило:
Всегда рисуйте:
- Context — для каждой системы. Это стоит 15 минут и экономит часы объяснений.
- Container — для каждой системы, которую вы активно разрабатываете.
Рисуйте по необходимости:
- Component — только для сложных контейнеров, где непонятно «что внутри».
- Code — только если диаграмма реально помогает понять доменную модель.
Практические советы
1. Начинайте с Context, даже если кажется очевидным. Вы удивитесь, сколько разногласий вскроется при попытке нарисовать «очевидную» диаграмму. Один думает, что мы сами отправляем email, другой уверен, что это делает CRM.
2. Подписывайте связи. Стрелка без подписи — бесполезная стрелка. Указывайте что передаётся и по какому протоколу: «Отправляет заказ, HTTPS/JSON», а не просто «использует».
3. Храните диаграммы рядом с кодом. Structurizr DSL или Mermaid в markdown — оба варианта живут в репозитории. Диаграмма в Confluence, которую обновляли полгода назад, хуже, чем отсутствие диаграммы.
4. Обновляйте при архитектурных решениях. Добавили новый сервис? Обновите Container-диаграмму. Подключили внешнюю систему? Обновите Context. Это занимает 5 минут.
5. Используйте цвета осмысленно. Внутренние системы — один цвет, внешние — другой, deprecated — третий. Не раскрашивайте «для красоты».
Антипаттерны
🚫 «Диаграмма всего» Одна диаграмма с пользователями, сервисами, классами, таблицами и очередями. Никто не может её прочитать, включая автора через неделю.
🚫 Diagram-driven development Часами полировать диаграммы вместо написания кода. C4 — инструмент коммуникации, а не цель. Если диаграмма не помогает принимать решения — она не нужна.
🚫 Все четыре уровня для всего Рисовать Code-уровень для простого CRUD-сервиса — пустая трата времени. Остановитесь на том уровне, где становится очевидно.
🚫 «Нарисовали и забыли» Диаграмма полугодовой давности врёт. Лучше удалить устаревшую диаграмму, чем оставлять дезинформацию.
🚫 Путать контейнер с Docker-контейнером В C4 контейнер — это запускаемая единица: приложение, БД, очередь. Не путайте с Docker/Kubernetes-контейнерами.
Минимальный чеклист
- Context-диаграмма есть и актуальна
- Container-диаграмма отражает текущую архитектуру
- Все связи подписаны (что передаётся + протокол)
- Внешние системы отличаются визуально от внутренних
- Диаграммы лежат в репозитории, а не в Confluence/Miro
- Обновляются при архитектурных изменениях
Инструменты
| Инструмент | Для чего | Формат |
|---|---|---|
| Structurizr | Полноценное моделирование C4 | DSL → PNG/SVG |
| Structurizr Lite | Локальный рендеринг, бесплатно | Docker |
| Mermaid | Быстрые диаграммы в markdown | Текст → SVG |
| PlantUML + C4 | C4 через PlantUML | Текст → PNG |
| IcePanel | Интерактивные C4 с навигацией | Web UI |
Вывод
C4 — это не фреймворк и не методология. Это здравый смысл: показывай архитектуру на разных уровнях масштаба, чтобы каждый видел то, что ему нужно.
На практике 90% ценности дают первые два уровня: Context и Container. Начните с них. Если потребуется больше — у вас есть Component и Code. Но не рисуйте их «на всякий случай».
Как применять это в живом проекте
Начните с одной системы, которую сложнее всего объяснить новичку. Нарисуйте Context за 15 минут, Container за 30. Покажите команде. Если после этого кто-то скажет «а, так вот как это работает» — значит, C4 уже окупился.
Дальше — закрепите обновление диаграмм в Definition of Done для архитектурных задач. Не для каждого тикета, а только для тех, которые меняют структуру системы.