RESTful APIにおけるソフトデリート(論理削除)のデータモデリング:状態表現と操作設計
はじめに
多くのシステム開発において、データの「削除」は物理的な削除だけでなく、論理的な削除(ソフトデリート)として扱われることがあります。これは、誤削除からの復旧や、監査目的での履歴保持といった要件から採用されます。
RESTful APIを設計する際に、このソフトデリートされたデータをどのように表現し、クライアントからどのように操作できるようにするかは、データモデリングの重要な課題の一つです。不適切な設計は、APIの使いにくさ、保守性の低下、そしてデータの不整合を引き起こす可能性があります。
本記事では、RESTful APIにおけるソフトデリートデータの効果的なデータモデリングについて、その基本的な考え方から具体的な設計パターン、そして考慮すべき点までを解説します。
ソフトデリートが必要とされる背景とAPI設計上の課題
なぜ物理削除ではなくソフトデリートが必要となるのでしょうか。主な理由として以下の点が挙げられます。
- データ復旧の可能性: ユーザーの誤操作やシステム側の不具合でデータが削除された際に、簡単に元の状態に戻せるようにするためです。
- 履歴保持と監査: 特定のデータがいつ、誰によって削除されたかといった履歴を保持し、後から追跡できるようにするためです。
- 関連データの維持: 削除対象のデータが他の多くのデータと関連している場合、物理削除してしまうと参照整合性が崩れたり、関連データまで芋づる式に削除する必要が生じたりするため、リンクを維持したまま非表示にしたいケースです。
これらの要件はシステム内部のデータ管理の課題ですが、RESTful APIはシステム内部のデータを外部に公開するインターフェースです。したがって、ソフトデリートの仕組みをAPIを通してどのように表現し、クライアントがそれをどのように認識し、操作できるように設計するかが課題となります。
具体的には、以下のような疑問が生じます。
- リソースがソフトデリートされた状態を、APIレスポンスでどう表現すべきか?
- クライアントはソフトデリートされたリソースを取得できるべきか?できるとしたらどのように指定するのか?
- DELETEリクエストは物理削除を意味するのか、それともソフトデリートを意味するのか?
- ソフトデリートされたリソースを元に戻す(復元する)操作はどのように設計すべきか?
- 関連リソースを取得する際に、ソフトデリートされたリソースはどのように扱うべきか?
これらの課題に対して、明確で一貫性のあるデータモデリングとAPI設計が必要になります。
ソフトデリートの状態をデータモデルにどう表現するか
ソフトデリートの状態を表現するための一般的なデータモデリング方法としては、以下の二つがよく用いられます。
-
フラグによる表現:
is_deleted
(boolean) のような真偽値フラグを追加する方法です。true
であればソフトデリート済み、false
またはフィールドがない場合はアクティブな状態とします。
json { "id": "item-123", "name": "商品A", "price": 1000, "is_deleted": false }
json { "id": "item-456", "name": "削除された商品B", "price": 2000, "is_deleted": true }
- シンプルで分かりやすい表現ですが、いつ削除されたかの情報は保持できません。
-
タイムスタンプによる表現:
deleted_at
(timestamp) のようなタイムスタンプフィールドを追加する方法です。- フィールドが
null
であればアクティブな状態、タイムスタンプが設定されていればその日時にソフトデリートされた状態とします。
json { "id": "item-123", "name": "商品A", "price": 1000, "deleted_at": null }
json { "id": "item-456", "name": "削除された商品B", "price": 2000, "deleted_at": "2023-10-27T10:00:00Z" }
- ソフトデリートされた日時を記録できるため、履歴追跡や復旧時の情報として有用です。フィールドの存在/非存在で状態を判断するため、フラグよりも表現力が高いと言えます。
多くのケースでは、ソフトデリート日時も重要になるため、タイムスタンプによる表現 (deleted_at
) が推奨されます。APIレスポンスにはこのフィールドを含めることで、クライアントはリソースの状態を正確に把握できます。
API操作の設計:取得 (GET)
ソフトデリートされたリソースの取得をどう扱うかは、設計上の重要な分岐点です。
デフォルトの挙動
多くの場合、クライアントがリソースを一覧取得したり個別取得したりする際に、デフォルトではアクティブなリソースのみを返すのが最もユーザーフレンドリーです。これは、通常の業務でソフトデリートされたデータに関心がない場合がほとんどだからです。
例: GET /items
は deleted_at
が null
の商品のみを返す。
ソフトデリート済みデータを含める/除外する制御
特定のユースケース(例: 管理画面での論理削除済みアイテム一覧表示、監査目的など)では、ソフトデリート済みのリソースも取得したい場合があります。この要件に対応するためには、クエリパラメータを用いるのが一般的です。
例:
* GET /items?status=active
: アクティブな商品のみ(デフォルト)
* GET /items?status=deleted
: ソフトデリート済み商品のみ
* GET /items?status=all
: 全ての商品(アクティブとソフトデリート済み)
このように、status
や include_deleted
といったクエリパラメータで取得対象を制御できるように設計することで、様々なクライアントの要求に対応できます。パラメータを指定しない場合のデフォルト挙動を明確にしておくことが重要です。
個別のリソースを取得する場合(例: GET /items/{id}
)も同様に考える必要があります。存在しないIDの場合は404 Not Foundを返しますが、ソフトデリート済みIDの場合はどうするか?
選択肢としては:
1. デフォルトでは404を返す。クエリパラメータ(例: GET /items/{id}?include_deleted=true
)を指定した場合のみソフトデリート済みのリソースを返す。
2. ソフトデリート済みであっても、IDが存在すれば常にそのリソースの状態(deleted_at
の値)を含めて返す。
どちらの設計が良いかは、そのAPIの利用者やユースケースに依存しますが、通常は1.の「デフォルトでは見えない」方が、意図しない情報漏洩を防ぎ、多くのクライアントにとってはシンプルになる傾向があります。ただし、ソフトデリート済みかどうかを判定するために常に include_deleted=true
で取得させる必要があるため、クライアントの実装は少し複雑になるかもしれません。2.の場合は、クライアントは取得したリソースの deleted_at
フィールドを見て、それがアクティブかソフトデリート済みかを判断する必要があります。APIの利用者がソフトデリートの概念を理解している必要がある場合に適しています。一貫性の観点からは、一覧取得と同じパラメータ(例: status=all
)で制御できるようにするのが良いでしょう。
API操作の設計:削除 (DELETE)
RESTfulな設計において、DELETE /resources/{id}
は特定のリソースを削除する操作を意図します。ソフトデリートを採用しているシステムでは、このDELETEリクエストに対して、物理削除ではなくソフトデリートを実行するのが一般的です。
クライアントから見ると、DELETEリクエストを送信したリソースは「削除された」状態になります。この「削除」が内部的に物理削除かソフトデリートかを意識させないようにするのが、API設計における一つの考え方です。
DELETEリクエストが成功した場合、APIは 204 No Content
や 200 OK
を返します。レスポンスボディを含める場合は、ソフトデリート後のリソース表現(deleted_at
が設定された状態)を返すこともあります。
DELETEリクエストを受け付けてソフトデリートを実行する際の処理:
1. 指定されたIDのリソースが存在するか確認する。存在しない場合は 404 Not Found
を返す。
2. リソースがすでにソフトデリート済みでないか確認する。すでに削除済みのリソースに対して再度削除リクエストが来た場合の扱いは仕様によりますが、冪等性を考慮して 204 No Content
や 200 OK
を返すのが一般的です。
3. リソースの deleted_at
フィールドに現在のタイムスタンプを設定し、データベースを更新する。
4. 更新が成功したら 204 No Content
または 200 OK
を返す。
API操作の設計:復元(PUT/PATCH/カスタムアクション)
ソフトデリートされたリソースを元に戻す、すなわち「復元」する操作は、RESTful APIでどのように表現すべきでしょうか。
- PUT: リソース全体を置き換える操作ですが、状態の一部(
deleted_at
をnull
にする)を変更するだけなので、不適切です。 -
PATCH: リソースの部分更新に使用するため、
deleted_at
フィールドをnull
に更新する操作としてPATCHを使用するのは理にかなっています。``` PATCH /items/{id} Content-Type: application/json
{ "deleted_at": null } ```
この方法のメリットは、RESTの標準的なメソッドを使用できる点です。デメリットとしては、クライアントがソフトデリートの内部表現 (
deleted_at
フィールドの存在) を知っている必要がある点です。 -
カスタムアクション:
restore
のようなカスタムアクションを表すエンドポイントを設ける方法です。POST /items/{id}/restore
この方法のメリットは、クライアントがソフトデリートの内部表現を知る必要がなく、「このリソースを復元する」という意図がAPI設計から明確に伝わる点です。
POST
メソッドは特定の処理を実行するために使用されることがあり、このケースにも適用できます。
どちらの方法を選択するかは、APIの設計思想やクライアントにどこまで内部構造を公開するかによります。カスタムアクションの方が、操作の意図が明確になり、よりドメイン固有の操作として表現できるため、APIの分かりやすさという点では優れているかもしれません。リクエストボディは不要か、あるいは復元に関するメタデータ(例: 誰が復元したかなど)を含めることができます。
関連リソースの扱い
ソフトデリートされたリソースに関連付けられているリソースや、逆にソフトデリートされたリソースを参照しているリソースをどう扱うかも考慮が必要です。
例えば、「注文」リソースが複数の「注文アイテム」リソースを持つ場合を考えます。「注文」がソフトデリートされた際に、「注文アイテム」も同時に非表示にすべきでしょうか、それとも「注文アイテム」はアクティブなままにして、参照元の注文が削除済みであることを示すべきでしょうか?
これは業務要件に強く依存しますが、一般的には、親リソースがソフトデリートされたら、関連する子リソースも(物理的には存在していても)APIからは見えなくなるように制御するのが自然な挙動です。
例:
* GET /orders/{order_id}
: order_id
の注文がソフトデリートされている場合、デフォルトでは 404 Not Found
を返す。
* GET /orders/{order_id}/items
: 親である注文がソフトデリートされている場合、たとえ注文アイテム自体はアクティブでも、このエンドポイントは空のリストやエラーを返す。
関連リソースを含めて取得する際に、ソフトデリートされた関連リソースを除外するか含めるかの制御も必要になる場合があります。例えば、GET /orders?status=all&include_deleted_items=true
のように、パラメータで細かく制御できるように設計することも考えられますが、パラメータが増えすぎてAPIが複雑にならないよう注意が必要です。
アンチパターン
ソフトデリートのAPI設計におけるいくつかのアンチパターンを挙げます。
- DELETEリクエストで物理削除とソフトデリートが混在する: 同じDELETEエンドポイントなのに、リソースの種類や状態によって物理削除されたりソフトデリートされたりすると、クライアントは挙動を予測できません。DELETEは常にソフトデリートに統一するか、物理削除が必要なら別の手段(例: 管理者専用のエンドポイントや、ソフトデリート後に一定期間経過したら自動物理削除など)を検討すべきです。
- ソフトデリート状態がAPIレスポンスに含まれない: クライアントがリソースがアクティブかソフトデリート済みかを判断できず、意図しない操作や表示を引き起こす可能性があります。
deleted_at
のような状態を示すフィールドは必ず含めるべきです。 - ソフトデリート済みのデータがデフォルトで取得される: ほとんどのユースケースではアクティブなデータのみが必要なため、これがデフォルト挙動だとクライアントは毎回フィルタリングの考慮が必要になり、非効率です。デフォルトではアクティブなデータのみを返し、必要に応じてフィルタリングパラメータを提供するのが良い設計です。
- ソフトデリートのリソースを一意に特定できなくなる: ソフトデリートしてもリソースIDは変更せず、同じIDで後から復元できるように設計すべきです。IDを変更してしまうと、そのリソースを参照していた他のデータとの関連性が失われます。
まとめ
RESTful APIにおけるソフトデリートのデータモデリングは、単にデータベースにフラグやタイムスタンプを追加するだけでなく、APIリソースとしてその状態をどのように表現し、クライアントにどのような操作を許可するかを体系的に設計することです。
- ソフトデリートの状態は、
deleted_at
のようなタイムスタンプフィールドで表現するのが一般的です。 - GETリクエストでは、デフォルトでアクティブなリソースのみを返し、クエリパラメータでソフトデリート済みリソースの取得を制御できるように設計します。
- DELETEリクエストは、原則としてソフトデリートを実行するものとします。
- リソースの復元は、
PATCH
メソッドでdeleted_at
をnull
に更新する方法か、POST
メソッドを使ったカスタムアクションとして設計する方法があります。 - 関連リソースの取得時には、親リソースの状態(ソフトデリートされているか)を考慮して表示を制御する必要があります。
これらの点を考慮してデータモデリングとAPI設計を行うことで、ソフトデリートが必要な要件に対応しつつ、クライアントにとって使いやすく、かつ保守性の高いAPIを実現できるでしょう。
データモデリングは常にシステム全体の要件とAPIの利用シーンに合わせて最適な形を模索する必要があります。ここで述べた原則やパターンを参考に、皆様のAPI設計に活かしていただければ幸いです。