13. Стабильные идентификаторы
Имена стареют. Сервис Payments становится PaymentsService. Команда Orders.createOrder становится Orders.placeOrder. Целую подсистему переименовывают, когда команда переименовывает саму себя. В любой системе, где исходные файлы — каноническая модель, нужна идентичность, переживающая переименования; иначе любое переименование выглядит для инструментов сравнения как удаление плюс добавление.
Ответ ArchLang — стабильные идентификаторы: непрозрачные буквенно-цифровые суффиксы, закрепляющие идентичность независимо от имён.
service #k7m2qx Payments { aspect team: "Payments" rest_create authorize}#k7m2qx — стабильный идентификатор. Он есть у модуля; у интерфейса Authorize его нет. Эта глава объясняет оба решения.
Правило. Идентификатор должен быть бессмысленным и постоянным. Бессмысленным — потому что всё, что вы в него закодируете (домен, тип, аббревиатуру), рано или поздно перестанет соответствовать и будет соблазнять вас «исправить» его. Постоянным — потому что весь смысл идентификатора в том, чтобы нетронутым пережить любое переименование, смену области и перемещение. Никогда не меняйте идентификатор. Тот
#k7m2qxс тем же успехом мог бы читаться как#q4w8zr— непрозрачность и есть достоинство.
Что несёт идентификатор
Заголовок раздела «Что несёт идентификатор»Несёт #id | Не несёт #id |
|---|---|
| Модули | Поверхности |
| Типы | Интерфейсы |
| Процессы | |
| Проекции | |
| Подпроцессы | |
| Политики | |
| Запросы |
Правило: всё, что можно переименовать независимо от контейнера. Модули можно переименовать; их идентичность нужно закреплять. Интерфейсы тоже можно переименовать, но они живут внутри модуля — их идентичность это «интерфейс Authorize внутри #k7m2qx», путь, который остаётся стабильным, пока стабильны и родительский модуль, и имя интерфейса. Когда переименовывается сам интерфейс, движок диффа использует эвристики по форме контракта и схожести имён.
Это компромисс. Идентификаторы на каждом интерфейсе обеспечили бы идеальное распознавание переименований ценой постоянного визуального шума в исходных файлах (каждый интерфейс в каждом модуле нёс бы непрозрачный префикс). Нынешнее распределение ставит идентификаторы туда, где они окупаются.
Что такое идентификаторы
Заголовок раздела «Что такое идентификаторы»#a3f2b7e#k7m2qx#q4w8zr#, за которым сразу идёт непрозрачный буквенно-цифровой суффикс.- Длина суффикса не ограничена; по соглашению 4–8 символов.
- Чисто технические — никакого закодированного имени, никакого закодированного типа, никакой человекочитаемой основы.
«Никакого встроенного смысла» — в этом весь смысл. Кодирование чего бы то ни было в идентификатор — даже «это похоже на идентификатор payments» — подрывает стабильность: переименуйте систему из Payments в Settlements, и встроенная подсказка тут же устареет. Идентификаторы непрозрачны, чтобы в них нечему было стареть. (#pay001 или #orderschk были бы антипаттернами — они протаскивают домен в идентичность.)
Предпочитайте инструмент; запасной вариант от руки
Заголовок раздела «Предпочитайте инструмент; запасной вариант от руки»Предпочитайте, чтобы идентификаторы создавал инструмент. Действительно случайные суффиксы трудно производить вручную, поэтому форматировщик создаёт его при первом сохранении:
// Вы пишете:service Payments { aspect team: "Payments"}
// После сохранения форматировщик записывает:service #k7m2qx Payments { aspect team: "Payments"}Точный суффикс выбирается реализацией; относитесь к нему как к случайному.
Если вам всё же приходится писать идентификатор вручную — автодополнение недоступно, набрасываете офлайн, — случайность от руки даётся по-настоящему трудно, поэтому допустимо откатиться к зерну на основе имени файла, где имя файла — основная часть:
service #payments-1 Payments { ... } // <имя-файла>-<индекс>service #payments-9f3 Settlements { ... } // <имя-файла>-<случайный-постфикс>Это будет выглядеть устаревшим как имя, как только модуль переедет в другой файл, — и это нормально. Идентификатор бессмыслен и никогда не меняется независимо от того, где живёт модуль; устаревший вид допустим, изменять его — нет. (Если найдёте лучший способ получить случайность от руки — используйте его; зерно из имени файла — это запасной вариант, а не идеал.)
Куда идут идентификаторы в объявлении
Заголовок раздела «Куда идут идентификаторы в объявлении»Слот для идентификатора — между типом и именем:
service #k7m2qx Payments { ... }process #h2k9p4 Checkout { ... }view #z7q3w PaymentsLandscape { ... }type #m4d8c module service { ... }Для объявлений типов слот находится между type и родительским типом.
Опустите слот, и форматировщик заполнит его при сохранении. Указывать вручную написанный идентификатор для совершенно нового объявления разрешено; указывать его для объявления, которое форматировщик не создавал, тоже допустимо. Удалять идентификатор после того, как форматировщик его записал, — плохая идея: любой дифф, сравнивающий «до» и «после», не сможет сопоставить переименованный модуль с его прежним состоянием.
Правило. Всё в реальной архитектуре несёт идентификатор. Модули, интерфейсы, типы, процессы, проекции — вся долговечная модель. Необязательные идентификаторы — удобство лишь для быстрых черновиков и набросков. Полезный тест: когда вы предлагаете изменения, у вещей должны быть идентификаторы. Если это часть модели, которую люди ревьюят и развивают, ей нужна идентичность, переживающая переименования и перемещения. Пропускайте идентификаторы только пока набрасываете.
Почему поверхности и интерфейсы не несут идентификаторов
Заголовок раздела «Почему поверхности и интерфейсы не несут идентификаторов»Если бы у поверхностей и интерфейсов были идентификаторы, визуально это выглядело бы так:
service #k7m2qx Payments { surface #f823n PaymentsResource { rest_create #cmd9c0 Authorize rest_create #cmdbb2 Capture }}Каждая строка получает непрозрачный префикс. Для сервиса с двадцатью интерфейсами это двадцать дополнительных лексем визуального шума на сервис.
Взамен вы получили бы: тривиальное распознавание переименований интерфейсов. Текущее распознавание на эвристиках не идеально — если вы переименуете интерфейс и поменяете его тип и его описание в одном коммите, дифф может назвать это удалением плюс добавлением. Этот компромисс — принять некоторое несовершенство эвристик в обмен на значительно более чистый исходный код — это сознательный выбор.
Если ваша команда переименовывает интерфейсы постоянно, это может раздражать. Делать всё сразу (drop OldName + rest_create NewName { ... }) в одном коммите может скрыть непрерывность. Решение: переименовывать маленькими шагами.
Автоматически распространяемые подмодули
Заголовок раздела «Автоматически распространяемые подмодули»Тела типов могут штамповать предзаполненные подмодули в каждый экземпляр:
type module service { component metrics { rest_create emit } // каждый экземпляр service получает компонент 'metrics'}Компонент metrics появляется в каждом сервисе; вопрос в том, какой стабильный идентификатор он получает. Ответ: детерминированно выведенный, а не свеже созданный.
Формула: hash(parent_module_id + type_id + declared_subname), обрезанная до стандартной длины суффикса. Это делает идентификатор:
- Стабильным при повторных сохранениях — один и тот же экземпляр производит один и тот же идентификатор
metricsпри каждом форматировании. - Одинаковым для экземпляров, выведенных из одного шаблона, — за исключением различия на экземпляр, вносимого
parent_module_id. - Удобным для перекрёстных ссылок —
[[#derived-id]]на подмодуль, поставленный типом, разрешается одинаково каждый раз.
В исходниках вы это не увидите. Форматировщик создаёт эти идентификаторы, но они сидят на разрешённых узлах, а не в ваших файлах .arch.
Области видимости
Заголовок раздела «Области видимости»Идентификаторы имеют область рабочего пространства. Коллизии внутри рабочего пространства не рекомендуются — форматировщик создаёт их так, чтобы их избегать, и инструменты предупреждают при обнаружении, — но они не фатальны. Дублирующиеся идентификаторы разрешаются в порядке объявления, и инструментарий отмечает конфликт.
Когда появятся библиотеки и внешние пакеты (будущая итерация), каждый пакет будет владеть собственным пространством имён идентификаторов; перекрёстные ссылки между пакетами будут уточняться пакетом.
Что дают идентификаторы
Заголовок раздела «Что дают идентификаторы»- Диффы видят переименования. Глава 14 посвящена этому: модуль с тем же
#idи другим именем — это переименование, а не удаление плюс добавление. Именно стабильные идентификаторы делают изменение полноправным — рефакторинг читается как переименования и перемещения, а не «всё удалено и заново добавлено». - Перекрёстные ссылки остаются стабильными.
[[#k7m2qx]]в описании продолжает работать после того, какPaymentsстановитсяPaymentsService. - URL инструментария остаются стабильными. Внешние системы, ссылающиеся на конкретный модуль (
<viewer>?focus=#k7m2qx), переживают каждое переименование модуля.
- Идентификаторы должны быть бессмысленными и постоянными — непрозрачные буквенно-цифровые суффиксы, которые никогда не меняются и не кодируют ни домен, ни тип, ни семантику.
- Идентификаторы несут модули, типы, процессы, проекции, подпроцессы, политики и запросы.
- Поверхности и интерфейсы — нет; их идентичность — точечный путь внутри стабильного родительского модуля.
- Предпочитайте форматировщик для создания случайных идентификаторов при сохранении; запасной вариант от руки — зерно на основе имени файла (
<имя-файла>-<индекс>), что нормально, ведь идентификатор бессмыслен независимо от того, где окажется модуль. - Всё в долговечной модели несёт идентификатор; пропускайте их только пока набрасываете. Когда вы предлагаете изменения, у вещей должны быть идентификаторы.
- Автоматически распространяемые подмодули получают детерминированно выведенные идентификаторы.
- Компромисс: некоторое несовершенство эвристик при переименовании интерфейсов в обмен на чистый исходный код.
Что дальше
Заголовок раздела «Что дальше»Глава 14: Диффы → — изменение как полноправный артефакт, построенный на системе идентификаторов из этой главы.