# ddd-toolkit
[](https://crates.io/crates/ddd-toolkit)
[](https://docs.rs/ddd-toolkit)
[](https://github.com/ryoshrimp/ddd-toolkit/actions/workflows/ci.yml)
[](#ライセンス)
ドメイン駆動設計のトレイトとヘルパーを提供するファサードクレートです。
[`ddd-toolkit-core`](crates/core) 0.4.1 の公開 API と、
[`ddd-toolkit-macro`](crates/macro) 0.4.1 の四つの derive マクロを再エクスポートします。
公開 API はすべて `ddd_toolkit::` から利用できます。
最新の Rust stable(edition 2024)で検証しています。
最低サポート Rust バージョン(MSRV)は定めていません。
このリポジトリは、ファサード・core・macro を含む Cargo ワークスペースです。
crates.io では別々のパッケージとして公開し、開発とテストを一緒に行います。
開発手順は [CONTRIBUTING.md](CONTRIBUTING.md)、公開手順は
[リリースガイド](docs/releasing.md)を参照してください。
## インストール
トレイトとヘルパーだけを使い、derive マクロを使わない場合:
```toml
[dependencies]
ddd-toolkit = { version = "0.3", default-features = false }
```
derive マクロを使う場合(デフォルトで有効):
```toml
[dependencies]
ddd-toolkit = "0.3"
ddd-toolkit-core = "0.4.1"
```
**重要:** マクロ 0.4 は、ファサード経由で呼び出す場合も利用側クレートに
`ddd-toolkit-core` の直接依存を要求します。コア依存の別名には対応しています。
以前のようにファサードだけに依存する構成では derive を利用できません。
マクロの再エクスポートによって依存の解決方法が変わるわけではありません。
ファサード 0.3.0 は core/macro 0.4.0 を採用しており、ファサード 0.2 から
破壊的な API 変更があります。後述の移行ガイドと [CHANGELOG.md](CHANGELOG.md)を参照してください。
## 提供する機能
- `Entity`、`ValueObject`、`AggregateRoot`、`DomainEvent` のトレイトと derive。
- `Repository` / `AsyncRepository`、`DomainEventPublisher` /
`AsyncDomainEventPublisher`、`dispatch_events` / `dispatch_events_async`。
- CQRS のコマンド/クエリハンドラーとライフサイクルコンテキスト。
- イベントストア、リビジョンの型、集約の再生、スナップショット。
- イベント処理、プロジェクション、チェックポイント、トランザクショナルアウトボックスの契約。
- 仕様、ドメインサービス/ポリシー/ファクトリー、プロセスマネージャー、
Unit of Work、イベントのアップキャスター。
- 書式出力の秘匿化と破棄時のゼロ化を行う `Secret<T>`。
コアが定義するのは契約です。ストレージアダプター、時計、ID 生成器、非同期ランタイムは
提供せず、トランザクションや配信も自動的には保証しません。アダプターはアプリケーション側で
実装してください。非同期ポートは、ランタイムを選ばずにボックス化された `Send` Future を返します。
## 使用例
上記の二つの依存を追加した場合:
```rust
#[derive(ddd_toolkit::ValueObject)]
#[value_object(validate = validate_email, error = &'static str)]
struct Email(String);
fn validate_email(value: &str) -> Result<(), &'static str> {
if value.contains('@') { Ok(()) } else { Err("missing @") }
}
fn main() {
let email = Email::try_new("a@b.com".to_owned()).unwrap();
assert_eq!(email.as_inner(), "a@b.com");
assert_eq!(Email::try_new("invalid".to_owned()), Err("missing @"));
let key = ddd_toolkit::Secret::new("sekret".to_owned());
assert_eq!(format!("{key:?}"), "[REDACTED]");
assert_eq!(key.expose_secret(|value| value.len()), 6);
}
```
`ValueObject` は `Debug`、`Clone`、`PartialEq`、`Eq`、`Hash` を生成するため、
重複して derive しないでください。検証なしの構造体では `new`、検証付きでは
`try_new` を使います。名前付き・複数フィールドの構造体と、データを持つ列挙型にも対応します。
`Entity` は `#[entity(id)]` フィールドだけを使った等値比較とハッシュを生成します。
### 集約イベントの発行
```rust
use ddd_toolkit::{AggregateRoot as _, DomainEventPublisher, dispatch_events};
#[derive(Debug, Clone, PartialEq, ddd_toolkit::DomainEvent)]
struct OrderPlaced;
#[derive(ddd_toolkit::Entity, ddd_toolkit::AggregateRoot)]
struct Order {
#[entity(id)]
id: u64,
#[aggregate(events)]
events: Vec<OrderPlaced>,
}
struct Publisher;
impl DomainEventPublisher for Publisher {
type Event = OrderPlaced;
type Error = &'static str;
fn publish(&mut self, _event: &OrderPlaced) -> Result<(), Self::Error> {
Ok(())
}
}
fn main() {
let mut order = Order { id: 1, events: vec![] };
order.record_event(OrderPlaced);
dispatch_events(&mut order, &mut Publisher).unwrap();
assert!(order.pull_events().is_empty());
}
```
リポジトリによる永続化とイベント発行は別の操作です。`Repository::save` は集約を
不変で借用し、保留イベントを自動的には取り出しません。`dispatch_events` は成功時に
イベントを取り出してキューを空にし、発行失敗時には取り出した**すべての**イベントを復元します。
発行済みのイベントはロールバックしないため、再試行で重複配信される可能性があります。
トランザクションの保証はアプリケーション側のアダプターが定めます。
## Feature
| `derive` | 有効 | `ValueObject`、`Entity`、`DomainEvent`、`AggregateRoot` の derive。利用側にはコアの直接依存も必要 |
| `serde` | 無効 | コアの serde 再エクスポートと、`#[value_object(serde)]` に必要な serde derive 対応。`derive` とは独立 |
値オブジェクトをシリアライズするには、ファサードの `serde` feature を有効にし、
型ごとに次の属性を指定します。
```rust
#[derive(ddd_toolkit::ValueObject)]
#[value_object(serde)]
struct UserId(String);
```
生成された実装のために `serde` へ直接依存する必要はありません。
検証付きのデシリアライズでは検証関数を実行し、失敗を `value object validation failed` に
変換します。列挙型は外部タグ形式を使います。同じ属性で検証と併用するときは
`#[value_object(validate = check, error = Error, serde)]` のように `serde` を最後に置いてください。
マクロ 0.4.1 は、フラグの後ろに他のオプションを記述すると拒否します。
**マクロ 0.4.1 の制限:** `Code(u32)` のような単一フィールドのタプルバリアントは
`{"Code":[7]}` にシリアライズしますが、生成されたデシリアライザーは `{"Code":7}` を
期待するため、自身の JSON 出力を復元できません。JSON の往復変換が必要な場合は
`Code { value: u32 }` のような名前付きバリアントを使ってください。
Clippy 1.99 は、マクロ 0.4.1 が生成する名前付き値オブジェクトのコンストラクターに
`clippy::redundant_field_names` を報告します。`-D warnings` を使う場合、生成器が修正されるまでは
利用側モジュールでこの lint に限定した許可が必要になる場合があります。
`Secret<T>` は feature の指定なしで使え、自動的にはシリアライズされません。
`Secret::into_inner` を呼び出した後のゼロ化は呼び出し側の責任です。
## core/macro 0.3 からの移行
- `domain`、`port`、`adapter`、`mock`、`application` のモジュールパスをルートからの
インポートに置き換えてください。組み込みアダプター/テストダブルと `UseCase` は廃止されました。
- `EntityId` / `EnumVo` の derive を適切な `ValueObject` 型に置き換えてください。
`Wrapped` の代わりに `ValueObject` の `as_inner` / `into_inner` を使います。
- `#[vo(validate = "function")]` を
`#[value_object(validate = function, error = YourError)]` に置き換えてください。
`ValidationError` は廃止され、エラー型は利用側で選びます。
- 生成された `TryFrom` / `From` / `Display` の使用を、コンストラクターや必要に応じた
アプリケーション側の明示的な実装に置き換えてください。
- `SecretVo` / `#[vo(zeroize)]` を `Secret<T>` に置き換えてください。
- `record` / `take_events` を `record_event` / `pull_events` に置き換えてください。
- `Load` / `Save` / `Delete` を `Repository` または `AsyncRepository` に、
`EventDispatcher` をイベントパブリッシャーの契約に置き換えてください。
`PortError` は廃止され、エラー型はアダプターが選びます。
- `chrono`、`uuid`、`zeroize` の feature 指定を削除してください。
- derive を使う場合はコアの直接依存を追加し、serde は値オブジェクトごとに有効にしてください。
詳細は [API ドキュメント](https://docs.rs/ddd-toolkit)を参照してください。
[`facade-tests`](facade-tests/tests) では、独自リポジトリ、非同期ポート、複数集約の処理、
発行失敗と再試行、derive、serde、秘密値をファサード経由で検証しています。
## 変更履歴
[CHANGELOG.md](CHANGELOG.md)を参照してください。
## ライセンス
[Apache License, Version 2.0](LICENSE-APACHE) または [MIT license](LICENSE-MIT) の
いずれかを選択して利用できます。