ドメインマクロ
derive や proc-macro は繰り返しパターンの符号化に使う道具であり、欠けているドメインモデリングを隠すためのものではない。同型が 3 つ以上あり、手書きが drift しうるときだけ導入を検討する。
不変条件の本体は ドメインモデリング、境界での検証は 境界防御、newtype 生成は クレートガイド(nutype) と整合させる。
型を先に、マクロは次
Section titled “型を先に、マクロは次”具体的な判断基準は次のとおりである。
マクロ向き:
- 共有検証メッセージ付き newtype
TryFrom/FromStr Clone、Debug、安定name()/version()が必要なドメイン event struct- 単一検証 string/integer を包む ID newtype
マクロ向きでない:
- 一度きりのビジネスルール
- 型ごとに異なる検証
- 生成コード内の
unwrapや panic の隠蔽
内部 proc-macro の前に既存 crate を使う
Section titled “内部 proc-macro の前に既存 crate を使う”| ニーズ | 推奨 | 備考 |
|---|---|---|
| 検証付き newtype | nutype、garde、validator | 不変条件をソースで可視に保つ |
| 単純 derive | derive_more、標準 derive | 透明 newtype、display ヘルパー |
| 繰り返し event メタデータ | 内部 #[derive(DomainEvent)] | event が同一形状を共有するときのみ |
チームがパターンを所有し、外部 crate が契約を表現できないとき、内部 proc-macro crate(例: my_app_domain_macros)を導入する。
推奨内部マクロパターン
Section titled “推奨内部マクロパターン”#[derive(NewtypeDomainId)]
Section titled “#[derive(NewtypeDomainId)]”不透明 ID newtype 向けに検証コンストラクタと変換を生成:
#[derive(NewtypeDomainId)]#[newtype(validate = "trim_non_empty")]pub struct RequestId(String);
// Expands to: new/try_new, as_str, TryFrom<String>, Display, Eq, Hash検証関数はマクロ crate 内で小さく単体テストする。プロジェクトが明示的に leaf での serde+検証を受け入れない限り、ドメイン ID に Deserialize を生成しない(境界防御 参照)。
#[derive(DomainEvent)]
Section titled “#[derive(DomainEvent)]”outbox と projection パイプラインで使う event レコードを標準化:
#[derive(DomainEvent)]#[event(name = "taxi.driver_assigned", version = 1)]pub struct DriverAssigned { pub request_id: RequestId, pub driver_id: DriverId, pub occurred_at: OccurredAt,}生成コードは次を提供すべき:
- redaction 向けフィールド可視性付き
Clone、Debug fn name(&self) -> &'static strとfn version(&self) -> u32- projection handler 向け optional
TryFrom<StoredEventEnvelope>
スキーマ進化ストーリーが文書化されていない限り、event payload に無制限 Serialize/Deserialize を derive しない(サービス境界 参照)。
繰り返し match アーム向け宣言マクロ
Section titled “繰り返し match アーム向け宣言マクロ”proc-macro が重いとき、macro_rules! ヘルパーで projection や error マッピングの重複を減らす:
macro_rules! domain_event_match { ($event:expr, { $($name:literal => $handler:expr,)* _ => $fallback:expr, }) => {{ match $event.name() { $($name => $handler,)* other => $fallback(other), } }};}宣言マクロは event を所有する crate 内に留める。サービス境界越しに macro DSL を export しない。
生成コードのレビュー期待
Section titled “生成コードのレビュー期待”- 生成 impl は
Default、public フィールド、不変条件を迂回する黙示 coercion を追加しない。 - event と ID の
Debugはログ安全のまま(ロギングとメトリクス 参照)。 - 型の rustdoc に展開を記載: どの trait が derive され、構築時にどの検証が走るか。
マクロを使わない場合
Section titled “マクロを使わない場合”- フィールド横断検証(amount + currency、日付範囲ルール)
- state machine 遷移 — state struct 上の明示メソッドとして保つ
- インフラマッピング(
FromRow、gRPC メッセージ)— 境界ルールをレビューで読めるよう明示 DTOTryFromを使う
レビューでは、不変条件を隠すマクロ、ログ非安全な生成 Debug / Display、少数型向けの過剰な内部 proc-macro、バージョン欠如の永続イベント、ドメイン型へのマクロ生成 serde / ORM derive を指摘する。
レビュー観点
Section titled “レビュー観点”マクロはドメイン不変条件を隠していないか — High
Section titled “マクロはドメイン不変条件を隠していないか — High”public フィールド、Default、黙示的な強制、手書きドメインルールと異なる検証を追加する proc-macro や derive を指摘する。
生成された Debug / Display はログに安全か — High
Section titled “生成された Debug / Display はログに安全か — High”ロギングとメトリクス も照合する。PII やシークレットを露出しうる ID、イベント、ペイロードへの生成 Debug / Display を指摘する。
イベントマクロはバージョンメタデータを保持しているか — Medium
Section titled “イベントマクロはバージョンメタデータを保持しているか — Medium”デプロイをまたいで永続化、キューイング、消費されるドメインイベントに、安定した name / version(または同等)がない場合は指摘する。
マクロ生成ドメイン型では Deserialize / FromRow derive を避けているか — Medium
Section titled “マクロ生成ドメイン型では Deserialize / FromRow derive を避けているか — Medium”境界防御 も照合する。プロジェクトが明示的なリーフ検証慣習を文書化していない限り、不変条件を持つドメイン型へのマクロ生成 serde や ORM derive を指摘する。
マクロは繰り返しで正当化されているか — Low
Section titled “マクロは繰り返しで正当化されているか — Low”1〜2 型のために nutype、TryFrom、明示 impl の方がレビューで明確なのに、新しい内部 proc-macro クレートを導入する箇所を指摘する。