5. Интерфейсы
Интерфейс — это именованная операция, которую модуль предоставляет другим модулям. Это единственный способ взаимодействия одного модуля с другим — процессы (Глава 7) ссылаются на интерфейсы, а не на модули напрямую.
module Payments { team: Payments
interface Authorize interface Capture interface GetTransaction interface PaymentEvents}Четыре интерфейса у Payments. У каждого есть вид (interface, базовый вид) и имя. Другие модули могут вызывать Payments.Authorize, делать запрос к Payments.GetTransaction, подписываться на Payments.PaymentEvents.
Эта глава использует базовый вид interface. Стандартная библиотека (Глава 11) вводит подтипы интерфейсов, такие как command, query, event и stream, которые добавляют семантические различия и стилизацию рёбер.
Базовый вид interface
interface — это базовый вид интерфейса. Он не несёт различия sync/async; он не управляет стилизацией рёбер и не подразумевает семантику подписки. Он просто объявляет: «этот модуль предоставляет операцию с именем X».
module Orders { interface CreateOrder interface GetOrder interface OrderEvents}Валидатор принимает объявление; рендерер диаграммы рисует ребро для каждого вызова, который процесс делает к Orders.CreateOrder и так далее. Является ли вызов синхронным RPC, асинхронной доставкой события или вызовом функции — для базового языка непрозрачно. Виды интерфейсов из стандартной библиотеки добавляют это различие.
Направление однозначно
Интерфейс всегда объявляется его поставщиком. Поставщик — это модуль, который его выполняет. Шаг процесса всегда имеет вид Caller > Callee.Interface — правая часть называет интерфейс (а значит и поставщика); левая часть называет того, кто вызвал.
process Checkout { Customer > Payments.Authorize // Customer calls Payments Payments > Ledger.Record // Payments calls Ledger}Вы никогда не объявляете «интерфейс, через который Customer общается с Payments». Интерфейсы существуют на поставщике. Вызывающие обращаются к ним по полному имени (ModuleName.InterfaceName).
Это исключает распространённую ошибку моделирования: рисование стрелки «Customer→Payments» без указания того, что именно вызывается. В Archlang так нельзя. Интерфейс должен сначала существовать на Payments; только тогда шаг процесса может его вызвать.
Тела
Тело интерфейса не обязательно. Если вам нечего добавлять, опустите его:
interface AuthorizeКогда тело нужно, чаще всего его содержимое — это описание и поля:
interface Authorize { "Authorize a payment hold. Returns a token used by Capture." timeout: "5s" idempotent: true}Внутри тела можно:
- Объявить описание (голую строку).
- Задать поля (
timeout: "5s",protocol: rest). - Добавить метки.
- Подключить
subscribes:(описано ниже).
Нельзя поместить интерфейс внутрь интерфейса. Интерфейсы — листовые. Если нужно сгруппировать несколько связанных интерфейсов, используйте фасет (Глава 6).
Подписки
Событийно-управляемые обработчики подключаются к источникам событий через subscribes:. Интерфейс-обработчик объявляет, что его запускает:
module Shipping { team: Fulfillment
interface CreateShipment { "Create a shipment when an order is confirmed" subscribes: Orders.OrderEvents }}CreateShipment запускается каждый раз, когда срабатывает Orders.OrderEvents. Подписка — это подключение на стороне обработчика, а не на стороне источника события. Источник (Orders.OrderEvents) не знает, кто его слушает; слушатель сам объявляет соединение.
Это важно для диаграмм. Рендерер может нарисовать стрелку «Orders → Shipping.CreateShipment через OrderEvents», соединяющую публикуемое событие с подписанным обработчиком, не засоряя ни одно объявление именем другого.
Подписки — это подключение, а не поток процесса. Процесс, в котором написано Orders > Shipping.CreateShipment, утверждает тот же сквозной факт — но форма с подпиской позволяет зависимости жить рядом с обработчиком, где ей логически место.
Для базового вида interface subscribes: принимается, но его семантика не ограничена — язык трактует каждый интерфейс единообразно. Вид event из стандартной библиотеки добавляет ограничение, что подписаться можно только на события (Глава 11).
Перекрёстные ссылки
Имена интерфейсов квалифицируются их модулем. Изнутри того же пакета можно обратиться к любому интерфейсу как Module.Interface (или Module.Facet.Interface, если он находится внутри фасета — см. Главу 6). Модули в пространствах имён используют тот же путь через точку: Personal.Banking.Payments.Authorize.
В описаниях (Глава 10) и в шагах процесса вы обращаетесь к интерфейсам по полному имени. Резолвер проверяет, что каждая ссылка указывает на реальный листовой интерфейс, а не на фасет или модуль. Если вы напишете process P { X > Payments } (модуль, а не интерфейс), валидатор это отвергнет.
Без идентификаторов у интерфейсов
Модули несут стабильные идентификаторы (#pay001); интерфейсы — нет. Их идентичность — это путь через точку внутри объемлющего модуля (Payments.Authorize). Переименования обнаруживаются эвристиками по структуре и форме контракта во время диффа — см. Главу 13 для компромисса.
Практическое следствие: не пишите префиксы #xyz у интерфейсов. Форматтер их не добавит, и в грамматике для них нет места — парсер их отвергнет.
Резюме
- Интерфейс — это именованная операция, объявляемая его поставщиком.
- Базовый вид —
interface. Подтипы из стандартной библиотеки (command,query,event,stream) добавляют семантические различия — см. Главу 11. - Направление в процессе всегда
Caller > Callee.Interface; интерфейс живёт на вызываемой стороне. - Интерфейсы — листовые: они не содержат других интерфейсов. Чтобы их сгруппировать, используйте фасеты.
subscribes:у интерфейса-обработчика подключает его к источнику событий на другом модуле.- У интерфейсов нет стабильных идентификаторов; идентичность — по пути через точку.
Что дальше
Глава 6: Фасеты → — как группировать интерфейсы внутри поверхности модуля.