15. Зачем нужны типы?
Вы используете виды (service, database, command) с главы 4. Все они приходили из стандартной библиотеки. Эта глава о том, что виды такое и откуда они берутся. Следующие четыре главы — о том, как определять свои собственные.
Это первая глава Части IV. Здесь центр тяжести книги смещается. Части I–III были посвящены написанию .arch-файлов с использованием уже имеющихся видов. Часть IV — о написании самих видов.
Смена мышления. Если вы пришли из объектно-ориентированного мира, слово «тип» в Archlang означает не то, что вы думаете. Тип в Archlang — это не класс. Тип — это даже, по сути, не категория. Тип — это шаблон формы: частично заполненный документ, который экземпляр завершает, заполняя пропуски.
Это различие определяет всё в Части IV. Держите его в уме.
Что такое тип
type module service { required cascade team required labels.domain}Это объявление говорит: «Микросервис — это модуль, у которого обязательно должно быть поле team (которое каскадирует к потомкам) и метка labels.domain. Любой экземпляр микросервиса должен заполнить их или явно отбросить».
Экземпляр заполняет форму:
service Payments { team: Platform labels { domain: Payments }}Тип предоставил форму; экземпляр предоставил содержание. Здесь нет наследования в смысле ООП — нет переопределения методов, нет виртуальной диспетчеризации. Есть штамповка шаблона: во время разбора тело типа отпечатывается на экземпляр так, будто записано там буквально. Экземпляр может уточнить, переопределить или отбросить отпечатанное, но отношение здесь — «этот экземпляр был отлит по этой форме», а не «этот экземпляр является членом этого класса».
Что заменяют типы
Если вы раньше пользовались инструментами для моделирования архитектуры, типы заменяют сразу несколько вещей:
| Их концепция | Концепция Archlang |
|---|---|
| Архетипы RSM | type module <kind> |
| Шаблоны RSM | type module <kind> |
| Стереотипы Sparx | type module <kind> |
Три разных механизма в трёх разных инструментах — все они служат одной цели: «модули такого характера разделяют общие значения по умолчанию и общие требования». Archlang сводит их в один механизм с одним словарём.
Что может содержать тело типа
Тело типа выразительнее тела экземпляра, потому что оно может помечать что-либо как обязательное (обязательные пропуски, которые экземпляр должен заполнить):
type module service { // Значение по умолчанию — экземпляры наследуют; могут переопределить cascade widget: arch-service
// Обязательный пропуск — экземпляры должны заполнить или отбросить required cascade team required labels.domain
// Предзаполненное под-объявление — каждый микросервис получает этот компонент component metrics { command Emit }
// Обязательный пропуск под-объявления — экземпляр должен уточнить или отбросить required database PrimaryStore}Здесь происходит шесть вещей:
- Значение по умолчанию (
cascade widget: ...) — значение, которое экземпляр наследует, но может изменить. - Обязательный пропуск поля (
required cascade team) — экземпляр должен заполнить. - Обязательный пропуск метки (
required labels.domain) — экземпляр должен заполнить. - Предзаполненное под-объявление (
component metrics { ... }) — экземпляр наследует целиком. - Обязательный пропуск под-объявления (
required database PrimaryStore) — экземпляр должен уточнить. - Модификатор распространения (
cascadeнаteam,widget) — рассматривается в главе 18.
Глава 16 проходит через объявление вашего первого типа. Глава 17 разбирает required. Глава 18 — глубокое погружение в cascade, append и метки.
Подтипы — это тоже шаблоны форм
Тип может расширять другой тип, называя его родительским видом:
// Базовый тип.type module service { required cascade team}
// Подтип — каждый paymentsService получает весь отпечаток service ПЛЮС этот.type service paymentsService { team: "Payments" // заполняет пропуск родителя labels { security.zone: PCI } // добавляет новую метку}Экземпляр paymentsService наследует оба шаблона: service и paymentsService. Перед экземпляром больше не стоит пропуск required team — paymentsService уже заполнил его. Но экземпляр всё ещё может добавлять метки, переопределять команду или отбрасывать что-либо.
Подтипы используют те же операции уточнения / переопределения / отбрасывания, что и экземпляры. Здесь нет «режима подтипа» против «режима экземпляра» — операции единообразны. Глава 19 разбирает это.
Почему такой выбор
Три причины, по которым язык пошёл по пути шаблонов форм, а не классов:
Моделированию нужны пропуски. В архитектурных документах есть обязательная информация, у которой нет подходящего значения по умолчанию. У каждого сервиса должен быть владелец. Невозможно выбрать владельца по умолчанию — значение «неизвестно» по умолчанию — это молчаливая ошибка. Правильная модель — «слот существует и должен быть заполнен». Это и есть пропуск. Классы в стиле ООП естественно не выражают пропусков; они выражают значения по умолчанию плюс переопределение.
Штамповка отлаживается. Когда у микросервиса разрешённое тело выглядит неожиданно, это можно проследить: «эта team: Payments пришла из шаблона подтипа paymentsService; этот widget: — из service; этот subscribes: — из экземпляра». Штамповка шаблона — это линейная цепочка маленьких добавлений; наследование классов порождает вопрос о порядке разрешения методов, который трудно прочесть прямо из исходника.
Это компонуется со структурным каскадом. В архитектуре есть второй механизм распространения: значения текут через вложенные модули (команда, заданная на родителе, распространяется на дочерние компоненты). Это другая ось, нежели наследование типов. Шаблоны форм чисто компонуются со структурным каскадом — типы предоставляют структуру и значения по умолчанию, а структурный каскад заполняет значения во время выполнения, обходя дерево вложенности. ООП-наследование и структурное распространение не компонуются так же чисто. Глава 18 — та глава, где это становится очевидно.
Как ссылаться на типы
Типы живут в .arch-файлах рядом с экземплярами:
// В acme.shared/kinds.archexport type module paymentsService { required cascade team labels { security.zone: PCI }}Другие пакеты импортируют тип через механизм use из главы 12:
// В acme.shop/package.archspacedependencies { acme.shared: "../shared" }use paymentsService from acme.sharedЗатем любой файл в acme.shop может объявить экземпляр:
paymentsService Stripe { team: External}Модификатор export на типе делает его видимым для импортёров. Без export тип остаётся внутренним для объявляющего его пакета.
Стабильные идентификаторы у типов
Типы несут стабильные идентификаторы так же, как модули:
type #t017 module paymentsService { ... }Форматтер выдаёт их при сохранении. Переименование типа не ломает его экземпляры — резолвер сопоставляет по идентификатору, а не по имени типа. См. главу 13.
Резюме
- Тип — это шаблон формы: частично заполненный документ, который экземпляр завершает.
- Типы — это не классы. Здесь нет диспетчеризации методов; есть штамповка шаблона.
- Тела типов могут содержать значения по умолчанию, обязательные пропуски (
required), предзаполненные под-объявления и модификаторы распространения. - Подтипы расширяют родителей, называя их родительским видом; применимы те же операции уточнения / переопределения / отбрасывания.
- Шаблоны форм компонуются со структурным каскадом — вторым механизмом распространения, рассмотренным в главе 18.
- Типы несут стабильные идентификаторы; форматтер выдаёт их при сохранении.
Что дальше
Глава 16: Определение видов → — напишите свой первый тип, на практике.