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

2. Ваша первая архитектура

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

Мы будем использовать только базовый язык — базовые типы module, surface, interface плюс конструкции верхнего уровня process и view — без стандартной библиотеки. Дальнейшие главы вводят более богатые типы (service, command, event); Глава 11 подробно их разбирает. Старт с базовым языком держит фокус на том, что делает сам язык, отдельно от соглашений.

Смоделируем платёжный домен: клиент платит, платёжный сервис авторизует, реестр записывает. И больше ничего.

Каждый проект начинается с манифеста. Создайте package.archspace:

package: shop

Одна строка. Поле package: называет корень пакета; стандартную библиотеку пока не импортируем, потому что используем только базовые типы. (Имена пакетов — в нижнем регистре, через точку; для одного проекта голого shop достаточно.)

Глава 12 подробно разбирает манифест. Пока хватит этого.

Добавьте payments.arch в тот же каталог:

module Payments {
aspect team: "Platform"
"Authorizes and captures card payments."
interface authorize
interface capture
interface refund
}
module Ledger {
aspect team: "Finance"
"Immutable financial record of every transaction."
interface record
}
module Customer {
"The person initiating the payment."
}

Вы объявили три модуля базовым типом module. У каждого есть:

  • Тип (module) — базовый тип языка.
  • Имя (Payments, Ledger, Customer) — как остальная часть модели на него ссылается. Имена модулей — в UpperCamelCase.
  • Аспект team — кто им владеет (необязательно; аспекты каскадируют на всё вложенное, с возможностью переопределения).
  • Описание — голый строковый литерал. Обычный markdown плюс два расширения, с которыми вы познакомитесь в Главе 10.
  • Ноль или больше интерфейсовinterface authorize и т. д. Это то, что другие модули могут попросить этот сделать. Имена интерфейсов — в lowerCamelCase и читаются как реальная операция: глагол для RPC-вызова (authorize), действие-плюс-ресурс для REST (подробнее в Главе 5).

У Customer интерфейсов нет. Это нормально — не каждый модуль выставляет операции наружу. (Типы стандартной библиотеки добавляют семантические различия вроде user для сущностей, которые только инициируют вызовы; пока всё — просто module.)

Мы не садились и не спрашивали «что с чем соединяется». Мы описали каждый модуль сам по себе — чем он является, что он умеет — и остановились. Соединения приходят позже, и они выпадают из процесса, а не рисуются. Эта привычка в два прохода (описать состав, затем рассказать историю) — основа авторской работы в ArchLang.

Попробуйте вживую — отредактируйте исходник выше, диаграмма обновится ниже. Наводитесь на идентификаторы, жмите F2 для переименования, Ctrl/⌘ Space для автодополнения:

Loading editor…

Сохраните файл и запустите:

Окно терминала
archlang validate .

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

Диаграмма выглядит просто, потому что у базового типа module нет специализированного отображения. Коробки подписаны именами; один общий виджет для всех трёх. Стандартная библиотека вводит виджеты под каждый тип (Глава 11); в этой главе суть в простых коробках — они делают лежащую под ними структуру очевидной.

Добавьте checkout.arch:

process Checkout {
Customer > Payments.authorize
Payments > Ledger.record
Customer > Payments.capture
Payments > Ledger.record
}

Процесс — последовательность шагов. Каждый шаг имеет форму Caller > Callee.Interface:

  • Вызывающий (слева от >) — модуль, делающий вызов.
  • Принимающий (справа от >) — интерфейс, конкретная вызываемая операция.

Сохраните — предпросмотр обновится. У диаграммы теперь есть стрелки. Их не рисовали — они выведены из процесса. Это первый принцип из предисловия в действии: поведение — источник структуры.

Сдвиг мышления. В большинстве инструментов диаграмм вы рисуете стрелку, потому что два сервиса общаются. В ArchLang вы объявляете шаг процесса; стрелка появляется, потому что что-то объявило, что общается. Удалите шаг — стрелка исчезнет. Добавьте ещё шаг — появится новая стрелка. Граф зависимостей — всегда функция поведения.

Откройте payments.arch и добавьте четвёртый интерфейс:

module Payments {
aspect team: "Platform"
"Authorizes and captures card payments."
interface authorize
interface capture
interface refund
interface void // new
}

Сохраните. Предпросмотр обновится. void появляется в узле Payments; стрелок к нему нет, потому что ни один процесс пока его не вызывает.

Теперь откройте checkout.arch и добавьте шаг:

process Checkout {
Customer > Payments.authorize
Payments > Ledger.record
Customer > Payments.capture
Payments > Ledger.record
Customer > Payments.void // new — customer cancels mid-checkout
}

Сохраните. Предпросмотр снова обновится. К void теперь идёт стрелка.

Вы добавили интерфейс и шаг процесса в две правки, и диаграмма последовала за вами. Не надо переделывать схему, не надо двигать коробки.

shop/
├── package.archspace
├── payments.arch
└── checkout.arch

Три файла, три модуля, один процесс. Полная (хоть и минимальная) архитектура, написанная на чистом базовом языке. Каждая диаграмма, каждая стрелка зависимости, каждый анализ влияния, который ArchLang может для вас сделать, выводится из этих файлов.

  • Рабочее пространство начинается с манифеста package.archspace, называющего пакет.
  • Базовых типов module и interface хватает для моделирования структуры и выставленных наружу операций.
  • Процессы — последовательности шагов Caller > Callee.Interface. Стрелки в диаграммах выводятся из процессов — они не рисуются напрямую.
  • Предпросмотр редактора перерендеривается на сохранение. Цикл разработки: править файл → видеть диаграмму.

Глава 3: Чтение диффа → — внести изменение в архитектуру и посмотреть, как ArchLang показывает, что изменилось.