Модуль 4.1. Архитектурные стили и разбиение системы
Темы: монолит, модульные монолиты, микросервисы, порты-адаптеры (Hexagonal), границы контекстов (DDD). Артефакт: контекстная диаграмма C4. Практика: C4 Level 1–2 для кейса.
Как системный аналитик (SA), вы влияете на архитектуру через требования, артефакты и решения по интеграциям. От того, как вы режете систему на части, зависит скорость поставки, устойчивость к изменениям, операционные риски и стоимость владения. В этом модуле:
- разберём стили (монолит / модульный монолит / микросервисы), их плюсы/минусы и «зонтичные» требования;
- оформим границы через DDD bounded contexts и интерфейсы портов/адаптеров;
- научимся фиксировать решения на диаграммах C4 (L1–L2) и в ADR;
- дадим пошаговый рецепт декомпозиции от бизнес-возможностей до контейнеров;
- подготовим практику и шаблоны для дальнейших модулей.
Стили архитектуры: когда и зачем
Монолит
- Что это: одно деплой-юнит приложение (часто один репозиторий/процесс), общая БД.
- Плюсы: простая отладка/транзакции, минимальные накладные в DevOps, хороший старт «с нуля».
- Минусы: со временем «комбайн» со связанностями; релизы «всем скопом»; масштабирование — целиком.
- Когда: ранние стадии продукта, одна команда, быстро меняющиеся требования, нет жёстких NFR по масштабированию.
Модульный монолит
- Что это: один деплой-юнит, но жёстко выделенные модули (границы + интерфейсы), «своя» схема данных на модуль, запрет сквозных импортов/SQL.
- Плюсы: дисциплина доменных границ, локальные изменения/тестирование, низкая DevOps-сложность, шаг к микросервисам.
- Минусы: всё ещё общий релиз/частые зависимости, нужны «охранные практики» (арх.-тесты, линтеры).
- Когда: 2–4 команды, растёт код/домены, вы хотите «контролируемый монолит» и возможность Strangler-миграций.
Микросервисы
- Что это: набор независимо деплоющихся сервисов вокруг доменных возможностей; БД на сервис; коммуникации по API/событиям.
- Плюсы: независимые релизы, масштабирование «по горячим точкам», изоляция отказов (при грамотной топологии).
- Минусы: сложность распределённых систем: сети, согласованность, трассировка, схема-регистры, наблюдаемость, рост накладных.
- Когда: зрелый продукт, много команд, высокие NFR (масштаб/надёжность), плотная DevOps-культура, готовность к EDA/CI-CD/обсервабилити.
Выбор стиля — через призму NFR и оргструктуры
- Драйверы: независимое развертывание, пиковые нагрузки, критичность отказа, требования к изоляции данных/комплаенс, частота изменений.
- Conway’s Law / Team Topologies: команда (stream-aligned) должна владеть сервисом/модулем end-to-end; cognitive load ≤ «две пиццы».
- DORA-метрики: частота деплоев, lead time, MTTR, change failure rate — при микросервисах улучшатся только при зрелых практиках.
Порты-адаптеры (Hexagonal/Чистая/Луковая архитектура)
Идея
- Доменное ядро (use-cases + модель) не знает об инфраструктуре.
- Входные порты (inbound) — интерфейсы сценариев (команды/запросы REST/gRPC/UI/CRON).
- Выходные порты (outbound) — интерфейсы к внешним зависимостям (репозитории, шины, PSP).
- Адаптеры — конкретные реализации портов (HTTP-контроллер, JPA/SQL, Kafka-producer).
Что это даёт SA
- Контракты на границах: вы чётко фиксируете вход/выход каждого модуля/сервиса.
- Упрощённое тестирование: домен тестируется без сети/БД.
- Лёгкая миграция: смена БД/шины без переписывания домена.
Мини-пример портов (псевдокод)
InboundPort: CreatePayment { execute(orderId, amount) -> Payment }
OutboundPort: PaymentsGateway { capture(payment) -> CaptureResult }
OutboundPort: PaymentRepository { save(payment), findById(id) }
В контейнерной C4-диаграмме входные порты представлены контроллерами/handlers, выходные — клиентами БД/шины/партнёра.
Границы контекстов (DDD) и карта контекстов
Bounded Context
- Смысл: область, где терминология и модели единообразны. «Order» в Checkout ≠ «Order» в WMS.
- Признак качества: изменения проходят локально, без «эффекта домино».
Context Map (отношения)
- Upstream / Downstream, Customer/Supplier — кто диктует язык.
- Published Language — общий язык договора (события/контракты).
- Anti-Corruption Layer (ACL) — «переводчик» между моделями.
- Open Host Service — унифицированный API поставщика.
Как SA очерчивает границы (чек-лист)
- Из бизнес-возможностей (Capabilities): «Заказы», «Платежи», «Каталог», «Доставка».
- По разным ритмам изменений и владельцам (разные продуктовые команды).
- По данным и инвариантам (кто «источник правды» за сущность).
- По NFR/секьюрности (PCI-зона — отдельно).
- По интеграциям (частые синхронные цепочки → возможная консолидация).
Пошаговая декомпозиция (рецепт для SA)
- Соберите цели и NFR (latency, availability, throughput, data residency).
- Составьте Capabilities Map → сгруппируйте по потокам ценности (customer journey).
- Выделите bounded contexts и источники правды по сущностям.
- Нарисуйте C4 Level-1 (Context): акторы, внешние системы, «наша система».
- Определите интеграции (Sync/Async, события/команды, см. модули 3.3–3.5).
- Разложите на контейнеры (C4-L2): веб/мобайл, API-шлюз, доменные сервисы, БД/шины/кеши.
- Опишите порты/адаптеры каждого контейнера (вход/выход).
- Зафиксируйте риски и ADR (варианты, решение, последствия).
- Проверьте «сдвиг вправо»: observability, фичефлаги, тестовые среда/данные.
- Согласуйте с командами (Conway): одна команда — один контекст/сервис/модуль.
Разделение данных и транзакций
- БД на сервис: каждый сервис — владелец своих таблиц; чужие данные только через API/события.
- Согласованность: избегайте распределённых транзакций; используйте саги (TCC/компенсации) и outbox+CDC.
- Кэш/реплики: чтение чужих данных — через read-модели (CQRS) или материализованные представления по событиям.
- Границы транзакций совпадают с границами сервисов.
Типовые риски и барьеры
|
Риск |
Симптом |
Меры |
|---|---|---|
|
Ранний микросервисинг |
много сервисов, одна команда, релизы буксуют |
начните с модульного монолита, выделяйте сервисы по мере роста команд/NFR |
|
Распределённый монолит |
общая БД, сервисы жёстко связаны |
«БД на сервис», ACL/Published Language, уберите cross-DB запросы |
|
Чаттеринг (слишком много sync-вызовов) |
длинные цепочки, хвосты латентности |
агрегируйте вызовы, async события, кеш, API-композиции |
|
Схема-хаос |
клиенты падают при изменениях |
registry, semver, consumer-driven contracts, параллельная публикация v1/v2 |
|
Отсутствие наблюдаемости |
инциденты «вслепую» |
trace (traceparent), метрики p95/err, структурные логи, кореляция |
|
PCI/GDPR утечки |
PII в событиях/логах |
минимизация полей, токенизация, шифрование, сегментация зон |
C4: как оформлять архитектуру
Уровни C4 (кратко)
- Level 1 (Context): «кто с кем разговаривает» — акторы, внешние системы, наша система как чёрный ящик.
-
Level 2 (Container): «из чего состоит система» — приложения/сервисы, БД, шины, связи и протоколы.
(Уровни 3–4 — компоненты/код — в этом модуле не нужны.)
Правила для SA
- На каждой стрелке: протокол/стиль (REST/gRPC/Event), направление, назначение.
- На узле: ответственность/владение, важные NFR (латентность/HA), технологические пометки.
- Без UI-пиктограмм и «красивостей» — только смысл, который можно проверить.
Кейc: e-commerce «Заказ–Платёж–Доставка» (референс C4 L1–L2)
C4 Level-1 (Context) — PlantUML (C4-PlantUML)
@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
Person(customer, "Покупатель", "Оформляет и оплачивает заказ")
System_Ext(psp, "Платёжный провайдер", "Авторизация/списание")
System_Ext(wms, "Склад/WMS", "Отгрузка и трекинг")
System_Ext(email, "Email/SMS провайдер", "Уведомления")
System_Boundary(sys, "Интернет-магазин") {
System(system, "Commerce Platform", "Витрина, чекаут, платёж, доставка")
}
Rel(customer, system, "Веб/Мобайл UI")
Rel(system, psp, "REST/gRPC: авторизация/капчур", "OAuth2 MTLS")
Rel(system, wms, "События: OrderShipped/Delivered", "Kafka, Avro")
Rel(system, email, "Webhooks/SMTP", "Подписанные webhooks")
@enduml
C4 Level-2 (Container)
@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
Person(customer, "Покупатель")
System_Boundary(sys, "Commerce Platform") {
Container(web, "Web App", "Next.js", "UI")
Container(mobile, "Mobile App", "iOS/Android", "UI")
Container_Boundary(edge, "Edge/GW") {
Container(api, "API Gateway", "Nginx/Kong", "Routing, auth, rate limit")
}
Container(checkout, "Checkout Service", "JVM/Node", "Заказы/чекаут (Hexagonal)")
Container(pay, "Payments Service", "JVM/Go", "Авторизация/списание, идемпотентность")
Container(ship, "Shipping Service", "Python/Go", "Оркестрация отгрузок")
ContainerDb(dbOrder, "Orders DB", "PostgreSQL", "Схема orders")
ContainerDb(dbPay, "Payments DB", "PostgreSQL", "Схема payments (PCI-зона)")
ContainerQueue(bus, "Event Bus", "Kafka", "Доменные события (Avro)")
Container(extEmail, "Notification Worker", "Node/Python", "Email/SMS отправка")
}
Rel(customer, web, "HTTP/S")
Rel(customer, mobile, "HTTP/S")
Rel(web, api, "REST")
Rel(mobile, api, "REST")
Rel(api, checkout, "REST/gRPC", "Idempotency-Key")
Rel(checkout, dbOrder, "JDBC/SQL")
Rel(checkout, pay, "REST/gRPC", "Create/Capture, 2.5s timeout")
Rel(pay, dbPay, "JDBC/SQL (PCI)")
Rel(pay, bus, "Publish PaymentCaptured", "Outbox+CDC")
Rel(checkout, bus, "Publish OrderCreated/OrderPaid")
Rel(ship, bus, "Consume OrderPaid → arrange shipment")
Rel(ship, wms, "REST", "Create shipment")
Rel(extEmail, bus, "Consume events → notify")
@enduml
Грани контекстов: Checkout, Payments, Shipping — разные bounded contexts. Payments — в PCI-сегменте (изолированная сеть/секьюрити). Источник правды: заказы — Orders DB; платежи — Payments DB.
Практические примеры решений и «тонкие места»
Стратегия миграции: «Strangler Fig»
- Начинаем с модульного монолита (Checkout/Payments/Shipping как модули с портами).
- Выносим Payments в отдельный сервис (PCI, NFR) → замещаем адаптер входного порта удалённым вызовом.
- Событийная интеграция через outbox из монолита, затем — шина.
Саги для «Оплаты–Отгрузки»
- Try: авторизовать платёж, заблокировать товар.
- Confirm: капчур, создать отгрузку.
- Cancel: отменить авторизацию, снять бронь.
- Оркестрация в Checkout / или хореография по событиям (см. 3.3/3.5).
Борьба с «чрезмерной чатомностью»
- Композиция API на стороне Gateway («backend-for-frontend»).
- GraphQL для мобильных экранов (персонализированные поля).
- Кэш чтений и материализованные представления по событиям.
Артефакты модуля
Контекстная диаграмма C4 (L1)
— акторы, внешние системы, наша система как чёрный ящик, связи и протоколы.
Диаграмма контейнеров C4 (L2)
— контейнеры (веб/мобайл, шлюз, доменные сервисы, базы, шина), протоколы и владельцы.
ADR (Architectural Decision Record) — шаблон
# ADR-00X: <Короткое название> Статус: Proposed/Accepted/Deprecated Контекст: <какие силы/ограничения действуют> Решение: <что выбрали и почему> Последствия: <плюсы, минусы, что теперь обязательно> Альтернативы: <что рассматривали и почему отвергли> Ссылки: <C4, OpenAPI/AsyncAPI, POC>
Практика: «C4 Level 1–2 для кейса» (90–120 мин)
Вход: домен (выберите один):
A) Интернет-магазин (как в примере), B) Платёжный сервис, C) ERP-заказы + склад.
Задачи:
- Нарисуйте C4-L1: акторы, внешние системы, контексты взаимодействий.
- Нарисуйте C4-L2: контейнеры (UI, Gateway, доменные сервисы/модули, БД, шины), подпишите протоколы и владельцев.
- Отметьте bounded contexts и источники правды (у кого чьи данные).
- Для 2 самых рискованных связей — заполните ADR (sync vs async; БД на сервис; outbox).
- Кратко опишите порты/адаптеры для одного сервиса (входные/выходные).
Критерии зачёта:
- Ясные границы контекстов; нет «общей БД».
- На стрелках указаны протоколы/стиль и назначение.
- Видна стратегия согласованности (саги/outbox), NFR отмечены у критичных контейнеров.
- ADR фиксирует компромиссы и последствия решения.
Вопрос–Ответ
Q: С чего начать: микросервисы или модульный монолит?
A: В 80% случаев — модульный монолит. Границы/порты закладывайте сразу; выносите сервисы по мере роста команд и NFR.
Q: Нужна ли «БД на сервис» всегда?
A: Для владения — да. Для чтений используйте проекции/реплики/кеш. Прямые cross-DB join’ы между сервисами — запрещены.
Q: Как понять «хорошую» границу?
A: Изменение проходит внутри границы с одной причиной изменения (SRP), не требует синхронной цепочки вызовов >2–3 сервисов, владелец данных очевиден.
Q: Можно ли начинать с GraphQL-шлюза?
A: Да для BFF (мобайл/веб), но доменные границы и сервисные контракты держите REST/gRPC/события. В GraphQL следите за лимитами (depth/complexity).
Q: Что делать с длинными процессами?
A: Саги (оркестратор/хореография), асинхронные события, таймауты/компенсации, наблюдаемость процессов.
Q: Как защититься от «цепных» падений?
A: Таймауты/CB, bulkhead (изоляция ресурсов), идемпотентность, ретраи только retryable, деградации/кэши, асинхронные очереди.
Шпаргалка (коротко)
- Начинайте с модульного монолита + порты/адаптеры; организуйте код по bounded contexts.
- БД на сервис; согласованность через события/саги/outbox.
- C4-L1/L2 — обязательны; на стрелках протокол и смысл.
- Следите за NFR: латентность, доступность, изоляция отказов, безопасность.
- Конвей законс — «структура команд формирует архитектуру»: один контекст/сервис — одна команда.
- Fix decisions в ADR; без наблюдаемости (traces/metrics/logs) микросервисы не взлетают.



