ドメインモデルを設計している人向けのお手軽URL設計法を考えました(☆命名:PRD法)
概説
API 設計をする際に、階層とかを意識しても一意にリソースを表す URL を作れなかったり、毎回チームで将来の保守性などを議論するのが面倒に感じる方はいませんか?
当記事では、"ドメインモデルがある!"という場合に限りますが、一定のルールに従えば比較的スムーズに API を設計できる方法を論じます。
どのような方法か、最初におおざっぱに説明すると、
- ドメインモデルを設計する
- エンティティの絞り込みに必要な述語をすべて定義する
- 2.のエンティティと述語の組み合わせで URI のパス部分を記述する
- ページング等のメタデータはクエリ文字列に記述する
というやり方になります。
私はこれに、Predicative Resource Description Method(叙述的リソース記述法)という名前を付け、略してPRD 法とすることにしました。(もし、もっと良い名前を思いついた方はコメントください)
もしここまでの説明で「あ~そういうことか」と理解できた方は先に進む必要がありません。
具体例
次に、具体的にどのようなエンドポイントができるかを示します。
この時点で分かった方も先に進む必要がありません。
解説
この先は Todo アプリにおけるドメインモデルを例に挙げながら細かく解説していきます。
ドメインモデルを設計する
まず、一般的なドメインモデルの例を提示します。
(便宜的に TypeScript の文法を借ります)
type Todo = {
id: string;
title: string;
deadline: Date;
owner: User;
};
type User = {
id: string;
name: string;
email: Email;
};
ここでのエンティティは TODO アイテム(Todo
)とユーザー(User
)で、あとは全て値オブジェクトです。
TODO アイテムにはタイトル(title
)と期限(deadline
)があり、所有者としてユーザーが紐づいています。
ユーザーには名前(name
)とメールアドレス(email
)の情報があることが分かります。
これらは DDD 等のプロセスの中で要件を聞きながら作っていくものだと思いますので、すでにできあがっているものとして進めていきます。
エンティティの絞り込みに必要な述語をすべて定義する
次にどのようなリソースを取得したいかをエンティティ+述語の形式で考えていきます。
例えば、
- TODO アイテム(エンティティ)
- 所有者が自分である(述語 ①)
- 2022 年 8 月 4 日に期限が切れる(述語 ②)
これを日本語の文章にすると、
「所有者が自分であり、2022 年 8 月 4 日に期限が切れる TODO アイテム」
になります。
これは要件を洗練させていくプロセスの中で得られる情報かと思います。
なぜなら、ユースケースを SVO 形式(主語+動詞+目的語)で考えるときの目的語に当たるからです。
- ユーザーは(主語)→ 認証
- 取得したい(動詞)→ リクエストメソッド
- 所有者が自分であり、2022 年 8 月 4 日に期限が切れる TODO アイテム(目的語)→ URL のパス
逆に言えば、PRD 法を使いたいときは、ユースケースを SVO 形式で表したときに目的語に当たるものが最終的に明確化される必要があります。
述語の一般化
では次に述語を一般化していきます。
先ほど、下記2つの述語を定義しました。
- 所有者が自分である(述語 ①)
- 2022 年 8 月 4 日に期限が切れる(述語 ②)
これをプログラムっぽい表現=関数(引数)
に変えるとこうなります。
-
ownerIs(me)
(述語 ①) -
expiredOn(2022-08-04)
(述語 ②)
(2022-08-04
は ISO8601 の拡張形式(YYYY-MM-DD)です)
ここの変形は機械的にはできません。
一般化にはセンスが必要です。
ただし、センスはそのうち身に着くものなので、頑張ってください。
もちろん一般化しないのも手です。
ownerIsMe
のままで述語として扱うのもさほど問題ではありません。
しかし、述語 ② の場合
expiredOn2022-08-04
となってしまい気持ち悪いですし、実際は特定の日付以外も対象にするはずなので、可変パラメータとして引数にしておいた方がおさまりがいいのです。(こちらもっといい説明方法あったらコメントで教えてください)
あとは関数(引数)
の形式にしていたものを URL で表現するために関数(ケバブケース)/引数
の形式に変えれば述語が完成します。
-
owner-is/me
(述語 ①) -
expired-on/2022-08-04
(述語 ②)
もし関数に複数の引数を渡すような述語にしたい場合は、以下のように引数動詞を/
で区切れば OK です。
term-between/2022-08-04/2022-08-06
-
termBetween(2022-08-04,2022-08-06)
(関数形式の場合)
パス部分を記述する
あとは作った2つの述語とエンティティを組み合わせてパス部分にします。
todo/owner-is/me/expired-on/2022-08-04
となります。
エンティティと述語、どちらを先にするか
今回は、エンティティ/述語
の順に書きましたが、述語/エンティティ
だとどうでしょう。
ownerIs/me/expiredOn/2022-08-04/todo
となり実は問題はないです。
ただ、PRD 法のお作法としてはエンティティ/述語
にしておきたいと思います。
メタデータはクエリ文字列に記述する
最後に、ページング等のメタデータです。
こういったものは、どちらかといえば非機能要件の実現に欠かせないでしょう。
こちらは特にルールは無いです。
以下はカーソルページネーションの場合の例です。
?start=abcde&end=fghij
完成
ここまでに作った、
パス部分:todo/owner-is/me/expired-on/2022-08-04
クエリ文字列部分:?start=abcde&end=fghij
をつなげて、
https://example.com/todo/owner-is/me/expired-on/2022-08-04/?start=abcde&end=fghij
とすれば完成です。わーい。
メリットとデメリット
これは最初に書くべきだったのですが、PRD 法を用いるメリットとデメリットをいくつか挙げます。
メリット
まずメリットから説明させてください。
ルールがあること
API の設計はチーム内でもフロントエンドとバックエンドのつなぎ目として議論になることも多いと思います。
そんなときにある程度の明確なルールがあった方が設計がスムーズに進み、個人的な嗜好同士の衝突も避けられます。
リソース名を考えなくていい
これは言うまでもないですね。
ドメインモデルが決まっていれば、あらゆる用語は用意済みなので、そこに追加でmy-todos
のような特別なそれらしいリソース名を考えて割り当てる必要は無くなります。
いうなれば、エンティティと述語の合成によってリソースを完全に抽象化できるので、さらなるカプセル化は不要になるのです。
リソースへのアクセススコープが調節できる(=権限制御になる)
先ほど作った URLをエンドポイントとするようなAPIサーバーのルーティング表現を考えたときに、Express.jsの場合なら
/todo/owner-is/me/expired-on/:expiredOn
/todo/owner-is/:ownerId/expired-on/:expiredOn
の2つを候補として考えることができます。
1.であればme
部分は固定なので、自分のTODOアイテム以外はどうやっても取れません。
2.は:ownerId
の部分にユーザーIDを指定することで、任意のユーザーのTODOアイテムを取得できるような表現になります。
もちろん、2.の場合でもサーバーの内部処理として:ownerId
がリクエスト者のIDであることをチェックするような処理を含めることは可能ですが、それをしてしまうとPRD法としては違反です。
また、リソースに関する暗黙の絞り込みが内部的に存在することはAPI利用者にとっても混乱のもとになります。
述語関数は使いまわせる
owner-is
やexpired-on
などの述語関数はtodo
以外でも使いまわせるワードです。
これらを貯めておくと、他のリソースを設計する際にすでにある述語からピックアップできるので、設計コストが減る場合があります。
デメリット
次にデメリットです。
ドメインモデルを設計している人向け
ここまで書いた通り、PRD法はドメインモデルがあるとスムーズに進みます。
そうでない場合、色々と考えることが増えます。
PRD法を利用してHTTPリクエストの設計を行うのは、ある種のユースケース設計です。
なので、モデルよりユースケースを先に設計している人は、幾分遠回りな感覚を得るかもしれません。
もちろん、あくまでPRD法はお行儀よく順序よくやる必要もないので、URL考えながらモデリングも行うというのは当然アリです。
やりやすい方法で取り組んでもらうのが良いと思います。
URLが長くなる
リソース名を考えなくていいというメリットを提示しましたが、カプセル化をサボる分、当然ながらURLは長ったらしくなります。
ある種の形式(文法)には従っているものの、様式として受け付けない人もいらっしゃると思うので、そういう方には不向きだと思います。
終わりに
ここまで読んでいただいた方、ありがとうございます。
正直、かなりの部分においてずさんな説明になっていると思うので、分からないところがあればどしどしコメント頂きたいです。
また、ある程度の理論的バックエンドは想像の範囲内にはありますが、証明等は一切していないのでご了承ください。
文言の修正PR等は、以下のリポジトリにお願いします。
再度になりますが、最後まで読んでいただきありがとうございました。
Discussion