C4: четыре уровня архитектуры
Как описывать архитектуру AI-продукта: от контекста к контейнерам и компонентам, только PlantUML, только as-code
Право «нормально вертеть проект» зарабатывается пониманием архитектуры: если ты свободно переходишь между уровнями C4 — от «кто нами пользуется» до «какой модуль за что отвечает», — любое изменение проекта перестаёт быть страшным.
В 2026 у архитектуры as-code есть второй заказчик: агент. Диаграмма в PlantUML, лежащая в репозитории, — это карта проекта, которую читает и человек, и Claude Code, и Vibe. Агент с актуальной container-диаграммой делает на порядок меньше архитектурного бреда.
Правила игры
- Только as-code. Исходник
.pumlлежит вdocs/diagrams/, сгенерированный.svg— рядом, оба закоммичены. Figma и Miro не используются. - Диаграмма отражает то, что есть, а не то, что мечталось. Разъехались — чинишь диаграмму в том же PR, что и код.
- Один уровень — одна диаграмма. Не смешивай контейнеры с компонентами на одной картинке: это главный источник каши.
Генерация SVG:
plantuml -tsvg docs/diagrams/*.pumlТри уровня — от мира к модулям
Пролистай: диаграмма закрепляется, а по мере чтения детализируется — от системы среди внешнего мира до модулей внутри одного контейнера.
01Context — система и мир
Показываем: систему одним прямоугольником, живых людей, внешние системы, направления связей.
Не показываем: технологии, базы данных, внутренности.
Типичные ошибки: контейнеры на контекстной диаграмме («а вот тут у нас PostgreSQL»); забытые внешние системы (LLM-провайдер и Langfuse — тоже внешние); связи без подписей.
02Container — из чего состоит система
Показываем: самостоятельно запускаемые единицы (frontend, API, БД, AI-модуль), технологию каждого, протоколы связи.
Тест на контейнер: запускается отдельно ИЛИ хранит данные — контейнер; иначе — компонент. Контейнер ≠ Docker-контейнер: слои controller/service/repository живут в одном процессе — это компоненты.
Это главная диаграмма проекта — именно её проверяет aact checkи именно она нужна агенту в контексте.
03Component — внутри одного контейнера
Показываем: крупные модули одного контейнера и их обязанности: роутеры, сервисы, доступ к данным, шлюз к модели.
Правило одного предложения: если обязанность компонента не описывается одним предложением без «и» — это два компонента. AI Gateway — единая точка вызова модели с ретраями, таймаутами и лимитами токенов.
Уровень 4 — Code: не рисуем
Диаграммы классов устаревают быстрее, чем их успевают дорисовать. Уровень кода — это сам код: структура папок, типы и сигнатуры. Если очень нужно показать связи — генерируй из кода, не рисуй руками.
Процедура: от описания к модели
Порядок из скилла aact-architect — строго по шагам, не забегая вперёд:
- Назови систему — одна система, одна
System_Boundary. - Люди и внешние системы — кто пользуется (
Person), от чего зависим (System_Ext: LLM-провайдер, Langfuse — тоже сюда). - Запускаемые единицы и хранилища — каждая проходит тест «запускается отдельно или хранит данные».
- Связи — каждая с подписью что течёт и как:
Rel(web, api, "fetches orders", "JSON/HTTPS"), а неRel(web, api). - Границы — только если контекстов реально больше одного.
- Стоп. На уровень компонентов — только когда понадобилось разобрать внутренности одного контейнера.
Связка с ADR: решение, диаграмма и цена
Каждое значимое изменение диаграммы — следствие решения, а у решения есть цена. Берём шаблон ADR из aact и добавляем одно обязательное поле:
# Выбор модели для генерации вопросов
## Контекст
<в каких условиях принимается решение: объём, бюджет, требования к качеству>
## Краткое описание решения и его обоснование
### Схема
<фрагмент C4-диаграммы, на который влияет решение>
### Решение
Используем <модель> через <провайдера>, деградация на <дешёвую модель>.
### Обоснование
<почему так, что рассматривали ещё и почему отказались>
## Цена решения
<какой долг берём: стоимость запроса, привязка к провайдеру, лимиты>
## Как покрыть тестами
<какая проверка поймает, что решение перестало соблюдаться>
### Примеры тестов
<ссылки на тесты или evals>Два раздела здесь делают всю работу. «Как покрыть тестами» — из шаблона aact, и это его лучшая идея: решение, которое нечем проверить, через два спринта существует только на бумаге. «Цена решения» — наше добавление и условие DoD: ADR с пустым полем цены не принимается. Так технический долг остаётся видимым, а не растворяется в «ну мы же обсуждали».
Диаграмма показывает «как», ADR отвечает «почему именно так и что нам это стоит». Живые примеры — в ADRs репозитория. Файл называй по решению, а не по технологии: «Anti-corruption Layer.md», а не «Добавили Kafka.md» — через полгода искать будут первое.