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

Лучшие практики

Это проверенные на практике соглашения для моделирования на ArchLang — как выбирать гранулярность, когда использовать аспект, а когда вкладывать, как назначать владение и каких антипаттернов сторониться. Они выросли из реальных диаграмм. Воспринимайте их как рекомендации, которые переживают столкновение с настоящей системой, начиная с философии, из которой следует всё остальное.

Это язык архитектуры, а не инструмент рисования

Заголовок раздела «Это язык архитектуры, а не инструмент рисования»

ArchLang — это не язык рисования или построения диаграмм. Это язык архитектуры — текстовый способ описать вашу архитектуру. Диаграммы, представления и раскладки — всё это производное от этого текста; они вторичны, и так и должно быть.

Поэтому оптимизируйте под истину, а не под внешний вид. Когда вы печатаете, не думайте о том, как отрисуется диаграмма. Спрашивайте, полна ли модель и верна ли она тому, что действительно существует. Раскладка — задача отрисовщика; ваша задача — точное, полное описание. Красивая диаграмма неверной модели бесполезна; невзрачная диаграмма верной модели — это цель.

Устаревание — это проблема процесса, а не свойство языка

Заголовок раздела «Устаревание — это проблема процесса, а не свойство языка»

ArchLang не пытается решать проблему дрейфа диаграмм. Это намеренно вне области применения.

Автоматический вывод реальной связности — это другой класс инструментов. Если вы хотите знать, что на самом деле с чем соединяется, выводите это из рантайма — трассировки, сервисной сетки вроде Istio — а не из ArchLang. Язык нужен для рассуждения об архитектуре и её дальнейшего развития: для устремлённой вперёд, стратегической стороны, а не для пассивного зеркала текущего состояния.

Поддержание модели в актуальном виде — на вашей ответственности и зависит от ваших процессов. Используйте любые корпоративные практики и практики разработки, что у вас уже есть; язык не решает ни одну из них, так что этим практикам здесь не место.

Агенты и инструменты — включая навыки Claude, поставляемые с ArchLang — могут помогать поддерживать актуальность, но сегодня они слабы в построении процессов. Эта работа требует глубокого понимания бизнес-домена, а агенты лучше работают с кодом, чем с бизнес-процессами. Полезное подспорье, но не решение.

Архитектура — для всех, а не только для архитекторов

Заголовок раздела «Архитектура — для всех, а не только для архитекторов»

Цель — донести архитектуру до всей команды, а не сделать специализированный артефакт. В основном ей пользуются технические люди, да, но она не ограничена системными архитекторами или аналитиками.

Миссия — сделать архитектуру простой, понятной и доступной — и отделить технические детали от важных для бизнеса. Это часть того, что делают аспекты, разделяя бизнес-плоскость и плоскость данных, чтобы читатель мог сосредоточиться на интересующем его слое. QA-инженер должен уметь открыть модель и рассуждать о ней. Владелец продукта должен уметь понять, что построено, и рассуждать о его ограничениях и пределах, даже не вникая в каждый технический нюанс того, почему оно так построено.

Следствие для того, как моделировать: отдавайте предпочтение ясности, за которой может проследить неспециалист. Держите бизнес-плоскость читаемой и спихивайте технические механизмы на аспекты и вложенные детали, чтобы они не топили высокоуровневую историю. Если читать это может только архитектор — суть упущена.

Авторство распределено и идёт снизу вверх

Заголовок раздела «Авторство распределено и идёт снизу вверх»

ArchLang не требует, чтобы его писал системный архитектор. Лучший автор любого конкретного куска — тот, кто этим куском владеет. Разработчик, сопровождающий микросервис, описывает его лучше, чем это сделал бы архитектор, и держит его актуальнее — он может не видеть всю систему, но свою часть знает назубок. Системный аналитик часто разбирается в бизнес-процессах гораздо лучше архитектора, поэтому процессами должен владеть он.

Рекомендуемое внедрение — распределять владение: сделать каждого ответственным за документирование того, чем он владеет. Это даёт документацию, которая точнее и актуальнее модели, где один-единственный архитектор вынужден идти и моделировать всё.

Поток идёт снизу вверх. Те, кто строит, пишут описания, и это накапливается в истинную систему. Архитекторы и старшие инженеры — у которых есть время понять всю картину — затем читают эту истинную систему, чтобы рассуждать о ней, спорить о ней и определять стратегические направления. Авторство снизу вверх даёт верхушке лучшую точку обзора.

Авторство — это двухпроходный, по большей части бездумный процесс

Заголовок раздела «Авторство — это двухпроходный, по большей части бездумный процесс»

Язык устроен так, чтобы минимизировать мышление, пока вы документируете существующую архитектуру. Берегите мышление для рассуждения о ней и её изменения — когда вы просто выражаете то, что уже существует, авторство должно ощущаться почти механическим.

Рекомендуемый рабочий процесс для фиксации существующей системы состоит из двух проходов.

В первом проходе документируйте каждый модуль изолированно. Идите модуль за модулем и описывайте, что каждый из них есть и что умеет. Пока не думайте о связях. Будьте настолько дотошны или ленивы, насколько хотите — голые, пустые модули допустимы, если хочется двигаться быстро. Только одна забота: каждая вещь сама по себе.

Во втором проходе документируйте каждый процесс. Переключите мозг в режим процесса и пройдите по каждому бизнес-процессу по порядку: как бизнес-шаги ложатся на программные вещи, которые действительно происходят. Здесь и появляются связи — вы описываете последовательность, и взаимодействия выпадают из неё.

Результат — архитектура, при том что вы ни разу не садились спрашивать «что с чем взаимодействует». Этот вопрос — самый трудный, и вы никогда не отвечаете на него напрямую — потому что не так на самом деле строится софт. Вы думаете о том, что каждая вещь умеет, затем о том, что делает бизнес. Связи — побочный продукт процессов, а не то, что вы проектируете заранее. Процесс, пожалуй, важнее модуля: модули — это актёры, процессы — история, а история — это то, чем бизнес на самом деле является.

Черновик от процессов — пусть скелет выпадет сам

Заголовок раздела «Черновик от процессов — пусть скелет выпадет сам»

Двухпроходный поток выше идёт от модулей: опишите актёрский состав, затем напишите историю. Можно запустить и наоборот — от процессов — что часто соответствует тому, как вы на самом деле думаете о новой системе: что происходит раньше, чем что существует.

Просто напишите процессы. Ссылайтесь на модули и интерфейсы, которых ещё нет — Customer > Orders.createOrder, когда нет ни модуля Orders, ни интерфейса createOrder. Вместо ошибки компилятор синтезирует их как заглушки (рисуемые пунктиром) и отслеживает каждый пробел как TODO — «недостающая деталь», а не «неверно». Ваш поток рисуется сразу, а под ним появляется скелет модулей.

Затем продвиньте заглушки: идите к каждому пунктирному модулю и определите его по-настоящему — описание, тип, поля, вложенность. По мере этого его TODO снимается. «Что осталось определить» становится измеримым числом, а не кучей подавленных ошибок, и инструменты могут ставить «готово к предложению» в зависимость от нуля TODO.

От модулей удобно документировать существующую систему, где состав вы уже знаете. От процессов удобно набрасывать новую или предлагаемую, либо при распределённом авторстве — аналитик набрасывает процессы, и появляется скелет модулей, чтобы инженеры его наполнили. Это зеркальные отражения одних и тех же двух занятий, так что выбирайте точку входа под то, что знаете первым. Оба сходятся к одной модели.

Выбирайте правильную гранулярность — глубина стоит сопровождения

Заголовок раздела «Выбирайте правильную гранулярность — глубина стоит сопровождения»

Вы можете вкладывать модули бесконечно — хоть модуль на Java-класс, если бы захотели. Не стоит. Чем глубже вы моделируете, тем труднее держать это живым: код текуч, поэтому архитектура всегда устаревает. Каждый уровень детализации — это то, что вам придётся возвращаться и обновлять, а вы, скорее всего, не будете. Сопоставляйте глубину масштабу и сложности системы, не более.

Архитектура — это не программирование. Модуль не обязан ложиться на структуру вашей программы. Не думайте о классах, интерфейсах-против-реализаций или технических пакетах при авторстве — это заботы программирования. Модуль-на-класс — неправильное значение по умолчанию: чрезмерно конкретно и неподдерживаемо. (Нишевое исключение — архитектура сверхвысокой надёжности «ракетного» класса, где буквально всё должно быть задокументировано — это не реальный бизнес-софт.)

Каждый элемент может нести описание — пользуйтесь этим. Это не повальное «всегда предпочитай прозу вложенности». Смысл в том, что когда подмодули были бы слишком гранулярными или слишком простыми, чтобы оно того стоило, нормально остановиться раньше и зафиксировать эту деталь в описании. Хорошее описание может побить дробление сервиса на тривиальные подмодули — но только когда эти подмодули не оправдывали бы своё существование. Вкладывайте, когда вложенность несёт реальную структуру; описывайте, когда нет.

Калибруйте по стилю архитектуры:

  • Микросервисы — отдельный сервис как узел корневого уровня совершенно нормален. Часто лучше хорошо описать сервис, чем дробить его на подмодули. При множестве микросервисов нормально оставить их без детализации (только описание) — но всё же спускайтесь до уровня отдельного микросервиса.
  • Сервис-ориентированная архитектура (не «супер-микро») — каждый сервис делает несколько вещей, поэтому несколько модулей внутри сервиса нормальны.
  • Монолит — несколько модулей внутри, даже многоуровнево вложенных, совершенно нормально.

Самый гранулярный рекомендуемый предел — один модуль на каждый низовой доменный пакет, и только если вы делаете доменно-ориентированную упаковку. «Низовой пакет» означает доменный пакет, а не технический — разделение интерфейсы/реализации не является доменно-ориентированным. Например, фича — это низовой модуль; один микросервис может содержать три тесно связанные фичи, давая три подмодуля. Остановитесь там. Не дробите мельче низовой доменной единицы.

Две оси: вложенность — это глубина домена, аспекты соединяют плоскости

Заголовок раздела «Две оси: вложенность — это глубина домена, аспекты соединяют плоскости»

За большинством решений по моделированию стоит одна ментальная модель. Вложенность модулей — это доменная вложенность: помещая B внутрь A, вы говорите, что B принадлежит домену A — подчасть этой вещи, а не равноправный, с кем она общается. Соединение аспектом — это соединение через плоскости архитектуры: оно связывает модуль с чем-то на другой плоскости мышления — плоскости данных, плоскости хостинга, плоскости обмена сообщениями — а не с равноправным на той же плоскости.

Поэтому, прежде чем что-то размещать, спросите, это подчасть домена (вложите) или другая плоскость, на которой вещь живёт (свяжите аспектом). Всё ниже — применение этого одного различия.

Инкапсулируйте ресурсы с одним владельцем; общие — это собратья

Заголовок раздела «Инкапсулируйте ресурсы с одним владельцем; общие — это собратья»

Классический учебный рисунок — ряд коробок-сервисов, каждая подключена к своему цилиндру-базе — в ArchLang неверен.

Если один сервис владеет одной базой, база — это техническая деталь внутри домена сервиса. Вложите её как подмодуль сервиса; не рисуйте её как соседнюю коробку, соединённую ребром. Домен — это сервис; БД инкапсулирована им.

Решающий вопрос — владение, а не тип. База, используемая ровно одним микросервисом, вкладывается внутрь этого микросервиса. База, общая для нескольких микросервисов, — это равноправная забота, поэтому она сидит на том же уровне, что и они. Не доводите до доски сервисов, веером расходящихся к базам — БД с одним владельцем место внутри её владельца.

Два способа использовать описания — выбирайте под каждый элемент

Заголовок раздела «Два способа использовать описания — выбирайте под каждый элемент»

Описания поддерживают полноценный Markdown. Есть два допустимых режима; используйте тот, что подходит каждому элементу.

Первый режим — короткая одна строка: объясните, что вещь делает, подробнее, чем одно имя, и не более. Не повторяйте очевидное — пропустите то, что уже говорят имя, вложенные компоненты, процессы или интерфейсы. Избыточное описание хуже, чем никакого: для PaymentProcessor «обрабатывает платежи» хуже, чем пустое описание. Совсем без описания — совершенно нормально, когда нечего неочевидного добавить.

Второй режим — более длинный документ. Поскольку Markdown поддерживается, пишите сколько хотите — внутренности сервиса, обзор API, заметки по проектированию. Здесь может жить настоящий документ. Можно также использовать его как свалку ссылок для дополнительных связей.

Но структурированные ссылки место в полях, а не в описании. Ссылка на внешнюю документацию — скажем, страницу Confluence — это свойство, а свойства идут в поля. Используйте поле для «документ живёт здесь», а описание приберегите для прозы. (См. Ссылайтесь вовне — сделайте архитектуру узлом ссылок.)

Владение — первоклассно — назначайте команды

Заголовок раздела «Владение — первоклассно — назначайте команды»

Владение — первоклассное понятие; используйте его. Модуль может нести свойство team, называющее своего владельца или сопровождающего.

Оно опционально, не повсеместно. Не каждому модулю нужна команда — мелкозернистому внутреннему модулю часто не нужна, а собственному микросервису — нужна. Назначайте команды там, где владение осмысленно. Владение распространяется по вложенности: если родитель принадлежит команде, его вложенные дети считаются принадлежащими той же команде, если не сказано иначе, поэтому вы отмечаете владение на том уровне, где оно действительно меняется, а не на каждом узле. Можно также задать команду в корне пространства, чтобы заявить, что всё по умолчанию принадлежит этой команде — чистая базовая линия, которую вы затем переопределяете там, где владение расходится.

Стремитесь к отсутствию сирот. Стремитесь, чтобы у каждого значимого куска был владелец — напрямую или по распространению. Модуль, которым никто не владеет, — это запах.

Связанное: распределённое авторство снизу вверх — владельцы пишут собственные куски.

Моделируйте бизнес-слой, а не транспортную обвязку

Заголовок раздела «Моделируйте бизнес-слой, а не транспортную обвязку»

Когда сервисы общаются через инфраструктуру — Kafka, брокер сообщений, сервисную сетку — моделируйте логический вызов между ними, а не прыжок через инфраструктуру.

Не рисуйте плоскость данных как узлы в пути вызова:

  • ❌ Сервис A → брокер Kafka → Сервис B

Это хоронит важную архитектуру — кто от кого зависит — под транспортной механикой, и каждый сервис в итоге указывает на один и тот же узел брокера, так что диаграмма не говорит ничего.

Вместо этого нарисуйте прямую логическую зависимость и прикрепите инфраструктуру как аспект на интерфейсе:

  • ✅ Сервис A вызывает интерфейс kafka на Сервисе B, и этот интерфейс несёт аспект broker, указывающий на модуль kafka-broker.

Брокер становится материализованным аспектом — присутствует в модели, но живёт на другой плоскости, нежели граф вызовов сервисов. Плоскость данных — это данные, а не путевая точка маршрутизации.

Это выигрышно по нескольким счётам. Рёбра показывают реальные зависимости между сервисами, поэтому архитектура остаётся читаемой. Инфраструктура остаётся выразимой без загрязнения основного представления. И вы можете включать раскладку аспекта брокера по запросу, чтобы увидеть, какие соединения используют какой брокер — плоскость данных раскрывается только тогда, когда вы об этом просите.

Тот же шаблон обобщается на инфраструктуру как отдельные слои существования: брокеры сообщений, разделение сетевых зон, логи, метрики. Каждый — своя плоскость, наложенная как слой-аспект, а не вплетённая в граф вызовов сервисов. Смоделируйте систему один раз на бизнес-слое, а затем раскрывайте каждую инфраструктурную заботу как переключаемый слой сверху.

Плоскость хостинга цепляется так же. Вложенная база — это не дно — она сидит на стопке плоскостей развёртывания, каждая связана аспектом, а не вложенностью. База (вложенная в свой сервис) несёт аспект, указывающий на СУБД, на которой работает, например конкретный экземпляр Postgres; эта СУБД, в свою очередь, несёт аспект, указывающий на сервер, ВМ или кластер, на котором развёрнута. Каждый прыжок — другая плоскость архитектуры, поэтому каждый — это соединение через аспект — ровно как аспект broker для Kafka. Вложенность ошибочно подразумевала бы «часть домена»; аспекты верно говорят «живёт на» или «развёрнуто на».

Связанное: поля и аспекты — строковое значение аспекта (классификация) строит общую идентичность, которая образует паутину наложений по всей модели.

Простые модули и интерфейсы хороши по умолчанию — типизация это сахар

Заголовок раздела «Простые модули и интерфейсы хороши по умолчанию — типизация это сахар»

Модуль — это базовый строительный блок, а его типизация — это сахар, добавляемый только когда нужен. Не всему нужен свой тип. Простой модуль — строительный блок по умолчанию; простой интерфейс — соединение модулей по умолчанию. Тянитесь к ним первыми — язык спроектирован так, что их достаточно.

Не изобретайте типы-пустышки. Внутри сервиса вам не нужен свой, бесповеденческий блок service, оборачивающий простой модуль — просто используйте простой модуль. Голый тип, который ничего не добавляет, — это шум.

Добавляйте свой тип, только когда он оправдывает своё существование — когда вы хотите делать подтипы, прикреплять свои виджеты, определять свои требования, добавлять свои поля или явно утверждать, что что-то является определённым типом (где само утверждение и есть ценность). Иначе: простой модуль как строительный блок, простой интерфейс как соединение. Обычно это всё, что нужно.

Моделируйте как можно больше процессов — они доказывают, что модули не мертвы

Заголовок раздела «Моделируйте как можно больше процессов — они доказывают, что модули не мертвы»

Стройте много процессов. В идеале моделируйте их все — это важнее, чем кажется.

Детализация опциональна; покрытие — нет. Вам не нужны полные ветвления, циклы и try/catch в каждом процессе — относитесь к этой детали потока управления как к сахару. Поверхностный процесс всё равно засчитывается.

Покрытие важно, потому что процессы документируют связи модели. Модуль, который не появляется ни в одном процессе, по определению одинок — ни с чем не сотрудничает, ни с чем не соединён. Это, по сути, мёртвый код: кусок без связи с реальным миром. А это почти никогда не так. Модуль без процесса обычно означает, что вы забыли смоделировать процесс, а не что модуль действительно мёртв. Поэтому недостающие процессы — это сигнал: идите найдите процесс, в котором этот модуль участвует, и смоделируйте его.

Есть одно исключение — опускайте очевидное, но только когда оно вложено. Некоторые взаимодействия настолько подразумеваются, что лишь добавляют шума. Микросервис, сохраняющий в свою инкапсулированную базу, — каноничный случай: вы не пишете «сервис сохраняет в БД» как шаг. Считайте микросервис актёром, а вложенную базу — очевидной деталью, которую он несёт. БД всё равно заслуживает своё место в модели — она документирует структуру — ей просто незачем появляться в процессе.

Проверка — это инкапсуляция, а не «кажется очевидным». Вы можете опустить вещь только когда она полностью вложена внутрь актёра, которого вы упоминаете. Не опускайте высокоуровневого равноправного просто потому, что «все им пользуются»:

  • ✅ Опустите собственную вложенную базу сервиса — общайтесь с сервисом.
  • ❌ Не опускайте шлюз. Шлюз — это высокоуровневая идея («всё идёт через шлюз»), а не инкапсулированная деталь. Пропуск его в процессе может скрыть нарушения политики, поскольку шлюз — это ровно то место, где живёт политика.

Инкапсуляция также делает возможными черновики на уровне абстракции. Поскольку вложенную деталь можно опускать, вы можете написать процесс, где крупные высокоуровневые системы общаются между собой, не называя конкретные сервисы внутри них — законный, быстрый черновик. Оговорка в том, что он остаётся черновиком: более низкоуровневое описание обычно лучше, потому что реальный мир может вести себя не так, как предполагает высокоуровневый набросок. И даже в черновике думайте в доменах — пропуск шлюза разрешён, но не приветствуется (риск политики), тогда как пропуск вложенной детали нормален.

Предпочитайте вложенность, но не навязывайте единый корневой модуль

Заголовок раздела «Предпочитайте вложенность, но не навязывайте единый корневой модуль»

Группируйте вещи в подмодули, а не сваливайте их в корень — но только когда это имеет смысл. Вкладывайте, но не перевкладывайте. Тянитесь к группировке в подмодули вместо добавления всего в корень плоско.

Но не изобретайте искусственный единый корневой модуль. Вам не следует оборачивать всё в один модуль system, вбирающий в себя весь мир. Корень хорош как корень: архитектура одной системы законно живёт в корне без зонтичного узла.

Держите процессы локальными к частям, которым они принадлежат

Заголовок раздела «Держите процессы локальными к частям, которым они принадлежат»

Объявляйте процесс в наименьшей области, которая его содержит. Процесс целиком внутри одного сервиса — это локальный процесс на этом сервисе. Процесс, который охватывает несколько сервисов, при том что у вас есть системный модуль, объявляется на системе. Процесс, который охватывает несколько сервисов, где ваша система является корнем — архитектура одной системы без зонтичного модуля — может быть корневым процессом.

Правило — локальность: процесс живёт с тем, кто владеет охватываемым им пролётом, а корень считается законным владельцем, когда архитектура одно-системна.

Повторяющаяся последовательность шагов место в переиспользуемом подпроцессе, вызываемом через do <name>(args). Выносите всё, что иначе повторялось бы между процессами.

Аргументы — только намерение, они не проверяются по типу и не связываются. Передача одного документирует «этому подпроцессу, вероятно, нужно это», так что используйте их, чтобы сделать место вызова читаемым, а не чтобы что-то обеспечивать. Прямые ссылки нормальны: вы можете do подпроцесс, которого ещё нет, и пробел — это TODO (заглушка), а не ошибка — то же правило неполноты-по-замыслу, что и при черновике от процессов. Определите его позже. Можно также экспортировать подпроцесс как межпространственную точку входа: экспортированный subprocess позволяет другому пространству вызвать поведение, не видя его внутренних шагов — сочетайте это со шлюзами для контролируемых межпространственных вызовов.

Это три способа аннотировать элемент. Чёткое различие: аспекты — это неявные, межплоскостные соединения; поля — структурированные данные; описания — неструктурированные данные.

Аспекты — сквозная связность через плоскости

Заголовок раздела «Аспекты — сквозная связность через плоскости»

С точки зрения чистого языкового проектирования аспект выглядит близко к полю — можно смоделировать его как одно поле, чьё значение — объект, держащий всякое. Так думать неправильно. Аспекты существуют для сквозной связности, а не для хранения данных.

Когда несколько элементов несут один и тот же аспект с одним ключом и одним строковым значением, объявляется, что они делят место на плоскости существования — это классификация. Ключ — это плоскость — отдельная плоскость существования, как network-zone. Значение — место на этой плоскости, как dmz, internal или vpc. Элементы, делящие ключ и значение, соединены там. Классификация представляет скорее слабую принадлежность, чем жёсткое ребро — «эти вещи живут в одной зоне» или «используют один брокер».

Вы можете продвинуть плоскость в реальные смоделированные элементы: сделать модули для каждого сетевого сегмента, скомпоновать их и написать процессы, объявляющие сетевые политики между ними. Теперь у вас отдельная система — сетевая плоскость — и аспекты становятся соединениями (членством) от плоскости бизнес-логики к этой сетевой плоскости. Это материализованные аспекты.

Интерфейсы тоже могут нести аспекты (только классификацию — присутствие на интерфейсе не тянет за собой членство). Нормально, что интерфейс соединяется с аспектами — например, интерфейс сервиса, несущий аспект с брокером Kafka, который он использует. Брокер Kafka — это аспект: он не принадлежит диаграмме бизнес-сервисов; он живёт на инфраструктурной плоскости, рядом с СУБД. Затем вы можете читать этот интерфейс как топик Kafka в том брокере.

См. также: Моделирование → Моделируйте бизнес-слой, и Две оси: вложенность — это глубина домена, аспекты соединяют плоскости.

Поля — структурированные данные, а не связность

Заголовок раздела «Поля — структурированные данные, а не связность»

Поле держит структурированные данные, которые вы явно не хотите взаимно соединять. Используйте поля для поэлементных структурированных свойств — версии сервиса, статуса сервиса; для обязательных полей — структурированных данных, предписанных на всех экземплярах типа; для управления виджетами, поскольку поля управляют виджетами; и для любых своих структурированных данных, которые должны оставаться локальными для элемента, а не становиться межплоскостным соединением.

Правило большого пальца: если это свойство вещи — используйте поле; если это соединение с другой плоскостью — используйте аспект. (Ссылки на внешние документы — это свойства, поэтому они — поля — см. Два способа использовать описания.)

Проза. Markdown. См. Два способа использовать описания о том, как использовать их хорошо.

Структура файлов и рабочего пространства

Заголовок раздела «Структура файлов и рабочего пространства»

Главенствующий принцип — организовывать по домену на каждом уровне. Домены — первичная ось структуры, не тип элемента, не технический слой. Папки группируют по домену. Файлы — это меньшие домены: поддомены, фичи, срезы. Всё рабочее пространство должно читаться как иерархия доменов.

Когда в сомнении, как что-то разделить или разместить, спросите, какому домену оно принадлежит — доменный ответ и есть рекомендуемый. Записи ниже — конкретные применения этой одной идеи.

Организуйте файлы по домену, а не по типу элемента

Заголовок раздела «Организуйте файлы по домену, а не по типу элемента»

Группируйте файлы .arch вокруг доменов, а не вокруг рода вещей, которые они содержат.

Не разбивайте файлы по типу элемента:

  • processes.arch
  • types.arch
  • views.arch

Называйте файлы по домену — или поддомену, фиче или срезу — который они описывают:

  • <domain-name>.arch

Доменный файл держит всё, принадлежащее этому домену, вместе — модули, интерфейсы, типы, представления, процессы — так что домен читается как одна связная единица.

Разбиение нормально, когда оно всё ещё в рамках домена. Можно вытащить подмножество в свой файл, но называйте его по домену и заботе, никогда — по голому типу элемента:

  • business-primitives.arch — свои бизнес-типы, используемые в разных модулях
  • <domain>-<typename>.arch — типы для конкретного домена
  • network-security-views.arch — представления в своём файле, когда файл — это связная линза над доменом

Проверка — является ли файл куском домена или целенаправленной линзой, а не свалкой для одной синтаксической категории. network-security-views.arch проходит, потому что это инструмент, сфокусированный на заботе; views.arch не проходит, потому что это просто «все представления».

По умолчанию — одно корневое пространство; вкладывайте пространства только для корпоративных подразделений

Заголовок раздела «По умолчанию — одно корневое пространство; вкладывайте пространства только для корпоративных подразделений»

Пространство — это граница видимости, но осмысленная ось для его вложения — это владение, кто работает над доменом, а не как система технически декомпозируется.

По умолчанию — одно пространство в корне, которое также является пакетом — держите это монорепозиторием. Для подавляющего большинства проектов единое корневое пространство — правильный ответ. Вы можете иметь несколько, но обычно не нужно.

Вкладывайте пространства, только когда владение действительно расходится. Вложенное пространство заслуживает своё место, когда другой набор людей владеет им и работает над ним, и домен очень велик и закрыт — закрыт не в смысле системной архитектуры, а в организационном, как корпоративные подразделения, которые едва взаимодействуют. Типичный триггер — действительно крупная компания с несколькими доменами, за которыми стоят сильные, отдельные доменные лидеры, которые толком не сотрудничают. Это разделение на оргуровне и выражают вложенные пространства.

Речь о вложенных пространствах, а не о вложенных пакетах — это другая забота. Пространства проводят линии владения и видимости; не тянитесь к ним, чтобы моделировать техническое расслоение.

Держите типы близко к их экземплярам; общий пакет типов — это запасной выход

Заголовок раздела «Держите типы близко к их экземплярам; общий пакет типов — это запасной выход»

По умолчанию держите типы в том же пространстве имён, что и — и доменно близко к — их экземпляры. Тип живёт рядом с модулями, которые его используют.

Запасной выход для крупных оргструктур — выделенный пакет типов, вложенный внутрь пакета. Когда вы крупная компания — будь у вас одно очень большое пространство имён или несколько — вы можете положить общие типы в их собственный пакет внутри пакета. Этот пакет изолирован (непрозрачен, экспортирует только то, что выбирает), что нормально, и вы можете затем использовать его отовсюду, где вам нужны эти типы. Это покупает чистое разделение: общий словарь живёт в одном принадлежащем, изолированном месте, а остальная модель зависит от него явно, а не дублирует или рассыпает определения типов.

Предпочитайте явный use; избегайте use * из пакета

Заголовок раздела «Предпочитайте явный use; избегайте use * из пакета»

По умолчанию импортируйте явно — выбирайте и подбирайте. Относитесь к пакету как к палитре и берите конкретные типы, которые вам нужны, а не всё целиком.

Не вытягивайте всё из крупного пакета:

  • use * from store.backend

use * затаскивает поток типов, которые вы не используете, и связывает вас со всей поверхностью пакета.

Соберите свой рабочий набор явно:

  • use postgres, kafka from store.backend

Собирайте абстракции, которые вам действительно нужны, по имени, и вы получите свою кураторскую библиотеку, а не полный инвентарь пакета.

use * приемлем для небольших библиотек, особенно внутрикорпоративных самописных — вы её написали и хотите всю. Но будьте строги со стандартной библиотекой: всегда относитесь к ней как к палитре «выбирай-и-подбирай» и никогда не делайте use * стандартной библиотеки.

Стандартная библиотека — это опциональные «батарейки» — стройте свою, когда нужно

Заголовок раздела «Стандартная библиотека — это опциональные «батарейки» — стройте свою, когда нужно»

Стандартная библиотека — это «батарейки в комплекте», а не обязательство. Используйте её свободно, но вы совершенно вольны построить собственную библиотеку с нуля. Это приятное дополнение поверх голого языка, нацеленное быть хорошим, исчерпывающим набором по умолчанию для архитектуры.

Чтобы начать, опирайтесь на элементы стандартной библиотеки и заменяйте их позже без волнений — старт на стандартной библиотеке не запирает вас. Крупной компании может понадобиться своя библиотека, часто не про виджеты (виджеты стандартной библиотеки переиспользуемы), а про оргспецифичную семантику: требования к сопровождающему и владению, отношения между департаментами или подразделениями и тому подобное. И расширение лучше замены — вы всегда можете расширить элемент стандартной библиотеки дополнительными требованиями или опциями связывания, а не переписывать его. Механизм библиотек прост, так что авторство своей низкозатратно; если вашей компании это нужно — делайте.

Думайте о библиотеке как о потенциальной мета-модели. Язык поставляет фундаментальную модель — универсальную и полезную — но библиотека может быть чем-то большим, чем удобство: она может добавлять ограничения и руководства, которые направляют то, как вы думаете. Через линзу «приятного дополнения» библиотека — это набор удобных виджетов и моделей, как arch.extras с её элементами actor и group, которые делают диаграмму живее. Через линзу мета-модели библиотека навязывает нотацию или дисциплину — нотация C4 как библиотека ArchLang — это очень даже мета-модель, и её принятие формирует то, как вы декомпозируете и рассуждаете. Выбирайте библиотеку за то, что она делает с вашим мышлением, а не только за компоненты, которые она вручает.

Объявляйте импорты на уровне пространства, чтобы они были общепространственными

Заголовок раздела «Объявляйте импорты на уровне пространства, чтобы они были общепространственными»

По умолчанию делайте use вещей в самом пространстве — манифесте package.archspace — чтобы импортированная палитра была общепространственной и доступной везде. Стройте палитру в одном месте и надёжно переиспользуйте её по всему пространству, а не реимпортируйте одни и те же типы файл за файлом. Локальные, очень мелкие, разовые нужды могут импортировать ближе к использованию, но уклон — к единому объявлению на уровне пространства.

Причина — обнаруживаемость: манифест пространства становится явным списком того, что доступно всем работающим в нём. Кто-то может открыть пространство и увидеть всю палитру с одного взгляда. UI предлагает для этого просмотрщик типов, но и сам код должен быть самодокументируемым, и явный список use на уровне пространства — это и есть та документация.

Построение настоящей архитектуры означает пакет, централизованное управление, полную настройку. Но отдельный самостоятельный файл .arch — это первоклассный, поощряемый способ начать — идеальный для быстрого черновика. Файл-локальные импорты существуют именно для поддержки этого.

Единственное ограничение в том, что самостоятельный файл может зависеть только от локально определённых типов и типов стандартной библиотеки, а не от своих типов пакета. Нет синтаксиса связывания пакетов для однофайлового определения, поэтому межпакетные свои типы недосягаемы. Это нормально для черновиков: стандартная библиотека огромна и полезна, и вы можете далеко уйти, моделируя быструю идею на стандартной библиотеке плюс локальных типах, прежде чем продвигать черновик в управляемый пакет.

ID должны быть бессмысленными и постоянными

Заголовок раздела «ID должны быть бессмысленными и постоянными»

ID никогда не должен меняться. В этом весь смысл ID: даже когда смысл модели меняется — переименован, перераспределён по областям, перемещён — ID остаётся фиксированным. ID — это стабильная идентичность; всё остальное изменчиво.

Поэтому ID должны быть бессмысленными. ID не должен встраивать имя домена, тип или любое другое семантическое содержание, потому что что угодно осмысленное в итоге становится неверным, и тогда вас тянет изменить ID — чего делать нельзя. Стремитесь к по-настоящему случайному ID.

Правило. Никогда не изменяйте ID, ни по какой причине.

Инструменты делают это за вас: автодополнение генерирует по-настоящему случайный ID, так что предпочитайте инструмент всегда, когда возможно — рандомизировать вручную трудно. Если вам придётся набирать ID вручную и лучше не получается, приемлемо использовать <filename>-<index> или <filename>-<random-postfix>, с именем файла как основным куском. Оговорка в том, что модуль может позже переехать в другой файл, поэтому ID на основе имени файла устареет как имя — но это нормально, потому что ID всё равно бессмыслен и никогда не меняется независимо от того, где живёт модуль. Устаревший на вид — приемлемо; изменение ID — нет. Это запасной вариант, потому что ID трудны, а не идеал — если вы можете найти лучший способ получить случайность вручную, делайте.

В реальной архитектуре у всего должен быть ID

Заголовок раздела «В реальной архитектуре у всего должен быть ID»

Опциональность ID — это удобство для быстрых черновиков, а не лицензия пропускать их вдолгую. Для правильной, добротной, долгосрочной архитектуры у каждого элемента есть стабильный ID — у модулей, интерфейсов, типов, процессов, представлений.

Полезная ментальная модель: когда вы предлагаете изменения, у вещей должны быть ID. Если это часть долговечной модели, которую люди ревьюят и развивают, у неё есть идентичность, переживающая переименования и перемещения. Пропускайте ID только пока набрасываете.

  • Модули → UpperCamelCase (например, OrderService).
  • Типы → lower_snake_case (например, order_id, payment_method).
  • Интерфейсы → lowerCamelCase (например, placeOrder, getById).
  • Filesets → UpperCamelCase.
  • Процессы → UpperCamelCase (например, Checkout, OrderFulfilment).
  • Пакеты → lower.dotted.segments, строчными и через точку.
    • Публикуете типы? Используйте префикс из обратного домена компании (например, com.acme.payments), чтобы имя было глобально уникальным адресом. Это нужно только для пакетов, чьи типы вы намерены публиковать или делиться ими.
    • Свой репозиторий? Просто имя компании или проекта, без доменного префикса (например, acme.payments или payments). Это нормально.
    • Пакеты стандартной библиотеки сохраняют свои существующие корни (arch.cloud.aws, arch.extras).
  • Пространства → тоже через точку, продолжая имя пакета. Пространство концептуально — это подпакет своего корневого пакета, поэтому его имя расширяет корневой путь через точку, разделённый по доменам (например, корень acme → пространство acme.retailbanking). Тот же строчный стиль через точку, что и у пакетов.

(Именование пространств и пакетов — это рекомендация, а пока не жёсткое соглашение — открыта к пересмотру.)

Интерфейсы зеркалят реальный интерфейс модуля — называйте их по протоколу

Заголовок раздела «Интерфейсы зеркалят реальный интерфейс модуля — называйте их по протоколу»

Интерфейс моделирует фактический интерфейс, который модуль выставляет, поэтому его имя должно следовать семантике реального протокола. Для REST используйте <action><Resource> — REST-действие плюс имя ресурса, как getOrder или createInvoice. Для RPC используйте глаголы — фактические имена вызовов, как chargeCard или reserveSeat.

Направляющая идея — не изобретать абстрактные имена интерфейсов, а отражать то, что модуль действительно предлагает, в терминах его протокола. Регистр по-прежнему следует соглашениям выше: lowerCamelCase.

Используйте поверхности — группируйте связанные интерфейсы

Заголовок раздела «Используйте поверхности — группируйте связанные интерфейсы»

Поверхности хороши; используйте их, не игнорируйте. Группируйте интерфейсы по поверхности, когда они образуют связную группу. Если интерфейс не связан с остальными, ему не место в группе — вынесите его в другое место, в свою поверхность или прямо на модуль. Интерфейс, стоящий особняком, может жить в самом модуле, а не быть втиснутым в поверхность.

Поверхности — это домены интерфейсов. Поверхность группирует интерфейсы в связный домен интерфейсов — поверхность userInteraction держит интерфейсы пользовательского взаимодействия. Это то же доменное мышление, что движет организацией файлов и модулей, применённое к интерфейсам, и поверхности могут и должны вкладываться, когда у домена интерфейсов есть поддомены.

Проверка группировки та же, что для файлов: представьте, что выписываете все эти привязки интерфейсов. Привязки, которые вы охотно держали бы в одном файле, принадлежат одной поверхности; привязки, которые вы скорее разбили бы на отдельные файлы, принадлежат разным поверхностям. Это тот же инстинкт, что и в Организуйте файлы по домену — поверхности это просто домены на уровне интерфейсов.

Стройте переиспользуемые поверхности. Это поощряется — как мы делаем с REST CRUD: стандартная форма поверхности, применяемая по модулям, вместо переобъявления одних и тех же интерфейсов каждый раз.

ArchLang — не инструмент моделирования баз данных или API

Заголовок раздела «ArchLang — не инструмент моделирования баз данных или API»

Знайте, для чего ArchLang не предназначен, и берите правильный инструмент. Для моделирования баз и таблиц используйте настоящий SQL DDL — ArchLang предлагает способы рисовать базы, таблицы и связи между ними, но это не язык моделирования таблиц и не пытается им быть; эти возможности — для неформальных набросков. Для моделирования API используйте OpenAPI или подходящий IDL для протокола.

Черновики нормальны — детали место в другом месте. Вы вольны импровизировать: набросать конкретные поля API или идеи контракта в описании интерфейса, накидать несколько столбцов таблицы. Только не путайте набросок с источником истины. Когда вам нужна реальная детализация, особенно в крупной компании, ссылайтесь вовне на надлежащую спецификацию, а не воспроизводите её. На интерфейсе поле может ссылаться на спецификацию OpenAPI, хранящуюся в другом месте. На базе ссылайтесь на свою схему или каталог данных — каталог вроде DataHub или Amundsen, либо инструмент схем и миграций вроде Atlas или Liquibase — систему, которая автоматически собирает и структурирует ваши схемы.

Ссылайтесь вовне — сделайте архитектуру узлом ссылок

Заголовок раздела «Ссылайтесь вовне — сделайте архитектуру узлом ссылок»

Всегда ставьте внешние ссылки. Внутренние архитектурные ссылки — это данность, ведь язык для этого. Суть здесь — внешние ссылки: соединяйте каждый элемент с реально-мировым ресурсом, который он представляет, столько, сколько сможете.

Прикрепляйте поле-ссылку всюду, где такая есть. Сервис ссылается на свой репозиторий. База ссылается на свою консоль или вход. Инфраструктура наблюдаемости (Prometheus, Grafana, на которые ссылаются через аспекты) ссылается на свою админ-панель или дашборд. Документы ссылаются на страницы Confluence или вики. Окружения ссылаются на dev, staging и prod.

Выигрыш в том, что модель становится узлом ссылок — одним местом, которое маршрутизирует вас ко всему о системе. Ссылки полезны, ссылки упрощают, и хорошая архитектурная документация живёт через свои ссылки.

Ссылаться дёшево, потому что это поэлементное украшение. Это укладывается в проход модуль-за-модулем: выберите один модуль, сосредоточьтесь целиком на украшении только его — описание, поля, ссылки — затем двигайтесь дальше. Не нужно держать всю систему в голове. По одному элементу за раз вы неуклонно выстраиваете богатую, хорошо связанную запись для каждого куска.

Одиночные пробелы — без табличного выравнивания

Заголовок раздела «Одиночные пробелы — без табличного выравнивания»

Разделяйте токены одиночным пробелом. Не добивайте множественными пробелами, чтобы выстроить всё в столбцы.

  • ❌ Не выравнивайте шаги процесса или интерфейсы в таблицу (лишние пробелы, чтобы места вызова или типы выстроились вертикально).
  • ✅ Один пробел между токенами: <type> <action> <target>.

Выравнивание по столбцам выглядит опрятно, но гниёт: каждое переименование заново ломает столбцы, и оно порождает шумные, только-выравнивающие диффы. Форматирование одиночными пробелами стабильно и держит диффы осмысленными.

Точечная нотация против фигурных скобок — по числу значений

Заголовок раздела «Точечная нотация против фигурных скобок — по числу значений»

Есть два способа писать поля, аспекты и тому подобное. Выбирайте по тому, сколько значений у вас есть. Одно значение или одна строка хорошо читаются как точечная нотация, вроде security.zone. Пара значений (около двух) может пойти любым путём — точечная нотация нормальна, скобки нормальны, на ваш выбор. Много значений (около пяти и более) лучше читаются в фигурных скобках; точечная нотация не запрещена, но скобки выигрывают при таком размере.

Встроенные скобки для одной строки нормальны — держите их короткими. Для небольшого числа пар можно написать aspect { first: x; second: y } в одну строку, но ограничивайте встроенные скобки двумя парами, максимум тремя. Сверх этого — переносите на несколько строк. Ни одна из нотаций никогда не обязательна — это предпочтения, а не правила.

В грубом порядке приоритета:

  1. Разделение доменов — самое главное. Разбита ли модель на правильные домены? Сидит ли каждый кусок в домене, которому принадлежит?
  2. Границы — все ли вещи уважают свои границы? Следите за элементами, тянущимися через границу, через которую не должны.
  3. Именование — тоже важно, пусть и ниже приоритетом. Переименование тривиально в ArchLang, но дорого в реальной жизни, где имя ложится на реальные системы, команды и код. Поэтому относитесь к именам серьёзно сейчас, пока их дёшево менять в модели — плохое имя, дошедшее до продакшена, становится дорогим реально-мировым переименованием.
  4. Незакрытые TODO (черновой долг) — TODO — это непродвинутые заглушки: модули, интерфейсы или подпроцессы, на которые ссылаются, но которые не определены. Они нормальны посреди черновика, но зрелая модель не должна доходить до продакшена с ними. Проверяйте число и подумайте о том, чтобы поставить «готово к предложению или принятию» в зависимость от нуля TODO. Отличайте TODO от ошибки (противоречия) и предупреждения (не приветствуется, но допустимо) — TODO это недостающая деталь, а не неверная.

Стройте границы политиками (когда политики появятся)

Заголовок раздела «Стройте границы политиками (когда политики появятся)»

Когда политики будут реализованы, не забывайте обеспечивать границы политиками — а не только соглашением. Границы, которые имеют значение, должны подкрепляться явной политикой, чтобы модель обеспечивала разделение, а не полагалась на ревьюеров, ловящих каждое пересечение.

Быстрый указатель «нельзя», каждый указывает на запись, которая его объясняет.

  • Инфраструктура как путевая точка маршрутизации — A → broker → B. Моделируйте логический вызов; прикрепляйте инфру как аспект. → Моделируйте бизнес-слой.
  • Свои типы-пустышки — бесповеденческий блок service, оборачивающий простой модуль. → Простые модули и интерфейсы хороши по умолчанию.
  • Модуль-на-класс или слишком глубокая вложенность — ложит программирование на архитектуру, неподдерживаемо. → Выбирайте правильную гранулярность.
  • Ряд-сервисов-с-цилиндрами-БД — каждый сервис подключён к своей базе как соседняя коробка. БД с одним владельцем вкладываются внутрь сервиса. → Инкапсулируйте ресурсы с одним владельцем.
  • Вложение межплоскостного хоста — помещение СУБД или сервера внутрь модуля вместо связи аспектом. Развёртывание — другая плоскость. → Две оси: вложенность — это глубина домена, аспекты соединяют плоскости.
  • Искусственный единый корневой модуль system — оборачивание всего мира в один зонтичный узел. → Предпочитайте вложенность, но не навязывайте единый корневой модуль.
  • Модули ни в одном процессе — читается как мёртвый код; обычно забытый процесс. → Моделируйте как можно больше процессов.
  • Файлы, названные по типу элемента — processes.arch, types.arch, views.arch. → Организуйте файлы по домену, а не по типу элемента.
  • Вложение пространств для технического расслоения — пространства для владения, а не для декомпозиции. → По умолчанию — одно корневое пространство.
  • use * из крупных или стандартных библиотек — затаскивает всю поверхность. → Предпочитайте явный use.
  • Осмысленные ID — встраивание домена, типа или семантики; искушает изменить ID позже. → ID должны быть бессмысленными и постоянными.
  • Изменение ID — когда-либо, по любой причине. → ID должны быть бессмысленными и постоянными.
  • Границы только по соглашению — обеспечивайте политиками, как только доступны. → Стройте границы политиками.
  • Полная схема БД или спецификация API внутри ArchLang — это не замена DDL или OpenAPI; ссылайтесь вовне на реальную спецификацию. → ArchLang — не инструмент моделирования баз данных или API.
  • Нет внешних ссылок — элементы, которые не указывают на свой реальный репозиторий, консоль, дашборд или документы. → Ссылайтесь вовне — сделайте архитектуру узлом ссылок.