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

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
Архетипы RSMtype module <kind>
Шаблоны RSMtype module <kind>
Стереотипы Sparxtype 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 teampaymentsService уже заполнил его. Но экземпляр всё ещё может добавлять метки, переопределять команду или отбрасывать что-либо.

Подтипы используют те же операции уточнения / переопределения / отбрасывания, что и экземпляры. Здесь нет «режима подтипа» против «режима экземпляра» — операции единообразны. Глава 19 разбирает это.

Почему такой выбор

Три причины, по которым язык пошёл по пути шаблонов форм, а не классов:

Моделированию нужны пропуски. В архитектурных документах есть обязательная информация, у которой нет подходящего значения по умолчанию. У каждого сервиса должен быть владелец. Невозможно выбрать владельца по умолчанию — значение «неизвестно» по умолчанию — это молчаливая ошибка. Правильная модель — «слот существует и должен быть заполнен». Это и есть пропуск. Классы в стиле ООП естественно не выражают пропусков; они выражают значения по умолчанию плюс переопределение.

Штамповка отлаживается. Когда у микросервиса разрешённое тело выглядит неожиданно, это можно проследить: «эта team: Payments пришла из шаблона подтипа paymentsService; этот widget: — из service; этот subscribes: — из экземпляра». Штамповка шаблона — это линейная цепочка маленьких добавлений; наследование классов порождает вопрос о порядке разрешения методов, который трудно прочесть прямо из исходника.

Это компонуется со структурным каскадом. В архитектуре есть второй механизм распространения: значения текут через вложенные модули (команда, заданная на родителе, распространяется на дочерние компоненты). Это другая ось, нежели наследование типов. Шаблоны форм чисто компонуются со структурным каскадом — типы предоставляют структуру и значения по умолчанию, а структурный каскад заполняет значения во время выполнения, обходя дерево вложенности. ООП-наследование и структурное распространение не компонуются так же чисто. Глава 18 — та глава, где это становится очевидно.

Как ссылаться на типы

Типы живут в .arch-файлах рядом с экземплярами:

// В acme.shared/kinds.arch
export type module paymentsService {
required cascade team
labels { security.zone: PCI }
}

Другие пакеты импортируют тип через механизм use из главы 12:

// В acme.shop/package.archspace
dependencies { acme.shared: "../shared" }
use paymentsService from acme.shared

Затем любой файл в acme.shop может объявить экземпляр:

paymentsService Stripe {
team: External
}

Модификатор export на типе делает его видимым для импортёров. Без export тип остаётся внутренним для объявляющего его пакета.

Стабильные идентификаторы у типов

Типы несут стабильные идентификаторы так же, как модули:

type #t017 module paymentsService { ... }

Форматтер выдаёт их при сохранении. Переименование типа не ломает его экземпляры — резолвер сопоставляет по идентификатору, а не по имени типа. См. главу 13.

Резюме

  • Тип — это шаблон формы: частично заполненный документ, который экземпляр завершает.
  • Типы — это не классы. Здесь нет диспетчеризации методов; есть штамповка шаблона.
  • Тела типов могут содержать значения по умолчанию, обязательные пропуски (required), предзаполненные под-объявления и модификаторы распространения.
  • Подтипы расширяют родителей, называя их родительским видом; применимы те же операции уточнения / переопределения / отбрасывания.
  • Шаблоны форм компонуются со структурным каскадом — вторым механизмом распространения, рассмотренным в главе 18.
  • Типы несут стабильные идентификаторы; форматтер выдаёт их при сохранении.

Что дальше

Глава 16: Определение видов → — напишите свой первый тип, на практике.