Архитектура и DDD¶
Agate следует Domain-Driven Design и Clean Architecture. Эта страница
формулирует правила, которым подчиняется каждый крейт; они являются контрактом
для всех, кто работает в репозитории (см. AGENTS.md).
Правило зависимостей¶
Зависимости направлены только внутрь. Внутри крейта ограниченного контекста слои являются модулями, добавляемыми наружу по мере роста контекста:
flowchart LR
subgraph crate["один крейт ограниченного контекста"]
direction LR
domain["domain<br/>чистый: нет async,<br/>нет I/O, нет фреймворков"]
application["application<br/>сценарии CQRS,<br/>порты (трейты)"]
infrastructure["infrastructure<br/>адаптеры"]
presentation["presentation<br/>HTTP-обработчики"]
setup["setup<br/>корень композиции"]
end
presentation --> application
infrastructure --> application
application --> domain
setup --> presentation
setup --> infrastructure
setup --> application
setup --> domain
- Слой
domainчист: нетasync, нет I/O, нет зависимостей от фреймворков. Это обеспечивается структурно ацикличным графом крейтов — модуль домена не может использовать то, чего нет вCargo.tomlкрейта. - Полагайтесь на абстракции, а не на реализации. Порты — это трейты; конкретные
адаптеры внедряются в корне композиции. DI-фреймворк (
froodi) остаётся вне слоёв domain и application. - Композиция вместо наследования. В Rust нет наследования; переиспользование через трейты + методы по умолчанию, встраивание структур, дженерики и derive.
Крейт = ограниченный контекст, без общего ядра¶
Каждый крейт владеет своими агрегатами и своим доменом. Общего ядра нет.
Межконтекстные технические возможности — криптография яркий пример —
публикуются как библиотеки общего поддомена со стабильным интерфейсом, а не
как разделяемые доменные модели. Зависимость от agate-crypto подобна
зависимости от sha2 или ring: техническая возможность, а не общий домен.
Когда двум контекстам нужно взаимодействовать (например, структурной инспекции прокси и решениям политики о контенте), они встречаются только в корне композиции, который переводит между их словарями. Контексты никогда не импортируют друг друга.
Строительные блоки DDD в Rust¶
| Блок | Как реализован |
|---|---|
| Объект-значение | #[derive(Clone, PartialEq, Eq, Hash)] + impl ValueObject; приватные поля; валидирующий «умный» конструктор new(..) -> Result<Self, DomainError> (parse, don't validate); неизменяем — мутаторы возвращают новое значение. |
| Сущность | Реализует Entity (равенство по идентичности). Идентичность и жизненный цикл составлены из явных частей (поле id + объект-значение Timestamps), а не из мешка Meta. |
| Корень агрегата | Встраивает EventCollection<E>, реализует AggregateRoot. Конструирование доступно только через Factory, внедряющую коллабораторов; new/reconstitute — pub(crate). |
| Доменный сервис | Структура-юнит без состояния + impl DomainService. |
| Фабрика | Единственный публичный способ построить агрегат; внедряет коллабораторов (clock, генератор id). |
| Ошибки | Иерархия вложенных enum (DomainError::Time(TimeError)), связанная через Error::source(). |
| Порты | Clock и IdGenerator — доменные порты. Персистентность и внешние системы — порты приложения, разделённые по CQRS: командный шлюз загружает/сохраняет агрегат (сторона записи); запросный шлюз возвращает модели чтения/DTO (сторона чтения). |
Персистентность по CQRS¶
Персистентность разделена, чтобы модель записи (агрегат) и модель чтения (DTO / проекции) развивались независимо:
flowchart LR
cmd["Командный обработчик"] -->|загрузить / сохранить агрегат| cg["Командный шлюз"]
qry["Запросный обработчик"] -->|модель чтения / DTO| qg["Запросный шлюз"]
cg --> store[("хранилище")]
qg --> store
Почему это важно¶
Чистота домена и ацикличный граф крейтов означают, что критичная для корректности логика (доказательства Меркла, вычисление вердикта, инварианты объектов-значений) тестируется изолированно без сети и часов, а компилятор обеспечивает слоистость. Алгебраические и криптографические инварианты (например, round-trip доказательств Меркла, отклонение подделки) покрываются proptest; контексты без таких инвариантов, например политики, используют сценарные example-based тесты.
См. страницы по контекстам, где описано, как каждый крейт применяет эти правила: crypto, audit, proxy, policy, server.