C4 Model

Визуализация архитектуры на четырех уровнях

1 апреля 2024

Справочник

Архитектурная диаграмма должна отвечать на конкретный вопрос. C4 Model Саймона Брауна помогает выбрать уровень детализации: Context, Containers, Components или Code. В статье разбираю, что показывать на каждом уровне, как избегать смешения масштабов и как поддерживать диаграммы в актуальном состоянии.

Не обязательно рисовать все четыре уровня C4. Для многих команд достаточно Context и Container; Component и Code добавляют, когда они помогают ответить на конкретный вопрос. Модель не привязана к инструменту или нотации.

Проблема абстракции диаграмм

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

UML, доминирующая нотация 1990-х и 2000-х, давал 14 типов диаграмм (class, sequence, activity, component, deployment и т. д.), но не давал правила, как эти типы между собой соотносятся. Grady Booch в «Object Solutions» (Addison-Wesley, 1996) и в более поздней «Object-Oriented Analysis and Design with Applications» (3rd ed., 2007) подробно обсуждал levels of abstraction как ключевую инженерную дисциплину - но в самом UML это правило осталось имплицитным. В результате команды, искренне старавшиеся «делать UML», получали либо переусложненные class diagrams (которые быстро устаревали и переставали обновляться), либо вырожденные рисунки в Visio, формально похожие на UML, но без какой-либо дисциплины уровней.

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

Саймон Браун, британский архитектор и автор «Software Architecture for Developers» (Leanpub, 2014), сформулировал проблему так: команды разучились разговаривать об архитектуре общим языком. Не потому что они не знают паттернов или не умеют рисовать, а потому что у них нет согласованного словаря зума. Когда один говорит «архитектура», он имеет в виду набор сервисов; когда другой - расположение классов в кодовой базе; когда третий - схему деплоя в облако. Все три ответа правильные, но они о разных слоях, и без явной фиксации уровня обсуждение распадается.

C4 Model - ответ на эту проблему. Браун предложил не новую нотацию, а правило: всегда явно фиксировать, на каком из четырех уровней (Context, Containers, Components, Code) находится диаграмма, и не смешивать элементы разных уровней на одном холсте. Аналогия Брауна, которую он повторяет в большинстве своих выступлений - карты Google: страна → город → район → улица. На каждом уровне виден свой набор объектов, и переключение между уровнями - это именно зум, а не переключение типа карты. Тот же набор объектов реального мира описывается с разной степенью детализации в зависимости от того, какой вопрос вы задаете.

C4 - не замена UML, а дисциплина зума. UML class diagram прекрасно живет внутри уровня Code как одно из возможных представлений; UML deployment diagram прекрасно живет как отдельный supplementary view. C4 не запрещает существующие нотации - он запрещает смешивать их на одной диаграмме без явного указания уровня.

Четыре уровня абстракции

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

Уровни C4 описывают детализацию архитектуры, а bounded contexts в DDD - границы моделей предметной области. Эти понятия можно использовать вместе, но они решают разные задачи.

Mark Richards и Neal Ford в «Software Architecture: The Hard Parts» (O'Reilly, 2021) формулируют похожий тезис в терминах архитектурных решений: каждое решение принимается на определенном уровне (системном, сервисном, компонентном), и попытка решать вопрос одного уровня инструментами другого - частая причина проваленных миграций. C4 операционализирует эту идею: уровень обсуждения должен быть виден на самой диаграмме, а не подразумеваться.

C4-диаграмма показывает структуру системы, ADR объясняет принятое решение. Их можно связывать в документации, указав владельца и порядок обновления.

Level 1: System Context

Показывает систему как черный ящик в окружении пользователей и внешних систем. Это bird's eye view - вид с высоты птичьего полета.

Scope Одна software system
Primary elements Ваша система (в центре)
Supporting elements Пользователи (роли, персоны) и внешние системы
Аудитория Все: технические и нетехнические люди

Пример: B2B SaaS платформа

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

┌─────────────────────────────────────────────────────────────────┐
│                        USERS (Personas)                         │
├─────────────┬─────────────┬─────────────┬─────────────┬─────────┤
│ Employee    │ Client      │ Admin       │ Support     │ SRE     │
│ (firm user) │ (end user)  │ (system)    │ (L2 help)   │ (ops)   │
└──────┬──────┴──────┬──────┴──────┬──────┴──────┬──────┴────┬────┘
       │             │             │             │           │
       ▼             ▼             ▼             ▼           ▼
┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│                    ┌───────────────────┐                        │
│                    │   THE PLATFORM    │                        │
│                    │   (Our System)    │                        │
│                    └─────────┬─────────┘                        │
│                              │                                  │
└──────────────────────────────┼──────────────────────────────────┘
                               │
       ┌───────────────────────┼───────────────────────┐
       ▼                       ▼                       ▼
┌─────────────┐         ┌─────────────┐         ┌─────────────┐
│  Payment    │         │ E-Signature │         │   Email     │
│  Provider   │         │   Service   │         │ Integration │
└─────────────┘         └─────────────┘         └─────────────┘
           EXTERNAL SYSTEMS (Third-Party Services)
Рекомендация: создавайте для каждой системы. Это точка входа для понимания контекста.

Что НЕ включать: технологии, протоколы, низкоуровневые детали. Диаграмма должна быть понятна product manager или CEO. Самая частая ошибка на этом уровне - попытка показать «как у нас устроено внутри»: появляются микросервисы, базы данных, очереди. Из-за внутренних деталей нетехнической аудитории может быть сложно понять назначение системы и ее внешние связи.

Level 2: Containers

Zoom-in внутрь системы. Показывает deployable units: веб-приложения, API, базы данных, очереди сообщений, мобильные приложения.

Container ≠ Docker container. В C4 "container" - это любой отдельно запускаемый/деплоимый компонент: процесс, который выполняет код или хранит данные. Это терминологическая ловушка C4: модель появилась раньше, чем Docker стал доминирующим, и Браун в нескольких выступлениях сожалел, что выбрал слово "container" - оно сегодня перегружено. В контексте C4 Postgres-инстанс, Rails-приложение и iOS-клиент - все это containers, независимо от того, упакованы ли они в Docker.
Scope Одна software system
Primary elements Containers внутри системы
Supporting elements Пользователи и внешние системы, связанные с containers
Аудитория Технические люди: архитекторы, разработчики, DevOps

Показывает:

Пример: Backend платформы

Стилизованный пример на основе типичных паттернов B2B SaaS-бэкенда: web app + background jobs + scheduler поверх общего набора storage, плюс несколько внешних gRPC-сервисов для специализированных функций. Конкретные имена обобщены, структура отражает класс систем.

CLIENT APPS:  [React SPA] [Mobile Employee] [Mobile Client] [Desktop] [Admin]
                   |              |               |             |         |
                   +-------+------+-------+-------+-------------+---------+
                           |              |
                           v  HTTPS API   v
              +--------------------------------------------------+
              |                    BACKEND                       |
              |                                                  |
              |  +----------+  +------------+  +-----------+     |
              |  | Web App  |  | Background |  | Scheduler |     |
              |  | (Rails)  |  | Jobs       |  | (Cron)    |     |
              |  +----+-----+  +-----+------+  +-----+-----+     |
              |       |              |              |            |
              |       +--------------+--------------+            |
              |                      |                           |
              |                      v                           |
              |  +----------------------------------------------+|
              |  |               DATABASES                      ||
              |  | [PostgreSQL] [Redis] [OpenSearch] [S3]       ||
              |  +----------------------------------------------+|
              +--------------------------------------------------+
                           |         |         |
                           v gRPC    v gRPC    v gRPC
                    +---------+ +---------+ +-----------+
                    |Payments | |   OCR   | |Marketplace|
                    | Service | | Service | |  Service  |
                    +---------+ +---------+ +-----------+

Что НЕ включать: кластеризацию, load balancers, репликацию - это для Deployment diagram. Показывать «у нас 3 инстанса web app за nginx-балансировщиком» на Container-диаграмме - распространенная ошибка: вы смешиваете логическую структуру (какие containers есть) с физическим деплоем (как они размещены), и диаграмма перестает быть полезной для обоих вопросов.

Level 3: Components

Zoom-in внутрь одного container. Показывает структурные блоки: сервисы, контроллеры, репозитории и их взаимодействие.

Scope Один container
Primary elements Components внутри container
Supporting elements Другие containers и внешние системы
Аудитория Архитекторы и разработчики

Пример: Web Application container

Стилизованный пример на основе типичных паттернов Rails-приложения с разделением на controllers, services, repositories и background jobs. Структура отражает класс архитектур, а не конкретный продукт.

+--------------------------------------------------------------+
|                    WEB APPLICATION (Rails)                   |
|                                                              |
|  +--------------+  +--------------+  +------------------+    |
|  | Controllers  |  |   Services   |  |   Background     |    |
|  |              |  |              |  |   Jobs           |    |
|  | - Invoices   |->| - Billing    |  | - PaymentSync    |    |
|  | - Users      |  | - Payments   |->| - ReportBuilder  |    |
|  | - Reports    |  | - Analytics  |  | - Notifications  |    |
|  +------+-------+  +------+-------+  +--------+---------+    |
|         |                  |                   |             |
|         v                  v                   |             |
|                   +--------------+             |             |
|                   | Repositories |<------------+             |
|                   |              |                           |
|                   | - UserRepo   |                           |
|                   | - InvoiceRepo|                           |
|                   +------+-------+                           |
|                          |                                   |
+--------------------------------------------------------------+
                           |
                           v SQL
                    +--------------+
                    |  PostgreSQL  |
                    +--------------+
Рекомендация: создавайте только если это добавляет ценность. Рассмотрите автогенерацию из кода для долгоживущей документации. Component-диаграммы особенно полезны при онбординге новых разработчиков в конкретный сервис.

Level 4: Code

Самый детальный уровень: UML-диаграммы классов, ER-диаграммы и т. д. В большинстве случаев не нужен - код сам себя документирует, а IDE позволяет навигировать по нему.

Когда полезен:

Для большинства проектов достаточно уровней 1-3. Если код хорошо структурирован и покрыт тестами, Level 4 создает больше работы по поддержанию актуальности, чем приносит пользы. Браун в публичных выступлениях прямо говорит: если вы рисуете Code-диаграмму вручную и обновляете ее при каждом коммите, вы тратите время; если Code-диаграмма не генерируется из самого кода, она устареет в течение спринта.

Дополнительные диаграммы

System Landscape Diagram

Когда нужно показать несколько систем в рамках организации. По сути - Context diagram без фокуса на конкретной системе.

Scope Enterprise / организация / департамент
Аудитория Все

Dynamic Diagram

Показывает взаимодействие элементов во время выполнения: user story, use case, конкретный сценарий. Похоже на UML sequence diagram, но с более свободной раскладкой.

Scope Конкретная feature / user story
Elements На выбор: systems, containers или components

Deployment Diagram

Показывает, как containers деплоятся на инфраструктуру: серверы, VM, Kubernetes, cloud services. Основан на UML deployment diagram.

Scope Одна или несколько систем в конкретном environment (prod, staging, dev)
Elements Deployment nodes, container instances, infrastructure (DNS, load balancers, firewalls)
Аудитория Архитекторы, разработчики, DevOps, SRE

Можно использовать иконки AWS, Azure, GCP - но включайте их в легенду. AWS Reference Architectures, публикуемые на aws.amazon.com/architecture, - один из самых известных публичных примеров иерархического подхода: на верхнем уровне они показывают workload как черный ящик в окружении managed-сервисов (структурно это Context), на следующем - какие сервисы и каким образом интегрированы (структурно это Containers), и только на глубоких страницах появляется детализация по компонентам и зонам доступности (Deployment view). Сами AWS не называют это «C4», но дисциплина уровней узнаваема.

Стратегия внедрения в команде

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

Шаг 1: одна Context-диаграмма на каждую систему. Это минимальный артефакт, который окупается на первой же встрече с новым человеком в команде. Context рисуется за 30 минут, отвечает на вопрос «что это и кто этим пользуется» и не требует от команды менять процессы. Если за месяц после введения дисциплины каждая ваша система обзавелась актуальной Context-диаграммой - это успех первого этапа.

Шаг 2: Container-диаграмма для каждой системы, которая шире одного приложения. Это уровень, на котором живут технические дискуссии: куда добавить новый сервис, что мигрировать, где провести границу. Здесь важно сразу класть диаграмму в репозиторий рядом с кодом (см. раздел про Diagrams as Code), а не в Confluence - иначе она устареет за квартал.

Шаг 3: Component-диаграммы выборочно. Не для каждой системы, а для тех container-ов, где есть онбординг или где структура нетривиальна. Хороший индикатор: если новый разработчик задает вопрос «как у вас устроен этот сервис», и ответ занимает 20 минут устной речи - пора нарисовать Component-диаграмму.

Шаг 4: Deployment-диаграмма как минимум для production. Этот уровень особенно важен для on-call: когда инцидент происходит в 3 утра, дежурный должен по диаграмме понимать, какие узлы за что отвечают и где смотреть метрики. Code-уровень в большинстве команд так и не появляется в явном виде, и это нормально.

Антипаттерны внедрения

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

Смешивание уровней на одном холсте. Самая частая ошибка. На Container-диаграмме появляются классы из одного из сервисов («ну у нас тут важная PaymentProcessor-логика»), на Component - облачные сервисы AWS («ну тут же видно, куда мы пишем в S3»). Каждый раз этому есть локальное оправдание, но результат тот же: диаграмма перестает принадлежать одному уровню, и аудитория, для которой этот уровень предназначен, перестает ее понимать. Правило простое: если элемент не подходит к уровню, значит, он живет на соседнем; нарисуйте на нем отдельную диаграмму или просто опустите.

Переусложненный Context. Команда соблазняется показать «как у нас все устроено внутри» уже на Context-диаграмме - и появляются микросервисы, базы, очереди. Продукт-менеджеру или CEO по цепочке «UserService → AuthService → Redis» трудно понять, какую задачу решает система. Context должен оставаться черным ящиком; все внутреннее - на следующем уровне.

Переусложненный Code. Зеркальная ошибка: команда рисует UML-диаграмму классов на 60 классов, фиксирует ее в Confluence и через месяц перестает обновлять. Через квартал диаграмма расходится с кодом настолько, что становится дезинформацией - новый разработчик пользуется ею и принимает неверные решения. Code-диаграммы оправданы только тогда, когда они либо генерируются автоматически из кода, либо описывают редко меняющееся ядро (state machine, доменное ядро) - то есть существуют в режиме, который защищает от стихийного устаревания.

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

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

Зависимость от единственного автора диаграмм. Когда диаграммы рисует один человек в Visio, а остальная команда не умеет с ними работать, после ухода этого человека диаграммы могут перестать обновляться. Дисциплина C4 окупается только тогда, когда ею владеет команда, а не один архитектор. Это связано с переходом к Diagrams as Code: текстовый исходник диаграммы доступен любому разработчику, и обновление - это PR, а не визит к автору.

Самопроверка адекватности диаграммы: дайте ее человеку, для которого она предназначена (Context - продакту, Container - разработчику другой команды, Component - новому инженеру в этой команде), и попросите за 5 минут пересказать своими словами, что эта система делает и из чего состоит. Если у него не получается - диаграмма не выполняет свою функцию, и важна не ее красота, а корректировка содержимого под заявленную аудиторию.

Примеры из production систем

C4 как формальная нотация принят далеко не везде, но иерархическая дисциплина зума - принципиально шире, и ее используют команды, которые формально про C4 не упоминают. Полезно посмотреть на несколько публичных примеров с разным контекстом.

c4model.com - example bank. Самый прямой пример - официальный сайт модели Брауна (c4model.com) и связанная коллекция в Structurizr DSL (structurizr.com/dsl). Там приведены полные комплекты диаграмм для нескольких эталонных систем (Big Bank plc, Internet Banking System), которые показывают, как все четыре уровня выглядят на одной системе и как они между собой связаны. Эти примеры помогают увидеть, как описать одну систему на разных уровнях детализации.

AWS Reference Architectures. Публичная библиотека референсных архитектур AWS (aws.amazon.com/architecture) формально не использует C4-нотацию, но дисциплина уровней в их диаграммах узнаваема: верхняя диаграмма показывает workload в окружении пользователей и managed-сервисов (структурно это Context), следующие слои показывают логические компоненты и data flow (структурно это Containers и Components), и только дальше - детализация по AZ и сетевой топологии (Deployment). Это полезный пример, потому что он показывает: иерархический подход работает не только для одной системы внутри одной компании, но и как способ публичной коммуникации между вендором и клиентом.

Стилизованный антипример. Стилизованный пример на основе типичных паттернов: команда из 30 инженеров поддерживает «архитектурный wiki», в котором лежит одна большая диаграмма «Overall Architecture» в Lucidchart, нарисованная два года назад техлидом, который с тех пор ушел. Диаграмма показывает 40 прямоугольников (микросервисы, базы, очереди) на одном холсте; уровни не разделены. Каждый новый разработчик видит ее при онбординге, ничего не понимает, идет спрашивать у соседей. Через год команда обнаруживает, что 7 из 40 прямоугольников описывают сервисы, которых уже нет, еще 5 - сервисы под другими именами, и обновлять диаграмму некому, потому что владелец один и он перегружен. Это типовой шаблон, который дисциплина C4 предотвращает с помощью правила «одна диаграмма на zoom-уровень, диаграмма живет в репо рядом с кодом».

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

Diagrams as Code в production

Для долгоживущих диаграмм нужны владелец, история изменений и порядок актуализации. Diagrams as Code удобен, если команда уже работает через Git и PR. Визуальный редактор также подходит, если обеспечивает необходимые для команды версионирование, совместное редактирование и проверку актуальности.

Diagrams as Code решает эту проблему за счет текстового исходника, который живет в том же репозитории, что и код:

Я использую PlantUML с C4 extension - библиотекой макросов от Ricardo Niepel, которая добавляет в PlantUML синтаксис C4 (Person, System, Container, Component, Rel и т. д.). Пример Context-диаграммы:

@startuml
!include <C4/C4_Context>
title My Platform - System Context

Person(employee, "Employee", "Firm user")
Person(client, "Client", "End user")

System(platform, "Platform", "Main system")

System_Ext(payments, "Payment Provider", "Processes payments")
System_Ext(email, "Email Service", "Sends notifications")

Rel(employee, platform, "Manages operations")
Rel(client, platform, "Uses services")
Rel(platform, payments, "Processes payments", "HTTPS")
Rel(platform, email, "Sends emails", "SMTP")
@enduml

Какие диаграммы создавать

Диаграмма Рекомендация
System Context Да - для каждой системы
Container Да - для каждой системы
Component По необходимости
Code Редко, только для сложных частей
Deployment Да - как минимум для production

Инструменты

Советы

Связанные материалы:

Источники

Первоисточники по C4 и архитектурной коммуникации

Реальные примеры и case studies

Инструменты Diagrams as Code