7. Процессы
Процесс — это упорядоченная, возможно ветвлёная последовательность вызовов интерфейсов. Он отражает бизнес- и технические потоки: оформление заказа, обработчик вебхука, ежедневное пакетное задание, последовательность онбординга пользователя.
Процессы — основной источник информации о зависимостях в Archlang. Стрелки между модулями на ваших диаграммах выводятся из шагов процессов. Удалите процесс — стрелки, которые он подразумевал, исчезнут. Отдельной операции «нарисовать зависимость» не существует.
process Checkout { Customer > Orders.CreateOrder Orders > Inventory.Reserve Orders > Payments.Authorize Payments > Ledger.Record Orders > Notifications.SendEmail}Пять шагов; четыре задействованных модуля; четыре выведённые стрелки зависимостей (Customer → Orders, Orders → Inventory, Orders → Payments, Payments → Ledger, Orders → Notifications).
Форма шага
Каждый шаг имеет форму:
Caller > Callee.Interface- Caller — модуль (сущность, выполняющая вызов). Слева от
>. - Callee — интерфейс (обработчик, который запускается). Справа от
>.
Стрелка > читается как «вызывает». Является ли нижележащий транспорт синхронным, асинхронным или каким-то ещё, определяется видом интерфейса на принимающей стороне. С базовым видом interface язык трактует каждый вызов единообразно; виды из стандартной библиотеки (command, event, …, Глава 11) добавляют различия sync/async и стилизацию рёбер.
Ошибка, которой стоит избегать. Поместить модуль с правой стороны.
Payments > Orders— ошибка разбора; правая сторона должна разрешаться в интерфейс, не в модуль. Валидатор это ловит и сообщает, что там вместо него.
Размещение
Процессы могут жить в трёх местах, все семантически эквивалентные:
Верхний уровень. Отдельно стоящая оркестрация:
process CheckoutFlow { Customer > Orders.CreateOrder Orders > Payments.Authorize}Внутри тела модуля. Принадлежит этому модулю; полностью квалифицированное имя становится Owner.Process:
module Orders { process Checkout { User > Orders.OrdersResource.Add }}Верхний уровень с in. Объявляется плоско, прикрепляется к телу модуля. Зеркалит модульный синтаксис in:
process Fulfillment in Orders { Orders > Shipping.Schedule}Форма in позволяет каждой команде объявлять процессы для модуля другой команды, не редактируя файл этой команды.
Ветвления: if / switch
Две формы условий. Используйте if для двоичных или коротких цепочек ветвей; используйте switch, когда одно значение-голова выбирает вариант из известного набора.
process Checkout { Customer > Orders.CreateOrder Orders > Payments.Authorize
if "stripe-customer" { Payments > Stripe.Charge } else { Payments > PayPal.CreateOrder PayPal > PayPal.CaptureOrder }
Payments > Ledger.Record}if cond Body (else if cond Body)* (else Body)?. Условие — идентификатор или строка в кавычках; тело — блок в фигурных скобках.
process Checkout { Customer > Orders.CreateOrder Orders > Payments.Authorize
switch "payment processor" { stripe { Payments > Stripe.Charge } paypal { Payments > PayPal.CreateOrder PayPal > PayPal.CaptureOrder } }
Payments > Ledger.Record}switch [head] { case-label Body case-label Body ... }. Никакого ключевого слова case — каждый блок начинается со своей метки. Валидатор считает каждый вариант достижимым; граф зависимостей включает каждый интерфейс, упомянутый в любом из вариантов.
Параллельность: parallel
Конкурентные шаги используют parallel:
process OrderFulfillment { Orders > Shipping.CreateShipment
parallel { { Shipping > Notifications.SendEmail } { Shipping > Notifications.SendSMS } }
Shipping > Orders.UpdateOrder}Ветки внутри parallel исполняются конкурентно. Каждая ветка — это явный блок в фигурных скобках (или : oneliner для одиночных шагов). Рендерер может разложить их бок о бок, а не последовательно.
Итерация: each
each Item in Collection { ... } итерирует по коллекции элементов. Используется для веерного распространения по множеству неизвестной мощности.
process NotifyAll { each subscriber in NotificationList { Notifications > subscriber.SendUpdate }}Пути ошибок: try / catch
try Body (catch [Label] Body)* для явных путей ошибок. Допускается несколько catch; метка у каждого необязательна.
process Checkout { Customer > Orders.CreateOrder try { Orders > Payments.Authorize } catch "declined" { Orders > Notifications.SendEmail }}Подпроцессы
Повторяющиеся последовательности шагов становятся подпроцессами — переиспользуемыми помощниками, вызываемыми через do:
subprocess RecordEvent(name) { Orders > Ledger.Record}
process Checkout { Customer > Orders.CreateOrder do RecordEvent("checkout_started") Orders > Payments.Authorize do RecordEvent("payment_authorized")}Подпроцессы могут быть верхнего уровня (видны везде), объявленными внутри тела модуля (ограничены процессами этого модуля) или объявленными внутри тела типа (отштамповываются на каждый экземпляр — см. Глава 18).
Порядок поиска при выполнении do X(...):
- Подпроцессы, объявленные лексически внутри вызывающего процесса.
- Подпроцессы на владеющем модуле (побеждает ближайший предок).
- Отдельно стоящие подпроцессы верхнего уровня.
Стабильные идентификаторы
Процессы несут стабильные идентификаторы так же, как и модули:
process #flow42 Checkout { Customer > Orders.CreateOrder}Форматтер выдаёт #flow42 при сохранении. Идентификатор позволяет диффам распознавать переименованный процесс. См. Главу 13.
Валидация
Парсер и валидатор обеспечивают инварианты процессов:
- Вызывающий (слева от
>) должен разрешаться в модуль (включая stdlib-модулиactorпосле импорта стандартной библиотеки — см. Главу 11). - Вызываемый (справа от
>) должен разрешаться в листовой интерфейс (не в фасет, не в модуль). subscribes:, указанный на обработчике, должен указывать на существующее событие.- Вызов
do Xдолжен разрешаться по порядку поиска подпроцессов.
Сбои появляются как диагностики LSP в вашем редакторе и как ошибки с кодом 1 от archlang validate.
Что вам дают процессы
Когда у вас есть процессы, автоматически следует несколько вещей:
- Граф вызовов сервисов. Модули, связанные рёбрами
Caller > Callee.Interfaceпо всем вашим процессам. - Критические пути. Поток запроса в десять шагов виден с одного взгляда.
- Радиус поражения. Когда один сервис падает, какие процессы ломаются? Тривиально выводится.
- Влияние изменений. Когда вы удаляете интерфейс, валидатор сообщает вам про каждый шаг процесса, который от него зависел.
Ничто из этого не требует поддерживать отдельный каталог зависимостей. Каталог — это объединение каждого шага процесса в рабочем пространстве.
Итоги
- Процесс — последовательность шагов
Caller > Callee.Interface. - Вызывающие — модули или акторы; вызываемые — листовые интерфейсы.
- Ветвления (
if/switch), параллельность (parallel), итерация (each) и пути ошибок (try/catch) — первоклассные. - Подпроцессы — переиспользуемые помощники, вызываемые через
do. - Процессы могут жить на верхнем уровне, внутри тела модуля или присоединяться через
in. - Граф зависимостей выводится из процессов; стрелки отдельно не рисуются.
Что дальше
Глава 8: Проекции → — кураторские проекции модели для конкретных аудиторий.