Эта страница переведена с английского. Читать страницу на английском
Zeyn работает в одном репозитории, но многие команды держат каждое приложение в своём репозитории, а общий код (entity, сервисы, целые feature) выносят в библиотеки, которыми пользуются несколько приложений. Это руководство показывает, как всё настроить, и одно правило, благодаря которому это работает.
Что где лежит
| Репозиторий | Создаётся командой | Содержит |
|---|---|---|
| Приложение | zeyn new billing | main.rs, app.rs, feature приложения, конфигурацию, каталоги приложения |
| Общая библиотека | zeyn new acme-domain --lib | entity, сервисы, feature, их каталоги, CATALOGS |
Feature библиотеки ничем не отличается от feature приложения: модели, action, экраны, права и переводы лежат вместе в одной папке. Приложение решает, какие feature библиотеки оно подключает.
let i18n = I18n::new("en")
.catalogs(acme_domain::CATALOGS)?
.catalog("en", include_str!("../locales/en.json"))?
.catalog("tr", include_str!("../locales/tr.json"))?;
App::new(t!("app.name"))
.i18n(i18n)
.service(acme_domain::customers::CustomerStore::default())
.feature(acme_domain::customers::feature())Каталоги библиотек загружаются первыми, поэтому приложение может переопределить строку библиотеки, задав тот же ключ. Начинайте ключи библиотек с имени feature (customers.list), чтобы две библиотеки никогда не сталкивались.
examples/shared-domain это такая библиотека. examples/billing и examples/crm оба подключают её feature customers; crm переименовывает «Customers» в «Accounts» в своём каталоге.
Чем должна владеть библиотека
Правило сирот (orphan rule) в Rust гласит, что трейт можно реализовать только в крейте, который определяет трейт или тип. Приложение не может реализовать zeyn::Model или zeyn::Entity для типа из библиотеки, поэтому:
- Ставьте
#[entity],#[model]и#[choice]на тип в библиотеке, рядом с его правилами. - Держите правила валидации (
#[rule(...)]) на общем типе. Тогда каждое приложение проверяет одинаково. - Приложение, которому нужно дополнительное поведение, оборачивает тип (
struct Invoice { customer: Customer, ... }) или добавляет собственный трейт. Оно не правит тип библиотеки.
Как приложения получают библиотеку
| Вариант | Плюсы | На что обратить внимание |
|---|---|---|
| Git-зависимость с тегом | Ничего не нужно запускать, работает с любым Git-сервером | Cargo забирает весь репозиторий; каждое приложение указывает URL |
| Приватный реестр Cargo (Kellnr, реестр пакетов GitLab и другие) | Обычные зависимости version = "0.3", разрешение semver | Сервис, который нужно запускать, резервировать и защищать |
| Один репозиторий-workspace для всего | Одна сборка, один Cargo.lock, рефакторинг одним коммитом | Каждая команда видит каждое приложение; доступ регулируется на уровне репозитория, а не папки |
Начните с Git-тегов. Переходите на реестр, когда библиотек становится больше пары или когда разбор версий вручную становится утомительным. Код приложения не меняется; меняются только строки в Cargo.toml.
[dependencies]
zeyn = { git = "https://git.example.com/platform/zeyn.git", tag = "v0.0.2" }
acme-domain = { git = "https://git.example.com/shared/acme-domain.git", tag = "v0.3.0" }zeyn new по умолчанию записывает zeyn = "<version>". Пока Zeyn не опубликован, замените эту строку на Git-форму выше в каждом новом приложении и библиотеке.
Правило: одна версия Zeyn везде
Каждая библиотека и каждое приложение должны зависеть ровно от одного и того же Zeyn. Для Git-зависимостей это значит тот же URL и тот же тег.
Cargo считает два разных источника Zeyn двумя разными крейтами. Библиотека, собранная с тегом v0.0.2, возвращает zeyn::Feature, который приложение, собранное с v0.0.3, принять не может, и компилятор сообщает о несоответствии типов между двумя сущностями с одним и тем же именем. Реестр смягчает это до «той же совместимой по semver версии», но правило остаётся: один Zeyn на сборку.
Чтобы обновить Zeyn:
- Обновите тег в каждой библиотеке, запустите её тесты, выпустите новый тег библиотеки.
- Обновите Zeyn и теги библиотек в каждом приложении вместе, одним коммитом.
cargo tree -d -i zeyn перечисляет дубликаты. Если он выводит больше одного Zeyn, какая-то зависимость всё ещё указывает на старый тег.
Шаг за шагом
- Создайте библиотеку и отправьте её в отдельный репозиторий.
zeyn new acme-domain --lib
cd acme-domain
cargo test
git init && git add . && git commit -m "First version"
git tag v0.1.0 && git push origin main --tags- Добавьте её в
Cargo.tomlприложения с тегом и подключите её каталоги, сервисы и feature, как показано выше. - Разрешите CI приложения читать библиотеку. Сгенерированный
.gitlab-ci.ymlпереписывает URLhttps://<gitlab host>/иssh://git@<gitlab host>/так, чтобы использовать токен задания. В GitLab откройте проект библиотеки, затем настройки CI/CD, Job token permissions, и добавьте проект приложения в список разрешённых. - Запускайте
app.check()в тесте приложения (сгенерированныйtests/app.rsэто делает). Он падает, когда у подключённой feature есть структурные проблемы, например ключ перевода, которого нет в одном из языков.
Работа над библиотекой и приложением одновременно
Направьте приложение на ваш локальный checkout, не редактируя Cargo.toml. Поместите это в .cargo/config.toml приложения и не добавляйте этот файл в Git:
[patch."https://git.example.com/shared/acme-domain.git"]
acme-domain = { path = "../acme-domain" }Когда изменение готово, уберите это, поставьте тег на библиотеку и обновите тег в приложении.
Выпуск библиотеки
- Повысьте
versionвCargo.toml. - Запустите
cargo semver-checks, если он установлен. Он сообщает об изменениях публичного API, ломающих вызывающий код. Ломающее изменение требует новой минорной версии, пока библиотека ниже 1.0. - Закоммитьте, поставьте тег
v<version>, отправьте тег. Задание CItag-matches-versionпадает, когда тег иCargo.tomlрасходятся.