6. Поверхности
Модуль с двадцатью интерфейсами в плоском списке нечитаем. Поверхность — это именованная группировка интерфейсов (и вложенных поверхностей) внутри тела модуля. Это исключительно средство организации: у поверхностей нет семантики развёртывания, нет стабильных идентификаторов, нет присутствия за пределами модуля, в котором они живут.
module Orders { aspect team: "Commerce"
surface ordersResource { "Order CRUD operations" base: "/orders"
interface create interface get interface update interface delete
surface products { base: "/products" interface add interface remove } }
surface webhooks { interface subscribe interface unsubscribe }}Две поверхности верхнего уровня на Orders: ordersResource (со своей вложенной поверхностью products) и webhooks. Каждая группирует интерфейсы под именем; каждая может нести поля, аспекты и описание. Имена поверхностей — в lowerCamelCase, как и у интерфейсов, которые они группируют.
Зачем нужны поверхности
Заголовок раздела «Зачем нужны поверхности»Без поверхностей каждый интерфейс живёт прямо в теле модуля. Это работает для небольших модулей. Для модулей с реальной публичной поверхностью — HTTP-сервис с двадцатью эндпоинтами, доменный сервис, группирующий операции по агрегату — плоская раскладка превращается в шум.
Поверхность — это домен интерфейсов: то же доменное мышление, что определяет, как вы разбиваете файлы и вкладываете модули, применённое на уровень ниже — к поверхности API модуля. Поверхность userInteraction держит операции взаимодействия с пользователем; поверхность webhooks держит операции вебхуков.
Поверхности дают вам место для того, чтобы:
- Сгруппировать концептуально связанные интерфейсы (операции
/ordersвместе). - Прикрепить общие поля, например базовый путь, который должен применяться ко всему, что находится ниже.
- Применить шаблон типа (Глава 16), чтобы можно было штамповать переиспользуемую поверхность
crud, получающую фиксированный набор операций бесплатно — одна и та же стандартная форма применяется к разным модулям, вместо того чтобы переобъявлять её каждый раз.
Они не про развёртывание, владение или адресацию. Это относится к модулям.
Тест на группировку — это уменьшенный файловый тест: представьте, что выписываете каждую привязку интерфейса. Привязки, которые вы охотно держали бы в одном файле, относятся к одной поверхности; привязки, которые вы скорее разнесли бы по разным файлам, относятся к разным поверхностям. А интерфейс, который не вписывается ни в одну группу — не связан с остальными, — не нужно силой запихивать в неё. Оставьте его прямо на модуле или дайте ему собственную поверхность. Отдельная операция прекрасно живёт в теле модуля; тянитесь за поверхностью, когда существует реальная группа.
Вложенность односторонняя
Заголовок раздела «Вложенность односторонняя»Правило. Модули содержат поверхности; поверхности не содержат модулей.
Поверхность может содержать другие поверхности и интерфейсы. Она не может содержать модуль. Если вам нужен подмодуль внутри сервиса, объявите его как вложенный модуль (Глава 4), а не как поверхность:
module Orders { surface api { interface create // ✅ интерфейсы внутри поверхности }
module Worker { // ✅ вложенный модуль — НЕ внутри поверхности interface runJob }
// ❌ Это было бы ошибкой разбора: // surface Bad { // module Inside { ... } // }}Причина — идентичность. Модули несут стабильные идентификаторы и представляют архитектурные элементы; поверхности организационны. Разрешение модулей внутри поверхностей смешало бы эти роли.
Точечные пути
Заголовок раздела «Точечные пути»Шаги процессов и перекрёстные ссылки разрешаются через поверхности по точечным путям:
process Buy { Customer > Orders.ordersResource.create Customer > Orders.ordersResource.products.add Customer > Orders.webhooks.subscribe}Полный путь — Module.Surface.[Surface.]Interface. Поверхности прозрачны для резолвера — они организуют исходный текст, но не вводят отдельное пространство имён, которое нужно было бы обходить.
Типы поверхностей
Заголовок раздела «Типы поверхностей»surface — единственный тип поверхности, определённый в стандартной библиотеке. Вы можете объявлять собственные типы поверхностей, когда предпочтительна доменно-специфическая лексика:
type surface resource { "An HTTP resource — appends to a base path" append base}
module Orders { resource ordersResource { base: "/orders" interface post interface get }}resource теперь — определённый пользователем тип поверхности, который ведёт себя как surface плюс поле append base. (Глава 16 рассказывает об определении типов; Глава 18 — про append.)
Распространённые соглашения, которые вы встретите в реальных проектах:
resource— в стиле HTTP-ресурса, дописывает пути.capability— группировка по возможностям, без семантики путей.endpoint_group— группировка связанных интерфейсов под общей меткой.
Ни один из них не поставляется со стандартной библиотекой; команды определяют их по необходимости.
Поля, аспекты, описания
Заголовок раздела «Поля, аспекты, описания»Тела поверхностей несут то же содержимое из полей и меток, что и модули:
surface ordersResource { "Order CRUD operations" base: "/orders" version: v2
aspect { domain: "Orders" }
interface create interface get}Аспекты, объявленные на поверхности, каскадируются на её вложенные интерфейсы и вложенные поверхности — именно так aspect { domain: "Orders" } на поверхности распространяется на каждую операцию внутри без повторов. Глава 18 полностью покрывает каскад.
Нет стабильных идентификаторов
Заголовок раздела «Нет стабильных идентификаторов»У поверхностей, как и у интерфейсов, нет стабильных идентификаторов. Их идентичность — это точечный путь внутри объемлющего модуля. Обнаружение переименования использует структурные эвристики, как и у интерфейсов (Глава 13).
Пустые тела
Заголовок раздела «Пустые тела»Поверхность, которой нечего добавить сверх шаблона её типа, может опустить тело:
module Orders { resource ordersResource // surface using a 'resource' type, no instance additions}Это объявляет поверхность ordersResource, содержимое которой целиком приходит из типа resource. Мы встретим типы в Главе 15.
- Поверхность группирует интерфейсы (и вложенные поверхности) внутри тела модуля.
- Поверхности организационны: нет стабильных идентификаторов, нет семантики развёртывания.
- Вложенность односторонняя — модули содержат поверхности, никогда наоборот.
- Шаги процессов достигают интерфейсов внутри поверхностей по точечному пути (
Module.Surface.Interface). - Пользовательские типы поверхностей (
resource,capability) позволяют согласовать запись с доменной лексикой.
Что дальше
Заголовок раздела «Что дальше»Глава 7: Процессы → — как описывается поведение и откуда на самом деле берутся стрелки зависимостей.