16. Определение видов
Вы потребляли виды из стандартной библиотеки с главы 4. Теперь вы напишете свои. Эта глава знакомит с тремя: пользовательский вид модуля, пользовательский вид фасета и пользовательский вид интерфейса. Каждый показывает свою форму тела типа.
Пользовательский вид модуля
Допустим, у вашей команды есть понятие внутреннего сервиса — микросервиса, доступного только изнутри вашего VPC, принадлежащего вашей команде и помеченного security.zone: Internal. Каждый экземпляр — это микросервис с этими тремя вещами, запечёнными внутрь.
Наивный путь: копировать-вставлять эти три строки в каждый внутренний сервис. Лучший путь: определить вид.
type module internal_service { required cascade team labels { security.zone: Internal }}Теперь экземпляр:
internal_service Inventory { team: Commerce "Tracks stock counts in real time."
command CheckAvailability command Reserve}Inventory автоматически получает метку security.zone: Internal. Он всё ещё обязан заполнить team, потому что тип пометил его required — это тема следующей главы.
Обратите внимание, что internal_service объявлен с module в качестве родительского вида. Это делает его обобщённым подтипом модуля — вы также могли бы сделать подтипом service, если бы хотели визуальную обработку микросервиса плюс ограничения internal_service:
type service internal_service { required cascade team labels { security.zone: Internal }}Теперь экземпляры internal_service отрисовываются виджетом service по умолчанию (унаследованным от типа service из стандартной библиотеки), вдобавок имея метки и обязательное team.
Форма объявления типа
type <stable_id?> <parent_kind> <name> { <body> }<stable_id?>— необязательный слот#xyz. Форматтер выдаёт его при сохранении.<parent_kind>— тип, который этот расширяет. Любой зарегистрированный вид.module,facet,interface— три базовых вида; всё остальное — пользовательский подтип где-то выше по цепочке.<name>— имя, которое будут использовать ваши экземпляры.<body>— значения по умолчанию, пропуски, под-объявления и модификаторы.
Ключевого слова extends здесь нет. Родитель кодируется позицией. type service paymentsService { ... } означает «paymentsService расширяет service».
Пользовательский вид фасета
Фасет для «HTTP-ресурса»:
type facet resource { "A facet representing an HTTP resource. Path composes via append." append base}append base говорит: экземпляры resource несут поле base, которое композирует со значениями потомков (родительский /orders и дочерний /items разрешаются в /orders/items). Модификатор append — тема главы 18; пока примите его как «способ, которым составляющие пути складываются».
Используется так:
service Orders { resource OrdersResource { base: "/orders"
command Post query Get
resource Items { base: "/items" // appends to /orders → /orders/items query List } }}Пользовательский вид интерфейса
Для вида интерфейса webhook, у которого по умолчанию поле protocol: webhook:
type interface webhook { protocol: webhook "An HTTP webhook callback delivered by an external system."}Используется так:
service OrderWebhooks { webhook OrderCreated webhook OrderCancelled}Каждый экземпляр webhook наследует protocol: webhook. Экземпляр может переопределить или отбросить его.
Что может содержать тело типа
Каждая форма, которую мы уже встречали как элемент тела, может появиться в теле типа, с добавлением маркеров required и модификаторов cascade / append на полях:
| В теле типа | Значение |
|---|---|
field: value | Значение по умолчанию для экземпляров |
required field | Обязательный пропуск — экземпляр должен заполнить |
cascade field: value | Значение по умолчанию, каскадирующее к потомкам |
append field | Поле, композирующее со значениями потомков |
required cascade field | Обязательный пропуск, который после заполнения каскадирует |
labels { x: y } | Значения меток по умолчанию |
required labels.x | Обязательный пропуск метки |
command X { ... } | Предзаполненный интерфейс — экземпляр наследует |
required command X | Обязательный пропуск интерфейса — экземпляр должен уточнить |
component Y { ... } | Предзаполненный подмодуль |
required component Y | Обязательный пропуск подмодуля |
Сочетания required, cascade и append — это язык для проектирования вашей метамодели.
Ошибка, которой стоит избегать.
required component logs { command Send }— противоречие.requiredозначает «нет значения»; фигурный блок означает «вот значение». Валидатор это отвергает. Либо помечайтеrequired(пропуск, который нужно заполнить), либо предоставляйте содержимое (безrequired).
Где живут типы
Типы живут в .arch-файлах. По соглашению: храните их в файле с именем kinds.arch в корне пакета для наглядности.
acme.shop/├── package.archspace├── kinds.arch # type module internal_service { ... }├── payments.arch # internal_service Payments { ... }└── orders.arch # internal_service Orders { ... }Чтобы сделать тип видимым для других пакетов, пометьте его export:
export type module internal_service { required cascade team labels { security.zone: Internal }}Без export тип внутренний для своего пакета. Импортёры пользуются механизмом use из главы 12.
Что не может быть родительским видом
Два ограничения:
processиviewне могут быть родительскими видами. Их тела не являются штампуемыми шаблонами (это последовательности и проекции соответственно), поэтому их подтипизация пока не имеет полезной семантики. Зарезервировано для будущих итераций.- Зарезервированные ключевые слова не могут быть именами видов. Нельзя написать
type module process { ... }, потому чтоprocess— ключевое слово. Приложение B: Ключевые слова перечисляет полный набор.
Проработанный срез метамодели
Вот небольшой набор связанных типов, определяющих словарь домена платежей:
// A service that operates in PCI scope.export type service pci_service { required cascade team labels { security.zone: PCI } required ext.runbook_url}
// A service that processes external card transactions.export type pci_service card_processor { required ext.processor_vendor required ext.api_docs_url "A card processor — talks to an external vendor and is in PCI scope."}
// An interface for a callback delivered by an external system.export type interface webhook { protocol: webhook "An HTTP webhook callback."}И затем в экземплярах:
pci_service Authorize { team: Payments ext.runbook_url: "https://wiki/auth-runbook"
command Authorize}
card_processor StripeIntegration { team: Payments ext.runbook_url: "https://wiki/stripe-runbook" ext.processor_vendor: "Stripe" ext.api_docs_url: "https://stripe.com/docs"
command Charge webhook PaymentSucceeded webhook PaymentFailed}Метамодель кодирует доменное знание: у каждого PCI-сервиса есть URL с инструкцией по эксплуатации; каждый обработчик карт называет своего поставщика и документацию API. Валидатор обеспечивает выполнение этих требований во время разбора. Словарь (pci_service, card_processor, webhook) естественно читается в исходнике.
Резюме
- Тип расширяет родительский вид позиционно:
type <parent> <name>. Без ключевого словаextends. - Тела типов содержат значения по умолчанию, обязательные пропуски, предзаполненные под-объявления и модификаторы распространения.
module,facet,interface— три базовых вида; всё остальное — подтип.processиview(пока) не могут быть родительскими видами.- Экспортируйте типы через
export type ..., чтобы сделать их видимыми для импортирующих пакетов. - По соглашению: храните типы в
kinds.archв корне пакета.
Что дальше
Глава 17: Обязательные пропуски → — механизм обязательного решения, который делает типы чем-то большим, чем просто синтаксический сахар.