13. Стабильные идентификаторы
Имена стареют. Сервис Payments становится PaymentsService. Команда Orders.CreateOrder становится Orders.PlaceOrder. Целую подсистему переименовывают, когда команда переименовывает саму себя. В любой системе, где исходные файлы — каноническая модель, нужна идентичность, переживающая переименования; иначе любое переименование выглядит для инструментов сравнения как удаление плюс добавление.
Ответ Archlang — стабильные идентификаторы: непрозрачные буквенно-цифровые суффиксы, закрепляющие идентичность независимо от имён.
service #pay001 Payments { team: Payments command Authorize}#pay001 — стабильный идентификатор. Он есть у модуля; у интерфейса Authorize его нет. Эта глава объясняет оба решения.
Что несёт идентификатор
Несёт #id | Не несёт #id |
|---|---|
| Модули | Фасеты |
| Типы | Интерфейсы |
| Процессы | |
| Проекции | |
| Подпроцессы |
Правило: всё, что можно переименовать независимо от контейнера. Модули можно переименовать; их идентичность нужно закреплять. Интерфейсы тоже можно переименовать, но они живут внутри модуля — их идентичность это «интерфейс Authorize внутри #pay001», путь, который остаётся стабильным, пока стабильны и родительский модуль, и имя интерфейса. Когда переименовывается сам интерфейс, движок диффа использует эвристики по форме контракта и схожести имён.
Это компромисс. Идентификаторы на каждом интерфейсе обеспечили бы идеальное распознавание переименований ценой постоянного визуального шума в исходных файлах (каждый интерфейс в каждом модуле нёс бы непрозрачный префикс). Нынешнее распределение ставит идентификаторы туда, где они окупаются.
Что такое идентификаторы
#pay001#a3f2b7e#orderschk#, за которым сразу идёт непрозрачный буквенно-цифровой суффикс.- Длина суффикса не ограничена; по соглашению 4–8 символов.
- Чисто технические — никакого закодированного имени, никакого закодированного вида, никакой человекочитаемой основы.
«Никакого встроенного смысла» — в этом весь смысл. Кодирование чего бы то ни было в идентификатор — даже «это похоже на идентификатор payments» — подрывает стабильность: переименуйте систему из Payments в Settlements, и встроенная подсказка тут же устареет. Идентификаторы непрозрачны, чтобы в них нечему было стареть.
Идентификаторы не пишут вручную. Форматировщик создаёт их при первом сохранении:
// Вы пишете:service Payments { team: Payments}
// После сохранения форматировщик записывает:service #pay001 Payments { team: Payments}Точный суффикс выбирается реализацией; относитесь к нему как к случайному.
Куда идут идентификаторы в объявлении
Слот для идентификатора — между видом и именем:
service #pay001 Payments { ... }process #flow42 Checkout { ... }view #v9 PaymentsLandscape { ... }type #t001 module service { ... }Для объявлений типов слот находится между type и родительским видом.
Опустите слот, и форматировщик заполнит его при сохранении. Указывать вручную написанный идентификатор для нового объявления разрешено; указывать его для объявления, которое форматировщик не создавал, тоже допустимо. Удалять идентификатор после того, как форматировщик его записал, — плохая идея: любой дифф, сравнивающий «до» и «после», не сможет сопоставить переименованный модуль с его прежним состоянием.
Почему фасеты и интерфейсы не несут идентификаторов
Если бы у фасетов и интерфейсов были идентификаторы, визуально это выглядело бы так:
service #pay001 Payments { facet #f823 PaymentsResource { command #cmd9c0 Authorize command #cmdbb2 Capture }}Каждая строка получает непрозрачный префикс. Для сервиса с двадцатью интерфейсами это двадцать дополнительных лексем визуального шума на сервис.
Взамен вы получили бы: тривиальное распознавание переименований интерфейсов. Текущее распознавание на эвристиках не идеально — если вы переименуете интерфейс и поменяете его вид и его описание в одном коммите, дифф может назвать это удалением плюс добавлением. Этот компромисс — принять некоторое несовершенство эвристик в обмен на значительно более чистый исходный код — это сознательный выбор.
Если ваша команда переименовывает интерфейсы постоянно, это может раздражать. Делать всё сразу (drop OldName + command NewName { ... }) в одном коммите может скрыть непрерывность. Решение: переименовывать маленькими шагами.
Автоматически распространяемые подмодули
Тела типов могут штамповать предзаполненные подмодули в каждый экземпляр:
type module service { component metrics { command Emit } // каждый экземпляр service получает компонент 'metrics'}Компонент metrics появляется в каждом микросервисе; вопрос в том, какой стабильный идентификатор он получает. Ответ: детерминированно выведенный, а не свеже созданный.
Формула: hash(parent_module_id + type_id + declared_subname), обрезанная до стандартной длины суффикса. Это делает идентификатор:
- Стабильным при повторных сохранениях — один и тот же экземпляр производит один и тот же идентификатор
metricsпри каждом форматировании. - Одинаковым для экземпляров, выведенных из одного шаблона, — за исключением различия на экземпляр, вносимого
parent_module_id. - Удобным для перекрёстных ссылок —
[[#derived-id]]на подмодуль, поставленный типом, разрешается одинаково каждый раз.
В исходниках вы это не увидите. Форматировщик создаёт эти идентификаторы, но они сидят на разрешённых узлах, а не в ваших файлах .arch.
Области видимости
Идентификаторы имеют область рабочего пространства. Коллизии внутри рабочего пространства не рекомендуются — форматировщик создаёт их так, чтобы их избегать, и инструменты предупреждают при обнаружении, — но они не фатальны. Дублирующиеся идентификаторы разрешаются в порядке объявления, и инструментарий отмечает конфликт.
Когда появятся библиотеки и внешние пакеты (будущая итерация), каждый пакет будет владеть собственным пространством имён идентификаторов; перекрёстные ссылки между пакетами будут уточняться пакетом.
Что дают идентификаторы
- Диффы видят переименования. Глава 14 посвящена этому: модуль с тем же
#idи другим именем — это переименование, а не удаление плюс добавление. - Перекрёстные ссылки остаются стабильными.
[[#pay001]]в описании продолжает работать после того, какPaymentsстановитсяPaymentsService. - URL инструментария остаются стабильными. Внешние системы, ссылающиеся на конкретный модуль (
<viewer>?focus=#pay001), переживают каждое переименование модуля.
Резюме
- Идентификаторы — непрозрачные буквенно-цифровые суффиксы, закрепляющие идентичность сквозь переименования.
- Идентификаторы несут модули, типы, процессы, проекции и подпроцессы.
- Фасеты и интерфейсы — нет; их идентичность — точечный путь внутри стабильного родительского модуля.
- Форматировщик создаёт идентификаторы при сохранении; не пишите их вручную.
- Автоматически распространяемые подмодули получают детерминированно выведенные идентификаторы.
- Компромисс: некоторое несовершенство эвристик при переименовании интерфейсов в обмен на чистый исходный код.
Что дальше
Глава 14: Диффы → — изменение как полноправный артефакт, построенный на системе идентификаторов из этой главы.