Перейти к содержимому

Представления процесса

Процесс — это одна модель: Глава 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.

Объявленная проекция-поток — та, что имеет имя, как 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: Анализ задержек → — тот же граф процесса, свёрнутый в оценку системного/бизнес-времени.