RESTful API データモデリング

RESTful APIデータモデリングにおけるDDD:集約と値オブジェクトのAPI表現

Tags: DDD, データモデリング, API設計, 集約, 値オブジェクト

はじめに

RESTful APIの設計において、どのようなデータ構造をリソースとして公開するかは非常に重要な判断です。システムの内部構造やビジネスロジックと密接に関わる部分であり、その設計の良し悪しがAPIの使いやすさ、保守性、そしてシステム全体の健全性に大きく影響します。特に、システムが複雑になるにつれて、データ構造の設計に迷うことは少なくありません。

ここでは、ドメイン駆動設計(Domain-Driven Design; DDD)の概念をRESTful APIのデータモデリングに応用する考え方について解説します。DDDは複雑なビジネスドメインを扱うための設計手法であり、その中で定義される「集約」や「値オブジェクト」といった概念は、APIリソースの適切な粒度や構造を検討する上で非常に参考になります。

ドメイン駆動設計(DDD)における主要な概念(APIモデリングの視点から)

まず、RESTful APIのデータモデリングに関連するDDDの主要な概念を簡単に整理します。

ドメインモデルからAPIリソースへのマッピング

DDDで設計されたドメインモデルをRESTful APIのリソースとして公開する際、以下のマッピングが基本的な考え方となります。

  1. 集約をAPIリソースの単位とする: 最も基本的なアプローチは、DDDの集約をAPIのリソース単位として扱うことです。集約ルートをリソースの識別子とし、集約全体をリソース表現の対象とします。これにより、APIを通じてデータの整合性を保ちやすくなります。

    • 例: 顧客集約 (Customer とその AddressContactInfo など) → customers/{customerId} というリソース。
    • 例: 注文集約 (Order とその OrderItemsShippingInfo など) → orders/{orderId} というリソース。

    この考え方によれば、APIクライアントは集約ルートのリソースを通じて、その集約内の全ての関連データにアクセスしたり、集約全体に対する操作(生成、更新、削除)を行ったりすることが一般的になります。

  2. 値オブジェクトのAPI表現: 値オブジェクトは識別子を持たないため、単独のリソースとして公開されることは稀です。通常は、それを所有するエンティティや集約の一部として、ネストしたオブジェクト構造で表現されます。

    • 例: Address という値オブジェクト(street, city, zipCode などの属性を持つ)は、Customer リソースや Order リソースのJSON表現内で以下のようにネストして表現されます。

    json { "customerId": "...", "name": "...", "shippingAddress": { "street": "123 Main St", "city": "Anytown", "zipCode": "12345" }, ... } 値オブジェクトは不変であるというDDDの性質をAPIにも反映させる場合、値オブジェクト全体を一度に更新する(新しい値オブジェクトで置き換える)設計が自然です。部分的な属性更新(PATCH)は、値オブジェクトの属性ごとではなく、包含するエンティティや集約の一部として扱われます。

  3. エンティティのAPI表現: 集約ルート以外のエンティティ(集約内部のエンティティ)は、単独で独立したリソースとするか、あるいは集約リソースのサブリソースとして表現するかを検討します。

    • 独立したリソースとして公開しない場合: 集約リソースのJSON表現の一部としてネストして表現されます。集約の整合性を保つ操作は、集約ルートを通じてのみ行われます。 例: 注文集約内の OrderItem は、orders/{orderId} リソースのGETレスポンス内で、items というリストとして表現される。

      json { "orderId": "...", "orderDate": "...", "items": [ { "itemId": "...", // OrderItemのIDだが、単独リソースではない "productCode": "...", "quantity": 2, "price": 1000 }, ... ], ... }

    • サブリソースとして公開する場合: 集約内の特定のエンティティに対して直接アクセスしたり、操作を行いたい場合にサブリソースとして公開することがあります。これは、そのエンティティが集約の外部から比較的独立して扱われる必要がある場合や、そのエンティティのリストが非常に大きい場合に検討されます。しかし、集約の整合性ルールを破らないように注意が必要です。 例: orders/{orderId}/items/{itemId} のように、特定の注文明細に直接アクセスする。ただし、注文明細の削除や数量変更などの操作が、注文集約全体のビジネスルール(例: 合計金額の再計算、在庫引き当ての解除など)と連携して行われる必要があります。

      サブリソースとするか否かの判断は、そのエンティティがどれだけ独立したライフサイクルを持つか、そしてそのエンティティに対する操作が集約全体の整合性にどの程度影響するかによって変わります。一般的には、集約内のエンティティは集約ルートを通じて操作される方が、データの整合性を保ちやすいため推奨されます。

具体的な設計パターンと考慮事項

1. 集約単位でのリソース設計

最もDDDの考え方を反映したシンプルなパターンです。

このパターンは、集約の境界がAPIの操作単位と一致するため、ドメインロジックとAPI実装の間のマッピングが明確になりやすいメリットがあります。

2. 集約内の部分的な変更(PATCH)

RESTful APIでは部分更新のためにPATCHメソッドがよく使われます。DDDの視点からPATCHを考える場合、以下の点に注意が必要です。

3. コマンドとしての操作

RESTful APIはリソース指向ですが、ビジネスロジックによっては、特定のリソースに対する「操作」や「アクション」を表現したい場合があります(例: 注文を確定する、商品をカートに追加する)。DDDではこれを「ドメインサービス」や「アプリケーションサービス」として設計することがあります。

このような操作をAPIで公開する際は、カスタムメソッド(例: POST /orders/{orderId}/confirm)や、操作の実行結果として生成されるリソース(例: カートへの商品追加リクエスト POST /cart-items → カート明細リソースの生成と応答)として表現することが考えられます。DDDで定義されたドメインサービスやアプリケーションサービスのメソッドを、APIエンドポイントの処理としてマッピングします。

4. 境界づけられたコンテキストとAPI

大規模なシステムでは、複数の境界づけられたコンテキストが存在し、それぞれが異なるドメインモデルを持つことがあります。このような場合、コンテキストごとに独立したAPI群を設計するか、あるいはAPIゲートウェイ等を用いて複数のコンテキストにまたがるAPIを構成することが考えられます。重要なのは、それぞれのAPIがどの境界づけられたコンテキストのモデルに基づいているかを明確にすることです。コンテキスト間の連携は、APIコールやイベント通知など、明示的な手段を通じて行われます。

DDDの概念を考慮しない場合のアンチパターン

DDDの概念、特に集約の境界を無視してAPIを設計すると、以下のような問題が発生しやすくなります。

まとめ

RESTful APIのデータモデリングにドメイン駆動設計(DDD)の考え方を取り入れることは、ビジネスロジックとの整合性が高く、保守性の高いAPI設計につながります。

DDDは決して銀の弾丸ではありませんが、複雑なドメインを持つシステムにおいて、データモデリングの指針を与えてくれる有効なアプローチです。ドメインエキスパートとの対話を通じてドメインモデルを深く理解し、それをAPIリソース設計に反映させることで、真にビジネスに貢献するAPIを構築することができるでしょう。