paint-brush
RESTを休ませる時が来たら@johnjvester
2,255 測定値
2,255 測定値

RESTを休ませる時が来たら

John Vester13m2024/05/13
Read on Terminal Reader

長すぎる; 読むには

この記事では、データ取得のための GraphQL と REST API を比較し、複雑なシナリオで GraphQL の柔軟性と効率性がいかに優れているかを紹介します。Apollo Server を Heroku にデプロイする方法を詳しく説明し、GraphQL の認証とリアルタイム データに関する今後のコンテンツを紹介します。
featured image - RESTを休ませる時が来たら
John Vester HackerNoon profile picture
0-item
1-item


長年サービスを構築してきた中で、 RESTful API が私の主な頼みの綱でした。しかし、REST にはメリットがあるとはいえ、それがすべてのユースケースに最適なアプローチであるとは限りません。長年かけて、特定のシナリオでは、よりよい代替手段があるかもしれないということを学びました。REST に情熱を持っているという理由だけで、それが適切でないのに REST に固執すると、技術的負債と製品所有者との緊張関係につながるだけです。


RESTful アプローチの最大の問題点の 1 つは、ビジネス上の意思決定に必要なすべての情報を取得するために複数のリクエストを実行する必要があることです。


たとえば、顧客の 360 度ビューが必要だとします。次のようなリクエストを行う必要があります。


  • GET /customers/{some_token}基本的な顧客情報を提供します
  • GET /addresses/{some_token}必要なアドレスを提供します
  • GET /contacts/{some_token}連絡先情報を返します
  • GET /credit/{some_token}重要な財務情報を返します


RESTの根本的な目的は、各リソースに対する応答を的確に保つことだと理解していますが、このシナリオでは消費者側の作業が増えます。組織が顧客との将来のビジネスに関する決定を下すのに役立つユーザーインターフェイスを設定するために、消費者は複数の呼び出しを行う必要があります。


この記事では、 GraphQL がRESTful API よりも推奨されるアプローチである理由を示し、Apollo Server (および Apollo Explorer) をデプロイして GraphQL をすぐに起動して実行する方法を紹介します。


Node.js を使用してソリューションを構築し、Heroku にデプロイする予定です。

REST よりも GraphQL を使用するタイミング

GraphQL が REST よりも優れたアプローチとなる一般的なユースケースがいくつかあります。


  • データの取得方法に柔軟性が必要な場合: さまざまなリソースから複雑なデータをすべて1 回のリクエストで取得できます。(この記事では、この点について詳しく説明します。)
  • フロントエンド チームが UI を頻繁に進化させる必要がある場合:データ要件が急速に変化しても、バックエンドでエンドポイントを調整する必要がなくなり、ブロックが発生することもありません。
  • 過剰フェッチ不足フェッチを最小限に抑えたい場合: REST では、必要なデータをすべて収集するために複数のエンドポイントにアクセスする必要が生じる場合 (不足フェッチ) や、単一のエンドポイントにアクセスすると実際に必要な量よりもはるかに多くのデータが返される場合があります (過剰フェッチ)。
  • 複雑なシステムやマイクロサービスで作業している場合、複数のソースから単一の API レイヤーにデータを渡すだけで済むことがあります。GraphQL は、単一の API 呼び出しを通じてその柔軟性を実現できます。
  • リアルタイムのデータをプッシュする必要がある場合: GraphQL には、リアルタイムの更新を提供するサブスクリプション機能があります。これは、チャット アプリやライブ データ フィードの場合に便利です。(この利点については、後続の記事で詳しく説明します。)

Apollo Server とは何ですか?

私のGraphQLのスキルがまだ磨かれていないので、この記事ではApollo Serverを使用することにしました。


Apollo Server は、あらゆる GraphQL スキーマで動作する GraphQL サーバーです。目標は、GraphQL API の構築プロセスを簡素化することです。基盤となる設計は、Express や Koa などのフレームワークとうまく統合されます。次の記事では、サブスクリプション ( graphql-wsライブラリ経由) を活用してリアルタイム データを取得する機能について説明します。


Apollo Server が本当に優れているのは、開発者が GraphQL API を探索およびテストするために使用できる組み込みの Web インターフェイスである Apollo Explorer です。クエリを簡単に構築でき、API スキーマをグラフィカル形式で表示できるため、このスタジオは私にぴったりです。

私の顧客 360 のユースケース

この例では、顧客の 360 度ビューを提供するために次のスキーマが必要であると仮定します。


 type Customer { token: String name: String sic_code: String } type Address { token: String customer_token: String address_line1: String address_line2: String city: String state: String postal_code: String } type Contact { token: String customer_token: String first_name: String last_name: String email: String phone: String } type Credit { token: String customer_token: String credit_limit: Float balance: Float credit_score: Int }


次の GraphQL クエリに重点を置く予定です。


 type Query { addresses: [Address] address(customer_token: String): Address contacts: [Contact] contact(customer_token: String): Contact customers: [Customer] customer(token: String): Customer credits: [Credit] credit(customer_token: String): Credit }


消費者は、表示したい顧客のトークンを提供します。適切な Address、Contact、および Credit オブジェクトも取得する必要があります。目標は、1 回の API 呼び出しではなく、このすべての情報に対して 4 つの異なる API 呼び出しを実行することを避けることです。

Apollo Server を使い始める

まず、ローカル ワークステーションにgraphql-server-customerという新しいフォルダーを作成しました。次に、Apollo Server ドキュメントの「Get Started」セクションを使用して、Typescript アプローチで手順 1 と 2 を実行しました。


次に、スキーマを定義し、テスト用の静的データもいくつか含めました。通常はデータベースに接続しますが、このデモでは静的データでも問題なく動作します。


以下は更新されたindex.tsファイルです。


 import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; const typeDefs = `#graphql type Customer { token: String name: String sic_code: String } type Address { token: String customer_token: String address_line1: String address_line2: String city: String state: String postal_code: String } type Contact { token: String customer_token: String first_name: String last_name: String email: String phone: String } type Credit { token: String customer_token: String credit_limit: Float balance: Float credit_score: Int } type Query { addresses: [Address] address(customer_token: String): Address contacts: [Contact] contact(customer_token: String): Contact customers: [Customer] customer(token: String): Customer credits: [Credit] credit(customer_token: String): Credit } `; const resolvers = { Query: { addresses: () => addresses, address: (parent, args, context) => { const customer_token = args.customer_token; return addresses.find(address => address.customer_token === customer_token); }, contacts: () => contacts, contact: (parent, args, context) => { const customer_token = args.customer_token; return contacts.find(contact => contact.customer_token === customer_token); }, customers: () => customers, customer: (parent, args, context) => { const token = args.token; return customers.find(customer => customer.token === token); }, credits: () => credits, credit: (parent, args, context) => { const customer_token = args.customer_token; return credits.find(credit => credit.customer_token === customer_token); } }, }; const server = new ApolloServer({ typeDefs, resolvers, }); const { url } = await startStandaloneServer(server, { listen: { port: 4000 }, }); console.log(`Apollo Server ready at: ${url}`); const customers = [ { token: 'customer-token-1', name: 'Acme Inc.', sic_code: '1234' }, { token: 'customer-token-2', name: 'Widget Co.', sic_code: '5678' } ]; const addresses = [ { token: 'address-token-1', customer_token: 'customer-token-1', address_line1: '123 Main St.', address_line2: '', city: 'Anytown', state: 'CA', postal_code: '12345' }, { token: 'address-token-22', customer_token: 'customer-token-2', address_line1: '456 Elm St.', address_line2: '', city: 'Othertown', state: 'NY', postal_code: '67890' } ]; const contacts = [ { token: 'contact-token-1', customer_token: 'customer-token-1', first_name: 'John', last_name: 'Doe', email: '[email protected]', phone: '123-456-7890' } ]; const credits = [ { token: 'credit-token-1', customer_token: 'customer-token-1', credit_limit: 10000.00, balance: 2500.00, credit_score: 750 } ];


すべてが期待どおりに設定されたら、次のコマンドを実行してサーバーを起動します。


 $ npm start


Apollo サーバーがポート 4000 で実行されている状態で、 http://localhost:4000/ URL を使用して Apollo Explorer にアクセスしました。次に、次のサンプル クエリを設定しました。


 query ExampleQuery { addresses { token } contacts { token } customers { token } }


アポロ エクスプローラーではこのように見えます:


[サンプルクエリ]ボタンを押して、応答ペイロードがindex.tsで指定した静的データと一致しているかどうかを検証しました。


 { "data": { "addresses": [ { "token": "address-token-1" }, { "token": "address-token-22" } ], "contacts": [ { "token": "contact-token-1" } ], "customers": [ { "token": "customer-token-1" }, { "token": "customer-token-2" } ] } }


Customer 360 のユースケースをさらに進める前に、このサービスをクラウドで実行したいと考えました。

Apollo Server を Heroku にデプロイする

この記事は何か新しいことをするということに関するものなので、Apollo サーバーを Heroku にデプロイするのがどれほど難しいかを見てみたいと思いました。


ローカルで実行する場合とクラウド内のどこかで実行する場合のポート番号の違いに対処する必要があることはわかっていました。サーバーを起動するためのコードを以下のように更新しました。


 const { url } = await startStandaloneServer(server, { listen: { port: Number.parseInt(process.env.PORT) || 4000 }, });


このアップデートでは、環境変数に PORT 値が指定されていない限り、ポート 4000 が使用されます。


Gitlab を使用して、これらのファイル用の新しいプロジェクトを作成し、Heroku コマンドライン インターフェース (CLI) を使用して Heroku アカウントにログインしました。


 $ heroku login


Heroku では、CLI または Heroku ダッシュボード Web UI を使用して新しいアプリを作成できます。この記事では、CLI を使用します。


 $ heroku create jvc-graphql-server-customer


CLI コマンドは次の応答を返しました。


 Creating ⬢ jvc-graphql-server-customer... done https://jvc-graphql-server-customer-b62b17a2c949.herokuapp.com/ | https://git.heroku.com/jvc-graphql-server-customer.git


このコマンドは、Heroku がリモートとして使用するリポジトリも自動的に追加しました。


 $ git remote heroku origin


デフォルトでは、Apollo Server は実稼働環境で Apollo Explorer を無効にします。デモでは、Heroku で実行したままにしておきます。これを行うには、 NODE_ENV環境変数を開発に設定する必要があります。これは、次の CLI コマンドで設定できます。


 $ heroku config:set NODE_ENV=development


CLI コマンドは次の応答を返しました。


 Setting NODE_ENV and restarting ⬢ jvc-graphql-server-customer... done, v3 NODE_ENV: development


これで、コードを Heroku にデプロイできるようになりました。


 $ git commit --allow-empty -m 'Deploy to Heroku' $ git push heroku


Heroku ダッシュボードを簡単に見ると、Apollo Server が問題なく動作していることがわかります。


Heroku を初めて使用する場合は、このガイドで新しいアカウントを作成し、Heroku CLI をインストールする方法を説明します。

受け入れ基準を満たしました: My Customer 360 の例

GraphQL を使用すると、次のクエリで Customer 360 ユースケースの受け入れ基準を満たすことができます。


 query CustomerData($token: String) { customer(token: $token) { name sic_code token }, address(customer_token: $token) { token customer_token address_line1 address_line2 city state postal_code }, contact(customer_token: $token) { token, customer_token, first_name, last_name, email, phone }, credit(customer_token: $token) { token, customer_token, credit_limit, balance, credit_score } }


必要なのはcustomer-token-1という値を持つ単一の Customer token変数を渡すだけです。


 { "token": "customer-token-1" }


1 回の GraphQL API 呼び出しを使用してすべてのデータを取得できます。


 { "data": { "customer": { "name": "Acme Inc.", "sic_code": "1234", "token": "customer-token-1" }, "address": { "token": "address-token-1", "customer_token": "customer-token-1", "address_line1": "123 Main St.", "address_line2": "", "city": "Anytown", "state": "CA", "postal_code": "12345" }, "contact": { "token": "contact-token-1", "customer_token": "customer-token-1", "first_name": "John", "last_name": "Doe", "email": "[email protected]", "phone": "123-456-7890" }, "credit": { "token": "credit-token-1", "customer_token": "customer-token-1", "credit_limit": 10000, "balance": 2500, "credit_score": 750 } } }


以下は、Heroku アプリから実行されている Apollo Explorer のスクリーンショットです。


結論

私がキャリアを始めた頃、Java と C# が開発者の採用をめぐって競い合っていたことを思い出します。どちらの側の支持者も、自分の選んだ技術が最善の選択であることを証明しようとしていました...たとえそれが最善の選択でなかったとしても。


この例では、Customer 360 のユースケースを複数の方法で満たすことができました。実績のある RESTful API を使用すればうまくいきましたが、必要なデータをすべて取得するには複数の API 呼び出しが必要でした。Apollo Server と GraphQL を使用することで、1 回の API 呼び出しで目標を達成できました。


また、ターミナルでいくつかのコマンドを実行するだけで、GraphQL サーバーを Heroku に簡単にデプロイできるのも気に入っています。これにより、インフラストラクチャの負担を軽減し、信頼できるサードパーティ プロバイダーにコードを実行させることで、実装に集中できます。最も重要なのは、これが私の個人的なミッション ステートメントと完全に一致していることです。


「知的財産の価値を高める機能や特徴を提供することに時間を集中してください。他のすべてについてはフレームワーク、製品、サービスを活用してください。」

– J. ベスター


この記事のソースコードにご興味がございましたら、 GitLabで入手できます。


でも待ってください…まだあります!


次回の投稿では、GraphQL サーバーをさらに構築し、サブスクリプションによる認証とリアルタイムのデータ取得を実装します。


本当に素晴らしい一日をお過ごしください!