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

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: Диффы → — изменение как полноправный артефакт, построенный на системе идентификаторов из этой главы.