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

6. Фасеты

Модуль с двадцатью интерфейсами в плоском списке нечитаем. Фасет — это именованная группировка интерфейсов (и вложенных фасетов) внутри тела модуля. Это исключительно средство организации: у фасетов нет семантики развёртывания, нет стабильных идентификаторов, нет присутствия за пределами модуля, в котором они живут.

module Orders {
team: Commerce
facet OrdersResource {
"Order CRUD operations"
base: "/orders"
interface Create
interface Get
interface Update
interface Delete
facet Products {
base: "/products"
interface Add
interface Remove
}
}
facet Webhooks {
interface Subscribe
interface Unsubscribe
}
}

Два фасета верхнего уровня на Orders: OrdersResource (со своим вложенным фасетом Products) и Webhooks. Каждый группирует интерфейсы под именем; каждый может нести поля, метки и описание.

Зачем нужны фасеты

Без фасетов каждый интерфейс живёт прямо в теле модуля. Это работает для небольших модулей. Для модулей с реальной публичной поверхностью — HTTP-сервис с двадцатью эндпоинтами, доменный сервис, группирующий операции по агрегату — плоская раскладка превращается в шум.

Фасеты дают вам место для того, чтобы:

  • Сгруппировать концептуально связанные интерфейсы (операции /orders вместе).
  • Прикрепить общие поля, например базовый путь, который должен применяться ко всему, что находится ниже.
  • Применить шаблон типа (Глава 16), чтобы можно было штамповать фасет crud, получающий фиксированный набор операций бесплатно.

Они не про развёртывание, владение или адресацию. Это относится к модулям.

Вложенность односторонняя

Правило. Модули содержат фасеты; фасеты не содержат модулей.

Фасет может содержать другие фасеты и интерфейсы. Он не может содержать модуль. Если вам нужен подмодуль внутри сервиса, объявите его как вложенный модуль (Глава 4), а не как фасет:

module Orders {
facet API {
interface Create // ✅ интерфейсы внутри фасета
}
module Worker { // ✅ вложенный модуль — НЕ внутри фасета
interface Process
}
// ❌ Это было бы ошибкой разбора:
// facet Bad {
// module Inside { ... }
// }
}

Причина — идентичность. Модули несут стабильные идентификаторы и представляют архитектурные элементы; фасеты организационны. Разрешение модулей внутри фасетов смешало бы эти роли.

Точечные пути

Шаги процессов и перекрёстные ссылки разрешаются через фасеты по точечным путям:

process Buy {
Customer > Orders.OrdersResource.Create
Customer > Orders.OrdersResource.Products.Add
Customer > Orders.Webhooks.Subscribe
}

Полный путь — Module.Facet.[Facet.]Interface. Фасеты прозрачны для резолвера — они организуют исходный текст, но не вводят отдельное пространство имён, которое нужно было бы обходить.

Виды фасетов

facet — единственный вид фасета, определённый в стандартной библиотеке. Вы можете объявлять собственные виды фасетов, когда предпочтительна доменно-специфическая лексика:

type facet resource {
"An HTTP resource — appends to a base path"
append base
}
module Orders {
resource OrdersResource {
base: "/orders"
interface Post
interface Get
}
}

resource теперь — определённый пользователем вид фасета, который ведёт себя как facet плюс поле append base. (Глава 16 рассказывает об определении видов; Глава 18 — про append.)

Распространённые соглашения, которые вы встретите в реальных проектах:

  • resource — в стиле HTTP-ресурса, дописывает пути.
  • capability — группировка по возможностям, без семантики путей.
  • endpoint_group — группировка связанных интерфейсов под общей меткой.

Ни один из них не поставляется со стандартной библиотекой; команды определяют их по необходимости.

Поля, метки, описания

Тела фасетов несут то же содержимое из полей и меток, что и модули:

facet OrdersResource {
"Order CRUD operations"
base: "/orders"
version: v2
labels {
domain: Orders
}
interface Create
interface Get
}

Метки, объявленные на фасете, каскадируются на его вложенные интерфейсы и вложенные фасеты — именно так labels { domain: Orders } на фасете распространяется на каждую операцию внутри без повторов. Глава 18 полностью покрывает каскад.

Нет стабильных идентификаторов

У фасетов, как и у интерфейсов, нет стабильных идентификаторов. Их идентичность — это точечный путь внутри объемлющего модуля. Обнаружение переименования использует структурные эвристики, как и у интерфейсов (Глава 13).

Пустые тела

Фасет, которому нечего добавить сверх шаблона его типа, может опустить тело:

module Orders {
resource OrdersResource // facet using a 'resource' kind, no instance additions
}

Это объявляет фасет OrdersResource, содержимое которого целиком приходит из типа resource. Мы встретим типы в Главе 15.

Итоги

  • Фасет группирует интерфейсы (и вложенные фасеты) внутри тела модуля.
  • Фасеты организационны: нет стабильных идентификаторов, нет семантики развёртывания.
  • Вложенность односторонняя — модули содержат фасеты, никогда наоборот.
  • Шаги процессов достигают интерфейсов внутри фасетов по точечному пути (Module.Facet.Interface).
  • Пользовательские виды фасетов (resource, capability) позволяют согласовать запись с доменной лексикой.

Что дальше

Глава 7: Процессы → — как описывается поведение и откуда на самом деле берутся стрелки зависимостей.