私は唯一の開発者であり、最近、Swaggerとフロントエンドを備えたAPIの形式で新しいWebアプリケーションを作成しました。これは、顧客が自分でAPIを使用でき、通常はスクリプトでした。
それは完成に近づいており、開発者ではない私の上司は、APIがコンシューマーには難しすぎるように見え、プッシュバックがあるように感じていると述べました。たとえば、/ api/customersを押すと、IDの形式で関連データが返される場合があります:{ name: "Bob", FavoriteProducts: [ 1, 2, 5] }
。
これはフロントエンドに最適です。関連データをオンデマンドでロードするページはたくさんあります。たとえば、顧客の概要を表示してFavoriteProductsを使用しない場合や、顧客の巨大なテーブルを表示して、すべての関連データのロードに永久に時間がかかる場合があります。
彼は、顧客は関連データを個別に呼び出す必要がないことを望んでおり、挿入時に製品名を提供し、それらをIDにマップしてバックエンドで魔法のように永続化させるほうが簡単だと言います。 { name: "Bob", FavoriteProducts: [ "Plastic Spiders", "Candy", "Pumpkins"] }
。これは十分に無害に聞こえますが、これはより多くのデータとより多くのエンドポイント、ルックアップテーブル、コードテーブルなどの大幅な単純化です。
まだ最初のコンシューマーがいないため、これが実際に問題になるかどうかはわかりませんが、スクリプトの経験から、API呼び出しをできるだけ少なくしたいと考えています。
私は完全に同意しませんが、特に競争の激しい世界であるため、お客様の生活をより快適にするために努力する必要があると思います。しかし、私はまた、優れたパフォーマンスと十分に文書化されたRESTful APIを作成するために一生懸命働きました。誰かが2つのAPI呼び出しを行いたくないので、バックエンドコードと技術的負債を追加することに関しては、バランスを取る必要があると思います。
GraphQLでこれが簡単になったと思いますが、この上司は、スクリプトの顧客がこの新しい言語を学ぶのは難しいと言っていたと思います。彼は私が学ぶために時間を費やしたくないので、拒否されたAngularこの正確な理由により、このプロジェクトでは。これは問題になるとは思いませんでした。すべてのデータを単一のテーブルに格納する大規模なレガシーアプリを書き直しました。適切なルックアップテーブルとデータ検証を備えたRESTful APIなので、消費が少し複雑になると思い、それで問題ないと考えました。
これまでにRESTful APIを使用したことがあれば、必要なすべてのデータを取得するために複数のAPI呼び出しを行わなければならない場所を少なくとも1つ見つけました。それはちょっと領土に付属しています。詳細なAPIの利点は、必要なものを正確に取得でき、不要なものは何も取得できないことです。コストは複数回の呼び出しであり、深いオブジェクトグラフを作成する必要がある場合は悪化する可能性があります。
裏側はRETURN EVERYTHINGです。お気に入りの製品、色、近日中の販売、マダガスカルへの発送の詳細など。読むのはとてもフレンドリーですが、顧客の名前と住所だけが必要な場合、ふるいにかけるのは面倒な場合があります。また、特に大量のデータを返す場合にも、パフォーマンスの問題が発生する可能性があります。
それで、あなたは何をしますか?
私の最初の推奨事項は、あなたが持っているような非常に単純なAPIに固執することです。ほとんどの開発者は、これによって油断することはありません。その上、顧客が欲しいものを言う顧客がいない場合、どうすれば彼らが欲しいものを知ることができますか?推測は可能ですが、しばらくソフトウェアを開発してきた人なら誰でも言うことができるので、顧客が何を望んでいるかは、たまにしかうまくいかないと推測できます。ほとんどの場合、あなたはそれを誤解します(または、少なくとも完全に正しくはありません)。
上司がしつこい場合は、APIユーザーが取得するデータを要求できるようにすることで、一種の妥協案を提供できます。次のエンドポイントと応答があるとします。
/customers/1 -> { name: "bob", favoriteProducts: [ 1, 3 ] }
次に、完全な製品情報が必要なことを示すためにパラメータを追加するとします。
/customers/1?favoriteProducts=full -> { name: "bob", favoriteProducts: [ { id: 1, name: "plastic spiders", ... }, { id: 3, name: "pumpkins", ... }] }
意味のある数の構成オプションを追加できます。それらを完全に文書化するようにしてください。そうすれば、驚きはありません。
APIを顧客に合わせて調整するのではありません。上司は、顧客が望むかもしれない彼のワイルドな推測にAPIを調整することを望んでいます。
APIの顧客として(ソフトウェア開発者なので、私はあなたの顧客であり、上司が売りたいと思っている人々ではありません):最悪のことは、賢いことを試みるAPIです。賢いAPIを作成しないでください。一意のIDは、APIにアクセスするための優れた方法です。
開発者からAPIが気に入らないと顧客に言われた場合は、上司が顧客に支払うことを確認した後で、物事を実装できます。成功するビジネスの鉄則を覚えておいてください。顧客が支払う場合、顧客は常に正しいです。
ここでボスに反対することで得るものはあまりないと思います。彼はビジネス上の決定を行い、その仕事は顧客のニーズを予測することです。
上司が提案するパターンに従うと、標準のフロントエンドを作成、維持、および/またはパフォーマンスを低下させることが難しくなる場合は、すべてのAPIエンドポイントの2つのバージョンを作成することを検討してください。フロントエンドの要件に合わせて調整された1つの「マシンフレンドリー」バージョンと、顧客のスクリプト作成者のニーズに合わせて調整された1つの「ユーザーフレンドリー」バージョン。
現在のAPIに加えてこの別個のAPIを作成することは、すでに機能しているクライアント側で何も変更する必要がないため、今のところこれ以上の作業はありません。ただし、長期的にはメンテナンスとドキュメントのオーバーヘッドが増えることを覚えておいてください。それらの間で可能な限り多くのコードを共有することにより、そのオーバーヘッドを削減できる可能性があります。もう1つを呼び出すことで、1つのエンドポイントを実装できます。
たとえば、/ api/customersを押すと、ID:{name: "Bob"、FavoriteProducts:[1、2、5]}の形式で関連データが返される場合があります。
これはフロントエンドに最適です。関連データをオンデマンドでロードするページはたくさんあります。たとえば、顧客の概要を表示してFavoriteProductsを使用しない場合や、顧客の巨大なテーブルを表示して、すべての関連データのロードに永久に時間がかかる場合があります。
上司のユースケースはここにあり、パフォーマンスに関する懸念も有効です。それらが競合しているとは思いません。両方に対処することで、より良いAPIが作成されます。
APIの場合、GET /api/customers/
を実行した場合、{ name: "Bob", FavoriteProducts: [ "Plastic Spiders", "Candy", "Pumpkins"] }
のような応答を表示したいということを言わなければなりません。
パフォーマンスの面では、コメントからいくつかのベンチマークを行ったと思いますが、プロファイルを変える必要があると思います。その理由は、パフォーマンスの問題が修正されるためです。APIのフォームを形作るべきではありません(当然のことながら)。
これを確認する1つの方法は、サンプルの応答をファイルに入れ、サーバーにこれらの静的ファイルを提供させることで、2つのAPIスタイルの違いを確認できます。つまり:
GET /api/customers <-- returns fav product IDs
GET /api/favorite_products/1
GET /api/favorite_products/2
GET /api/favorite_products/5
vs.
GET /api/customers <-- this one returns related fav products
私はそれを自分で試したわけではありませんが、往復が少ないため、2番目の方が速くなると確信しています。これはほとんどの顧客が望むことです。 fav製品が必要ない場合でも、気にしないでください。それでも、1回の呼び出しだけです。
もちろん、追加のルックアップをすべて実装して実装すると、パフォーマンスが低下し、対処する必要があります。余分な負荷のほとんどを取り除くことは不可能ではありません。メモリ内のKey-Valueストア、キャッシングなどを使用して、データを別の方法で保存する.
この問題について考えるのを助けるために、私はこの考えをエンティティーの関係の観点から表現したいと思います。これは、簡単であればデータベーステーブルと考えることができます。
この例に基づいて...
{ name: "Bob", FavoriteProducts: [ 1, 2, 5] }.
...私は次のエンティティを推測しています:
Customer
、CustomerIDの主キーProduct
、ProductIDの主キーCustomerProduct
、CustomerID + ProductIDの複合キー。個別に使用すると、それぞれのテーブルへの外部キーとしても機能します。2つのAPI呼び出しが必要です。
Customer
から単一の行が返され、CustomerProduct
から複数の行が返されますProduct
から単一の行が返されますCustomer
、CustomerProduct
、およびProduct
からデータを返す1つの呼び出し。これは、2つの呼び出しと同じですが、エンティティの分割方法が異なります。
Customer
を取得するための1回の呼び出しCustomerProduct
リスト全体とProduct
から関連するレコードを取得するための1回の呼び出しこれは、作業を分割するためのより自然な方法であり、クライアントが使用するのがはるかに簡単です。製品を必要とするかどうかにかかわらず、IDだけでは役に立たないのです。
これにより、Webサーバーが処理する必要がある呼び出しの数が減り、データベースの負荷が軽減されます。これは、データベースが単一のインデックスシークに続いて単一のインデックススキャンを使用してリスト全体を取得できるためです。クライアントが各製品を1つずつ要求した場合、これは不可能です。
最後に、この設計は、発生する可能性があるいくつかの厄介な並行性/一貫性のバグを取り除きます。たとえば、顧客の製品のリストを取得している最中に、リストの何かが変更された場合、または製品の1つが廃止された場合はどうなりますか?まれではありますが、この種の問題は再現が難しいため、気が狂います。リストと関連レコードをすべて一度に取得することで、その問題は完全に回避されます。