Это первый из шести разобранных проектов в части VI. Каждая глава берёт конкретную форму системы и моделирует её с нуля — модули, интерфейсы, процессы, проекции и (где это уместно) типы. Цель не в том, чтобы научить возможностям, описанным в предыдущих главах; цель — показать, как полная модель выглядит на практике и какие решения вы будете принимать, когда будете строить свою.
Система этой главы: небольшой SaaS-бэкенд. Аутентификация, управление заказами, платежи, уведомления. В основном синхронные вызовы с асинхронным событийным позвоночником. Шесть модулей, четыре процесса, две проекции. Примерно размер продуктового бэкенда стартапа на ранней стадии.
Два домена: коммерция (Orders, Inventory, Payments, Ledger) и платформа (Auth, Notifications). Один асинхронный переход — события заказов на Orders расходятся к обработчику на Notifications.
command Capture { "Capture an authorized payment." }
command Refund { "Issue a refund." }
}
service Ledger {
team: Finance
labels {
domain: Finance
security.zone: Internal
}
"Immutable financial record of every transaction."
command Record { "Record a financial event." }
}
Шесть модулей. Обратите внимание на распределение команд: Platform, Commerce, Payments, Finance. Обратите внимание на метки — domain и security.zone каскадируют на каждый интерфейс внутри; мы обопрёмся на это в проекциях.
Ошибки, которых стоит избегать. Не пытайтесь моделировать “аутентификацию”, добавляя поле requiresAuth: true к каждой команде. Аутентификация — это забота вызывающей стороны (актора или вызывающего в процессе), а не получателя. Модель фиксирует кто кому звонит через процессы; несёт ли этот вызов токен сессии — это реализация, а не архитектура.
Процессы
processes.arch:
process#flow01 SessionStart {
Customer > Auth.Login
Auth > Auth.SessionEvents
}
process#flow02 Checkout {
Customer > Auth.GetSession : "validate session"
Customer > Orders.CreateOrder
Orders > Inventory.Reserve
Orders > Payments.Authorize
switch"payment outcome" {
authorized {
Orders > Payments.Capture
Payments > Ledger.Record
Orders > Orders.OrderEvents : "OrderPaid"
}
declined {
Orders > Inventory.Release
Orders > Orders.OrderEvents : "OrderFailed"
}
}
}
process#flow03 Cancellation {
Customer > Auth.GetSession
Customer > Orders.CancelOrder
Orders > Inventory.Release
Orders > Payments.Refund
Payments > Ledger.Record
Orders > Orders.OrderEvents : "OrderCancelled"
}
Checkout — центральный бизнес-поток. switch "payment outcome" фиксирует двухстороннее ветвление при авторизации — каждая case-метка (authorized, declined) именует ветку. Метки после двоеточия (: "validate session", : "OrderPaid") аннотируют шаги для читаемости диаграммы, не меняя семантику.
Разветвление уведомлений разведено декларативно — Notifications.SendEmail несёт subscribes: Orders.OrderEvents, которое живёт на декларации интерфейса в platform.arch. Явный процесс не нужен; рендерер диаграммы соединит событие с его подписчиком.
Альтернатива: стиль явного процесса.
Если вы предпочитаете видеть разветвление как процесс ради лучшей обнаружимости на ревью, уберите subscribes: и добавьте:
Тот же архитектурный факт, другая поверхность. Выбор — вопрос вкуса: явные процессы лучше обнаруживаются; subscribes: меньше дублируется. Валидатор и рендерер принимают оба варианта.
"End-to-end customer journey. Used in onboarding and architecture reviews."
}
view PCIScope {
focus security.zone: PCI
groupby team
layout dagre
"Every service in PCI scope. For compliance review."
}
Две проекции. Первая — путь клиента целиком, сгруппированный по командам, — то, что вы показали бы при введении в проект. Вторая изолирует подмножество, входящее в зону PCI, для compliance-ревью; с расставленными метками это буквально focus security.zone: PCI, остальное делает рендерер.
Решения, с которыми вы столкнётесь, моделируя своё
Гранулярность модулей. Должен ли Orders быть одним модулем или несколькими? Правило: если у него одна команда и одна граница — один модуль. Команда может разбить его внутри через под-модули component позже (см. стандартный вид component в Главе 11) без изменения внешней поверхности.
Sync vs async. Используйте command и query для синхронных вызовов; используйте event (с subscribes: на обработчике) для асинхронных. Смешивать их в одном интерфейсе неправильно — вид передаёт форму вызова.
Где располагать метки. Метки идут на тот контейнер, который лучше всего описывает область классификации. domain и security.zone принадлежат модулям. criticality может принадлежать отдельным интерфейсам (у сервиса может быть одна критичная команда и двадцать справочных запросов). Если сомневаетесь — поставьте на более широкую область и переопределите вниз.
Гранулярность процессов. Один процесс на один бизнес-поток. Если у потока есть чистое условное разделение, используйте if или switch. Если есть независимая параллельная работа, используйте parallel. Используйте try / catch для явных путей ошибок. Не пытайтесь закодировать каждый путь ошибки; фиксируйте счастливый путь и основные альтернативы — исчерпывающая обработка — это работа валидатора, а не модели.
Внешние акторы.Customer здесь — actor из стандартной библиотеки. Для B2B-интеграций или сторонних вызывающих объявляйте их как external_system или actor и ставьте слева от шагов процесса. Они никогда не появляются справа (потому что они не выставляют интерфейсы вашей модели).
Что это вам даёт
После валидации:
Диаграмма с шестью сервисными узлами и актором, рёбра выведены из четырёх процессов, группировка по командам.
Вторая диаграмма, отфильтрованная по зоне PCI.
Валидация того, что каждый шаг процесса ссылается на реальный интерфейс.
Движок диффов, распознающий переименования, если вы, скажем, позже переименуете Orders в OrderService.
Описания с markdown во всплывающих подсказках, с кросс-ссылками [[Notifications]] и интерполяцией @security.zone, если вы её добавите.
Итого исходник: примерно 120 строк .arch в четырёх файлах. Это вся форма системы, в тексте, в системе контроля версий.
Итог
Моделируйте свой домен, сначала записывая модули, затем рисуя процессы между ними. Стрелки на диаграмме появятся следом.
Группируйте модули по командам и по доменам через метки; проекции разрезают модель по этим осям.
Используйте command/query для синхронных вызовов, event+subscribes: для асинхронных; выбирайте между стилем явных процессов и разводкой через подписки исходя из того, что яснее в вашей кодовой базе.
От трёх до шести модулей на файл, разбиение по доменам — комфортная плотность.
Проекция PCI получается автоматически, как только security.zone: PCI стоит на нужных модулях — метки делают работу.
Что дальше
Глава 26: Событийный конвейер → — другая топология: очереди, потоки, разветвление и сборка, разводка через подписки как основной механизм интеграции.