17. Обязательные пропуски
required-пропуск — это способ языка сказать «этот слот существует и должен быть заполнен, прежде чем модель станет валидной». Так автор типа сообщает каждому потомку: тебе нужно это решить; я не могу решить это за тебя.
type module service { required cascade version}Этот тип объявляет поле version. Значения по умолчанию нет. Каждый экземпляр микросервиса должен либо предоставить значение, либо явно отбросить поле. Всё остальное — ошибка валидации.
Эта глава о точной семантике required и о единственных двух способах, которыми экземпляр (или потомок-подтип) может с ним справиться.
Что такое пропуск на самом деле
Заголовок раздела «Что такое пропуск на самом деле»Ментальная модель, которая втягивает людей в неприятности: думать о required как о маркере, который можно добавить или убрать. Нельзя.
Смена мышления.
required-пропуск не является отделимым маркером. Он и есть состояние отсутствия значения. Нет операции «понизить required до необязательного без заполнения», потому что у этой операции нет смысла — объявленное поле без значения и без флагаrequired— это то же самое, что поле сrequiredи без значения, что то же самое, что отсутствие поля вовсе (на уровне типа).
Поэтому, читая required version, не читайте «к полю version прикреплён флаг required». Читайте «поле version существует без значения, и это недопустимо в экземплярах». Флаг и пустота — это один и тот же факт.
Следствие: есть только два способа справиться с required-пропуском:
- Заполнить. Установить значение. Пропуск перестаёт быть пропуском.
- Отбросить. Удалить всё объявление полностью. Нет пропуска, потому что нет поля.
Вот и всё. Третьего варианта не существует.
Заполнение
Заголовок раздела «Заполнение»Самый частый случай. Предоставьте значение:
type module service { required cascade version}
service Payments { version: "2.1" // fulfills the blank}Или предоставьте содержимое для пропуска под-объявления:
type module service { required database PrimaryStore}
service Orders { database PrimaryStore { // fulfills the blank with content db_read read db_write write }}После заполнения потомки и подтипы вниз по цепочке больше не сталкиваются с пропуском. Он отвечен. Если вы хотели, чтобы они тоже что-то ответили, родительскому типу следовало оставить другой пропуск.
Отбрасывание
Заголовок раздела «Отбрасывание»Если у вас действительно нет и не будет значения для обязательного поля — отбросьте объявление целиком:
type module service { required database PrimaryStore required cascade version}
service StatelessRouter { version: "1.0" drop PrimaryStore // explicit acknowledgement: this service has no store}Отбрасывание намеренно делается громким. Оно вынуждает автора написать drop PrimaryStore, а не молча опустить поле. Следующий ревьюер видит отбрасывание и может спросить: «почему у этого сервиса нет основного хранилища?» — это полезный вопрос. Молчаливое пропускание скрыло бы его.
Запрещённый третий вариант
Заголовок раздела «Запрещённый третий вариант»Чего нельзя:
// In a type body:type module service { required cascade version}
// In an instance:service Bad { // Idea: "I want the version field to exist but not be required and not be set." // There's no syntax for this. There's no concept for this. The thing // you're describing isn't a thing.}Если у вас возникает желание «понизить до необязательного без заполнения», отступите и спросите: а каким было бы значение этого поля? Если ответ «пока никакое, но оно должно существовать» — у вас пропуск, заполните позже. Если ответ «у этого сервиса его действительно нет» — отбросьте.
К чему может применяться required
Заголовок раздела «К чему может применяться required»К каждой части тела типа можно применить required:
type module service { // Required field required cascade version
// Required aspect required aspect domain
// Required sub-declaration (interface) required kafka heartbeat
// Required sub-declaration (sub-module) required database PrimaryStore}Каждый из них требует от экземпляра отдельного решения «заполнить или отбросить».
Для вложенных аспектов точечная и блочная формы эквивалентны:
type module service { required aspect domain // is the same as: aspect { required domain }}Набор действий против унаследованного пропуска
Заголовок раздела «Набор действий против унаследованного пропуска»Когда подтип или экземпляр наследует required-пропуск, у него ровно шесть возможных действий:
| Цель | Синтаксис |
|---|---|
| Уточнить до типа-подтипа, оставить пропуск | required <subtype-type> Name |
| Уточнить до типа-подтипа, заполнить | <subtype-type> Name { ... } |
| Сменить на тип, не являющийся подтипом, оставить пропуск | override required <new-type> Name |
| Сменить на тип, не являющийся подтипом, заполнить | override <new-type> Name { ... } |
| Заполнить, сохранив унаследованный тип | Name: value или Name { ... } |
| Полностью удалить | drop Name |
override — тема главы 19. Пока: это ключевое слово для смены унаследованного объявления на тип, не являющийся подтипом исходного.
Все шесть действий на одном пропуске:
type module service { required database PrimaryStore}
// 1. Refine to subtype type, keep blank — relational_db extends databasetype service transactional_service { required relational_db PrimaryStore}
// 2. Refine to subtype type, fulfilltype service fulfilled_relational { relational_db PrimaryStore { db_read read }}
// 3. Switch to non-subtype type, keep blanktype service cache_still_blank { override required cache PrimaryStore}
// 4. Switch to non-subtype type, fulfilltype service cache_fulfilled { override cache PrimaryStore { db_read lookup }}
// 5. Fulfill keeping inherited typetype service ordinary { database PrimaryStore { db_read read; db_write write }}
// 6. Remove entirelytype service stateless { drop PrimaryStore}Каждое допустимое действие против обязательного пропуска — одно из этих шести. Всё остальное — ошибка.
Подтипы могут добавлять новые пропуски
Заголовок раздела «Подтипы могут добавлять новые пропуски»Подтип может добавить свои собственные required-пропуски, требуя от своих экземпляров (и любых дальнейших подтипов) больше информации:
type module service { required cascade version}
type service payments_service { version: "2.1" // fulfills parent's blank required ext.processor_vendor // adds a new blank}Перед экземпляром payments_service больше не стоит version (тип заполнил это), но стоит ext.processor_vendor. Экземпляр должен заполнить или отбросить его.
Так эволюционирует метамодель: обобщённый тип задаёт универсальные требования; подтипы накладывают на него доменно-специфичные требования.
Поведение каскада у обязательных пропусков
Заголовок раздела «Поведение каскада у обязательных пропусков»Модификатор cascade — часть того, как поле объявляется на уровне типа. Вы не можете изменить его в подтипе или экземпляре — режим распространения зафиксирован у типа, который вводит поле.
type module service { required cascade version // cascades through nested modules}
service Payments { version: "2.1"
component MetricsExporter { // No 'version' set here; the cascade means resolved version is "2.1" }}Глава 18 — глубокое погружение в каскад.
Ошибки, которые вы увидите
Заголовок раздела «Ошибки, которые вы увидите»Валидатор выдаёт конкретные диагностики:
- «Required field
versionis not fulfilled and not dropped» — вы забыли заполнить или отбросить пропуск. - «Cannot use
requiredtogether with content» —required component logs { rest_create Send }— то самое противоречие. - «Cannot
dropfieldx: not inherited» — отбрасывать можно только то, что существует в области видимости.
Каждая указывает на место в исходнике и подсказывает исправление.
required-пропуск — это отсутствие значения, а не отделимый маркер.- Два способа с ним справиться: заполнить (установить значение или содержимое) или отбросить (удалить объявление).
- «Понижение required без заполнения» — не операция; состояние, которое она создала бы, не имеет смысла.
- Таблица из шести действий охватывает все допустимые действия против унаследованного обязательного пропуска.
- Подтипы могут добавлять свои собственные обязательные пропуски для дальнейшей специализации.
- Поведение каскада зафиксировано у типа, который вводит поле; подтипы не могут его изменить.
Что дальше
Заголовок раздела «Что дальше»Глава 18: Распространение → — самая длинная глава в книге и смена мышления, благодаря которой метамодель складывается воедино: два компонуемых механизма распространения.