Представления процесса
Процесс — это одна модель: Глава 7 разбирает шаги, ветвления, саги. Но аудитория, которая его читает, меняется. Новому сотруднику нужна история: кто кого вызывает, по порядку, от начала до конца. Ревьюеру API нужно, чтобы парность запрос/ответ была сделана явной — линии жизни, а не проза. Специалисту Ops, размечающему дорожками BPMN-диаграмму для runbook’а, нужно знать, какая команда владеет какой полосой диаграммы и какие команды объединяются в один пул.
Ничто из этого — не три разных процесса. Это один и тот же Checkout, отрендеренный тремя способами. Проекция flow (из Главы 8) выбирает режим; сам процесс под ней никогда не меняется.
Три режима
Заголовок раздела «Три режима»flow <ProcessRef> [sequence | bpmn] принимает токен режима. Обычная форма (без токена) — это пошаговый обход потока: форма из Главы 7, сверху вниз. sequence рендерит тот же поток управления как диаграмму последовательности в стиле UML: по одной линии жизни на участника, сообщения — стрелки между ними — форма, которую ревьюер API уже читает бегло, где парность запрос/ответ визуально явна там, где обход оставляет её подразумеваемой. bpmn рендерит BPMN-коллаборацию: исполнители рассортированы по горизонтальным дорожкам (swimlanes), дорожки при желании группируются в пулы — форма, которую Ops-инженеры и бизнес-стейкхолдеры ожидают от диаграммы процесса.
Любой режим рендерит разрешённые шаги, поэтому строчные глифы из Главы 7 не оставляют собственного следа: веер | рисуется вторым ребром от общей головы, змейка \ — следующим переходом прогона (узел-хвост не повторяется), а каждая написанная строка сохраняет ровно одну строку в дереве и боковой панели.
Режим — это истина представления: вы пишете тот режим, в котором намерены поставить проекцию, точно так же, как пишете show/hide для проекции доски:
service Orders { aspect { team: "Commerce" domain: "Storefront" }}
service Payments { aspect { team: "Payments" domain: "Backend" }}
service Inventory { aspect { team: "Fulfillment" domain: "Backend" }}
service Notifications { aspect { team: "Platform" domain: "Backend" }}
process Checkout { Customer > Orders.createOrder Orders > Inventory.reserve Orders > Payments.authorize
Payments if "flagged" { Ops "manual fraud review" }
Orders > Notifications.sendEmail}
view CheckoutWalkthrough { "Checkout for the onboarding deck — the flow walkthrough, plain and linear" flow Checkout}
view CheckoutApiReview { "Checkout for an API review — lifelines make request/response pairing explicit" flow Checkout sequence}
view CheckoutOps { "Checkout as a BPMN collaboration, laned by team and pooled by domain" flow Checkout bpmn { lane by @@team pool by @@domain }}Три проекции, один process Checkout. Измените шаг в процессе — и все три перерендерятся из него: синхронизировать нечего, потому что отличается только режим.
Каждый режим — это один из четырёх видов представления, которые может нести проекция (board, table, matrix, flow — Глава 8), так что проекция flow по-прежнему не может одновременно объявлять table или matrix; ссылка пишется явно (flow Checkout, никогда не выводится из show) и именует процесс или подпроцесс — всё, что имеет рендерящийся поток.
Дорожки — сортировка исполнителей по полосам
Заголовок раздела «Дорожки — сортировка исполнителей по полосам»Необязательное тело режима bpmn сортирует исполнителей — владельцев владеемых действий, Глава 7 — по дорожкам. Вызываемая сторона, до которой добираются только как до цели (никогда как до владельца), остаётся текстом-меткой задачи — своей полосы она не получает.
lane <selector> принимает любой селектор из языка селекторов и даёт одну дорожку на каждого подошедшего исполнителя — lane service на модели выше открыла бы четыре дорожки: по одной для Orders, Payments, Inventory, Notifications. Снабдите селектор заголовком — и несколько совпадений вместо этого сольются в одну дорожку: lane "Backend": Payments or Inventory or Notifications.
Захваты действуют по правилу кто раньше, тот и забрал, в порядке документа — исполнитель, уже размещённый более ранней клаузой lane, не подхватывается более поздней, даже если её селектор иначе бы подошёл:
service Orders {}service Payments {}service Inventory {}service Notifications {}
process Checkout { Customer > Orders.createOrder Orders > Inventory.reserve Orders > Payments.authorize Orders > Notifications.sendEmail}
view CheckoutOps { "first-wins: Payments is claimed by the first clause, so the titled lane below picks up only the two performers still unclaimed" flow Checkout bpmn { lane Orders or Payments // две дорожки: Orders, Payments lane "Backend": Payments or Inventory or Notifications // Payments уже захвачен — присоединяются только Inventory и Notifications }}Orders or Payments без заголовка, поэтому она открывает две дорожки, а не одну. К моменту, когда выполняется клауза с заголовком "Backend", Payments уже занят, так что эта дорожка в итоге содержит только Inventory и Notifications — двух исполнителей, ещё остававшихся свободными.
Для распространённого случая, когда дорожки должны следовать за аспектом, а не выбираться вручную, lane by <getter> автоматически открывает одну дорожку на каждое отдельное значение — именно это делал lane by @@team во флагманском примере выше: одна дорожка на команду, без необходимости перечислять исполнителей вручную по мере того, как команды появляются и исчезают.
Дорожка по умолчанию и актёры-персоны
Заголовок раздела «Дорожка по умолчанию и актёры-персоны»Ноль клауз lane — это рендер без дорожек: одна дорожка на исполнителя, как если бы lane применялась ко всем сразу. При наличии хотя бы одной клаузы lane неявная дорожка по умолчанию подхватывает всё, что осталось незахваченным: без заголовка, рендерится последней и показывается только когда непуста. Работа без владельца — шаг, чей владелец так и не разрешился, — тоже попадает туда, так что неназначенная работа видимо остаётся неразмещённой, а не тихо теряется.
Актёр-персона — имя владельца, которое встречается только в заметочном шаге или в префиксе потока управления, но никогда как реальный вызывающий, — не несёт аспектов, потому что не является смоделированным элементом. lane by @@team никогда её не подхватит: атомы аспектов, геттеры by и запросы пулов по отношению к персонам fail-closed (отказывают по умолчанию). Единственный способ разместить персону на дорожке — голая ссылка или or-объединение ссылок:
service Orders {}service Payments {}
process Checkout { Customer > Orders.createOrder Orders > Payments.authorize
Payments if "flagged" { Ops "manual fraud review" }}
view CheckoutOps { "@@team never matches Ops — it's a persona, not a modeled service, so it falls into the default lane unless claimed by bare ref" flow Checkout bpmn { lane by @@team }}
view CheckoutOpsClaimed { flow Checkout bpmn { lane by @@team lane Ops }}CheckoutOps располагает Orders и Payments по дорожкам согласно команде и сбрасывает Ops в дорожку по умолчанию — у Ops нет аспекта team, по которому можно было бы сопоставить. CheckoutOpsClaimed добавляет lane Ops — явный захват голой ссылкой, — и Ops вместо этого получает собственную полосу.
Пулы — группировка дорожек
Заголовок раздела «Пулы — группировка дорожек»Пул группирует дорожки по запросу над их исполнителями, по единственному правилу: дорожка входит в пул ровно тогда, когда каждый исполнитель на этой дорожке подходит под запрос пула. Это же правило покрывает и дорожку по умолчанию — она входит в пул ровно тогда, когда под запрос подходит всё незахваченное. Побеждает первый подошедший пул, в порядке объявления; пул, в котором в итоге не осталось ни одной дорожки, из рендера выпадает.
Три способа записать запрос:
pool <Ref>— сахар для<Ref> or in <Ref>. Пространство или модуль — естественный пул: в него входит каждая дорожка, чей исполнитель является этим контейнером или находится внутри него.pool "Title": <selector>— произвольный запрос, не привязанный ни к какому контейнеру. Заголовок здесь обязателен (пул без заголовка всегда принимает ссылку на элемент).pool by <getter>— один пул на каждое отдельное значение, выводится автоматически, зеркалитlane by.
system Backend { service Payments { aspect { team: "Payments" domain: "backend" } } service Inventory { aspect { team: "Fulfillment" domain: "backend" } } service Notifications { aspect { team: "Platform" domain: "backend" } }}
service Orders { aspect { team: "Commerce" domain: "storefront" }}
process Checkout { Customer > Orders.createOrder Orders > Inventory.reserve Orders > Payments.authorize Orders > Notifications.sendEmail}
view CheckoutBySpace { "Untitled pool sugar — pool Backend groups every lane whose performer sits inside the Backend system" flow Checkout bpmn { lane by @@team pool Backend }}
view CheckoutByQuery { "Titled pool — an arbitrary query over performers, not tied to a container" flow Checkout bpmn { lane by @@team pool "Backoffice": @@domain:"backend" }}
view CheckoutByDomain { "Auto pools — one per distinct @@domain value" flow Checkout bpmn { lane by @@team pool by @@domain }}Все три способа здесь приводят к одному результату — Payments, Inventory и Notifications объединяются в пул, Orders остаётся одна — тремя разными путями: структурная вложенность, запрос, написанный вручную, и автоматический вывод. Тянитесь к pool <Ref>, когда реальный контейнер уже существует, к pool by, когда группировка находится в одном аспекте, и к именованному запросу-пулу — на всё, что между ними.
Полосы компенсации остаются глобальными
Заголовок раздела «Полосы компенсации остаются глобальными»Цепочка unwind блока reversible (Глава 7) читается под своими прямыми шагами независимо от разметки по дорожкам — полосы компенсации рендерятся глобально, под каждой полосой дорожки, никогда не вкладываясь в строку одной команды:
process PlaceOrderSaga { dist reversible on error { Customer > Order.create unwind Customer > Order.cancel Order > TicketInventory.reserve unwind Order > TicketInventory.release TicketInventory > Payment.charge unwind TicketInventory > Payment.refund Payment > Badge.issue // terminal grant — nothing after to undo }}
view PlaceOrderSagaOps { "Compensation flow, laned by participant — the refund chain reads under its forward steps regardless of which lane is pooled" flow PlaceOrderSaga bpmn { lane Customer lane Order // TicketInventory and Payment stay unclaimed — the default lane }}У хореографированной саги (dist reversible) нет единого координатора — каждый участник сам является своей дорожкой: здесь Customer и Order захвачены явно, а TicketInventory и Payment попадают в дорожку по умолчанию. В какой бы пул ни вошла дорожка, полоса компенсации под ней остаётся на месте: цепочка возвратов, пересекающая границы команд, — это ровно тот случай, ради видимости которого и существует диаграмма с дорожками, поэтому рендер никогда не прячет её внутри строки одной команды.
Проекция-поток остаётся проекцией
Заголовок раздела «Проекция-поток остаётся проекцией»Всё, что было сказано про flow в Главе 8, по-прежнему верно. show/hide компонуются только для боковой панели и контекста — холст всегда рендерит весь указанный процесс целиком, никогда не отфильтрованный срез. style/lens нацеливаются на то, что реально входит во вселенную селектора: участвующие модули и интерфейсы, а также выведенные рёбра — никогда не шаги, которые не являются элементами модели и потому не стилизуемы. Постоянный комментарий к шагу (заметка, комментарий к вызову) живёт на самом процессе, версионируется вместе с ним, а не на проекции. on <plane> в проекции-потоке — это точечная диагностика: процесс уже несёт собственную плоскость, так что on нечего задавать. pin — тоже диагностика (раскладка определяется потоком); rename по-прежнему применим.
Тело bpmn легально только под токеном bpmn — если написано при обычном (plain) или sequence потоке, оно инертно с точечной диагностикой. «Дорожки» обычного и sequence-потока — это идентичность вызываемой стороны (кто на какой стороне вызова), другая ось, нежели полосы исполнителей, которые строит bpmn.
Headless-рендеринг — MCP, сервер, CLI
Заголовок раздела «Headless-рендеринг — MCP, сервер, CLI»Объявленная проекция-поток — та, что имеет имя, как CheckoutOps выше, — рендерится по имени с любой headless-поверхности, тем же резолвером, что использует живая вкладка:
- MCP:
arch_renderсviewName: "CheckoutOps". - Сервер:
GET /api/view/flow.svg?view=CheckoutOps(или.png). - CLI:
archlang render --out=checkout-ops.svg --view-name=CheckoutOps.
Проекция с ручками (knob) компонуется так же, как и экземпляр где угодно ещё (Глава 8) — привяжите $target к процессу, и ссылка на поток, и её тело bpmn обе прочтут ручку:
service Orders {}service Payments {}
process Checkout { Customer > Orders.createOrder Orders > Payments.authorize}
view TeamOps { knob process target knob module actor flow $target bpmn { lane $actor }}
view TeamOps PaymentsOps { target: Checkout actor: Payments}knob process target — это идиома параметризованного обхода: одна проекция flow $target, инстанцируемая по разу на каждый процесс, который её просят отрендерить; lane $actor привязывает ручку-элемент прямо в слот селектора дорожки. TeamOps PaymentsOps поставляется уже привязанной: --view-name=PaymentsOps рендерит Checkout, размеченный дорожкой вокруг Payments, и выбирать больше нечего.
- Один процесс, три режима: обычный (пошаговый обход потока),
sequence(линии жизни UML),bpmn(коллаборация с дорожками) —flow <ProcessRef> [sequence | bpmn]. Режим — это истина представления; сам процесс под ним никогда не меняется. - Тело
bpmnсортирует исполнителей по дорожкам (lane <selector>— по каждому совпадению,lane "Title": <selector>— слитно,lane by <getter>— автоматически) и группирует дорожки в пулы (pool <Ref>— сахар,pool "Title": <selector>— запрос,pool by <getter>— автоматически) — дорожка входит в пул ровно тогда, когда каждый её исполнитель подходит под запрос пула. - Захваты действуют по правилу «кто раньше, тот и забрал» в порядке документа; неявная дорожка по умолчанию подхватывает всё незахваченное и показывается только когда непуста; актёры-персоны (владельцы, отсутствующие в модели) захватываются только голой ссылкой, никогда — по аспекту или запросу пула.
- Полосы компенсации саги
reversibleрендерятся глобально, под каждой дорожкой, независимо от того, как дорожки объединены в пулы. - Проекция-поток остаётся проекцией:
show/hideограничивают только боковую панель,style/lensнацеливаются на модули и рёбра (никогда — на шаги), аon/pin— диагностики: процесс несёт собственную плоскость и раскладку. - Объявленная проекция-поток рендерится headless по имени —
viewNameв MCP,/api/view/flow.svg|png?view=на сервере,--view-nameв CLI — а экземпляр с ручками поставляется уже привязанным.
Что дальше
Заголовок раздела «Что дальше»Глава 34: Анализ задержек → — тот же граф процесса, свёрнутый в оценку системного/бизнес-времени.