Node.js&Express.jsを使用してREST APIを開発しています。最近、REST APIのデザインのベストプラクティスを探し始めましたが、少しわかりにくいので、ご容赦ください。
例:
次のAPIコレクションがあるとします。
そして、これらのリソースを持っている:
/api/users
はすべてのユーザーを返します/api/users/:id
は特定のユーザーを返します/api/users
は単一のユーザーを作成します/api/users/:id
は特定のユーザーを更新します/api/tasks
は新しいタスクを作成しますが、loginInユーザーに割り当てます。/api/tasks/:id
は特定のタスクを更新します/api/tasks
はすべてのタスクを取得します/api/users/:id/tasks
は、特定のユーザーのすべてのタスクを取得します/api/users/:id/tasks/:id
は、特定のユーザーの特定のタスクを取得しますこれで、上記の各"Resource"は、実際にアクションを実行する"Controller"にリダイレクトされます。
例えば:
POST ('/api/users', myController)
let myController = (req,res) => {
Create new User
Save to Database
Call Private Send an Email to Admin function etc
}
質問:
filter
などのリソースに対していくつかのクエリを実行する必要があるとしましょう。
オプション1:
So /api/users/:id/tasks/filter
のような新しいリソースをコントローラ内に作成し、コントローラ内のデータベースでフィルタリングアクションを実行します
オプション2:
So /api/users/:id/tasks?filter=title::someValue&priority::someOtherValue
のようなクエリ文字列を使用します。これを行う私の知識によると[〜#〜] not [〜#〜]新しいリソースを作成しますが、上記のクエリ文字列で/api/users/:id/tasks
リソースを呼び出すだけです。
オプション2に従うと、/api/users/:id/tasks
アクションを管理するコントローラーが1つだけになるため、このリソースのクエリを処理するには、次のようにします。
GET ('/api/users/:id/tasks', myController)
let myController = (req,res) => {
if(req.filter)
{handle Query here and get data from database and return to user}
else if(some other query)
{handle query here}
else if(some other query)
{handle query here}
else {
if there are NO queries then get get all Tasks for a specific User.
}
}
上記の実装/概念は正しいですか?私にはそれは厄介で混乱しているように見えます。それで、私はここで何が間違っているのですか?クエリを処理する正しい方法は何ですか?リソースに対して単一のコントローラが必要ですか?その特定のリソースのすべてを処理しますか?
「REST」で考えるのに苦労しているので情報が多すぎて何が間違っているのかわかりません。ですから、私の基本は少し弱いので、それに関するガイダンスは非常に役立ちます(記事やオンラインリソース)。
質問の対象はRESTですが、RESTとは何の関係もありません。
要するに、リクエストパラメータを特定のクエリ言語にマップする方法は、実装の詳細です。したがって、今のところ、RESTはそのままにしておきます。
上記の実装/コンセプトは正しいですか?
正しいかどうかを言うには、プロジェクトとその要件に精通している必要があります。これまでのところ、私たちが言えることは次のとおりです:解は柔軟ではありません。
長期的にはスケーリングの意味で柔軟です。新しいfilter
はすべて、(少なくとも)controller
、サービスレイヤー、そしておそらくデータアクセスレイヤーも変更する必要があります。管理の観点からは、機能が少ないために多くの作業を購入するのは困難です。言い換えれば、メンテナンスは高価に見えます。
それでも、クエリに新しいパラメーターを追加する頻度によって異なります。
だから私はここで何が間違っているのですか?
2つの可能な抽象化がありません。
注:一部のフレームワークでは#1がオプションになります。
現在、マッピングはif/else
ブロックとしてハードコードされています。うまくスケーリングするには硬すぎます。
代わりに、次のようになります。
myController(req,res){
var query = new QueryParametersMapper(req);
var page = new PageParametersMapper(req);
var sort = new SortParametersMapper(req);
var dataSet = myRepository.find(query, page, sort);
// etc...
}
クエリを処理する正しい方法は何ですか?
マッピングをより動的で再利用可能にする。
この時点で、私の最初のアドバイスは、フレームワークまたはプログラミング言語と互換性のあるライブラリを探すです。
2番目のアドバイスは、Convention over configurationをサポートするものを選択することです。
変更が妨げられない場合でも、少なくとも(確かに)変更のコストは現在よりも少なくなります。*
*質問の例として挙げられているコードを検討した場合。
RESTful API開発者としての私の意見を以下に示します。これらの一部は、RESTとは関係ありませんが、HTTPの適切なプラクティスです。
?filter=title::someValue&priority::someOtherValue
の代わりに?title=someValue&priority=someOtherValue
をお勧めします?filter=title::someValue&priority::someOtherValue
を使用するには、とにかくアンパサンドをURLエンコードする必要があります。/api/
を含めないことを強くお勧めします。人間を対象としたAPIエンドポイントと、URLに依存しない表現以外のマシンを対象としたAPIエンドポイントの間に違いはありません。 URLを共有すると、同じリンクを人間またはマシンがたどることができ、それぞれが適切な表現を取得できます。 /users/:id
がHTMLページを返し、/api/user/:id
がJSONを返す場合、これらのURLは両方とも同じことを表しているため(技術的には間違いではありません。複数のURLが完全に同じリソースを特定します—しかし、それはより良いかもしれません)。/api/users/:id/tasks/:id
を使用せず、その下に/tasks
コレクションと/tasks/:id
があり、/users/:id/tasks
から直接後者にリンクしています。/tasksとその下にあるすべてのものは、/ usersと同じようにアクセス制御されます。あなたも言う:
これを行う私の知識によれば、新しいリソースは作成されません
それは絶対にあります!すべての固有のURLは異なるリソースです。別のURLと同じコンテンツを返すか、別のURLにリダイレクトするか、他のURLに<link rel=canonical>
を持っているか、別のURLのサブセットであるかは関係ありません。 URLは「リソース」の抽象的な概念を識別します。その一部はバイトストリームとして表現でき、一部は表現できません。