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).
Что НЕ рисуем:
- технологии, базы данных, очереди — всё это внутри.
Пример: онлайн-магазин
C4Context
title System Context — Online Store
Person(customer, "Покупатель", "Оформляет заказы через веб и мобайл")
Person(admin, "Менеджер", "Управляет каталогом и заказами")
System(store, "Online Store", "Позволяет покупателям выбирать товары и оформлять заказы")
System_Ext(payment, "Payment Gateway", "Обработка платежей")
System_Ext(delivery, "Delivery Service", "Отслеживание и доставка заказов")
System_Ext(email, "Email Provider", "Transactional email")
Rel(customer, store, "Оформляет заказы")
Rel(admin, store, "Управляет каталогом")
Rel(store, payment, "Проводит платежи", "HTTPS/API")
Rel(store, delivery, "Создаёт доставки", "HTTPS/API")
Rel(store, email, "Отправляет уведомления", "SMTP")
Когда использовать: onboarding новых людей, разговор с бизнесом, kick-off проекта. Это единственный уровень, который стоит показывать не-разработчикам.
Level 2: Container
Раскрываем нашу систему. Показываем из каких запускаемых единиц она состоит: приложения, сервисы, базы данных, очереди, SPA.
Контейнер в C4 — это не Docker-контейнер. Это отдельно деплоимая/запускаемая штука.
Пример
C4Container
title Container — Online Store
Person(customer, "Покупатель")
System_Boundary(store, "Online Store") {
Container(spa, "Web App", "React", "SPA для покупателей")
Container(api, "API Server", "Go", "REST API, бизнес-логика")
Container(worker, "Order Worker", "Go", "Асинхронная обработка заказов")
ContainerDb(db, "PostgreSQL", "PostgreSQL 16", "Заказы, каталог, пользователи")
ContainerQueue(queue, "RabbitMQ", "RabbitMQ", "Очередь событий заказов")
}
System_Ext(payment, "Payment Gateway")
Rel(customer, spa, "Использует", "HTTPS")
Rel(spa, api, "Вызывает", "HTTPS/JSON")
Rel(api, db, "Читает/пишет", "SQL")
Rel(api, queue, "Публикует события", "AMQP")
Rel(worker, queue, "Подписан на события", "AMQP")
Rel(worker, db, "Обновляет статусы", "SQL")
Rel(api, payment, "Проводит платежи", "HTTPS")
Когда использовать: при обсуждении инфраструктуры, при принятии решений о технологиях, при планировании деплоя. Это самый полезный уровень для большинства команд.
Level 3: Component
Раскрываем один контейнер. Показываем его внутренние компоненты: сервисы, контроллеры, репозитории.
Пример: API Server изнутри
C4Component
title Component — API Server
Container_Boundary(api, "API Server") {
Component(auth, "Auth Middleware", "Go", "JWT-валидация, rate limiting")
Component(catalog, "Catalog Service", "Go", "CRUD каталога товаров")
Component(orders, "Order Service", "Go", "Создание и управление заказами")
Component(payments, "Payment Client", "Go", "Интеграция с платёжным шлюзом")
Component(repo, "Repository Layer", "Go", "Доступ к данным через SQL")
}
ContainerDb(db, "PostgreSQL")
ContainerQueue(queue, "RabbitMQ")
System_Ext(payment, "Payment Gateway")
Rel(auth, catalog, "Передаёт запросы")
Rel(auth, orders, "Передаёт запросы")
Rel(orders, payments, "Инициирует оплату")
Rel(orders, queue, "Публикует OrderCreated", "AMQP")
Rel(payments, payment, "Вызывает API", "HTTPS")
Rel(catalog, repo, "Использует")
Rel(orders, repo, "Использует")
Rel(repo, db, "SQL-запросы")
Когда использовать: при проектировании внутренней структуры сервиса, при рефакторинге. Обычно нужен только для сложных контейнеров.
Level 4: Code
Самый детальный уровень — UML-диаграммы классов, ER-схемы. По сути, это уже не архитектурная диаграмма, а документация кода.
classDiagram
class OrderService {
-repo OrderRepository
-payments PaymentClient
-queue EventPublisher
+CreateOrder(ctx, req) Order
+CancelOrder(ctx, id) error
+GetOrder(ctx, id) Order
}
class OrderRepository {
<<interface>>
+Save(ctx, Order) error
+FindByID(ctx, id) Order
+UpdateStatus(ctx, id, Status) error
}
class PostgresOrderRepo {
-db *sql.DB
+Save(ctx, Order) error
+FindByID(ctx, id) Order
+UpdateStatus(ctx, id, Status) error
}
OrderService --> OrderRepository
OrderService --> PaymentClient
OrderService --> EventPublisher
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 для архитектурных задач. Не для каждого тикета, а только для тех, которые меняют структуру системы.