Java クライント開発における Web API の実装アプローチ:その5 GraphQL 編
前回は REST ful な API プロトコルの OData でJavaクライアントを実装してみました。
今回は、2015年に公開された割と新しいAPI プロトコルであるGraphQLで実施してみたいと思います。
GraphQL は数年前に比べると海外のカンファレンスも活発に開かれるようになり、エコシステムも充実の兆しを見せています。
最初の記事はこちらから。
GraphQL とは何か?
GraphQL は 2015年 に Facebook が公開した Web APIのための新しいクエリ言語です。
独自の構造的なクエリ言語を用いることで、一つのエンドポイントで複数のリソース、ネストされた構造体のデータを取得することがしやすいように構成されています。
私が手っ取り早く、GraphQLという思想の理解を促そうとするならば、Facebookの画面を見るのが早いと思っています。
FacebookのWeb siteにアクセスすると、以下のような画面が表示されるかと思いますが
これらのデータは、私を中心として、友達や所属しているグループ、私のタイムラインなど、様々なリソースから取得したデータを元に構成されています。
これが、今までのRESTの思想で実装する場合、以下のようにリソースごとのリクエストを都度実施する必要がありました。(まあ、一つのエンドポイントで全部返すってこともREST思想的にはできますけど)
それをGraphQLであれば、以下のように一つのリクエストで関係性を持つデータを一括で取得して、クライアントサイドへレンダリングするということが実現することができます。
このように、REST の思想によるボトルネックを解消しようとして開発されたのが、GraphQL です。
なお、リソースの作成などで使う Mutation や アップデートなどの通知を受け取るための Subscription といった仕組みもありますが、今回は割愛します。
GaphQLで公開されているサービス
現状はどちらかと言えば、インターナルなAPIとして。React等の組み合わせた利用シチュエーションが多いと言えますが、Github や Shopify などがオープンなAPIとしても提供を進めています。
・Github https://developer.github.com/v4/
・Shopify https://help.shopify.com/en/api/custom-storefronts/storefront-api
あと、とっつきやすいプロジェクトとして、StarWarsデータを扱ったGraphQL APIがあります。
こちらは認証無しで触れるので、試しに GraphQL を触って見るにはもってこいだと思います。
対象の GraphQL 環境
対象となるGraphQLの環境は Hasura というオープンソースのGraphQL Server アプリケーションで作成しました。
PostgreSQL の既存DBからGraphQL APIを自動生成してくれたり、GraphQLスキーマからDBを生成してくれたりするすぐれものです。
データ構成は、REST や Swagger の記事で公開したものと一緒ですので、シナリオも変わりません。環境の構築方法はまた別の記事にまとめたいと思います。
そして、作成したGraphQL環境はこんな感じです。
たとえば、以下のようなQueryをリクエストすると
query{ orders{ order_id customer_id employee_id order_details{ product_id quantity discount } } }
こんな感じのレスポンスが返ってきます。
{ "data": { "orders": [ { "order_id": 10248, "customer_id": "VINET", "employee_id": 5, "order_details": [ { "product_id": 11, "quantity": 12, "discount": 0 }, { "product_id": 42, "quantity": 10, "discount": 0 }, { "product_id": 72, "quantity": 5, "discount": 0 } ] }, { "order_id": 10249, "customer_id": "TOMSP", "employee_id": 6, "order_details": [ { "product_id": 14, "quantity": 9, "discount": 0
ちなみに、HTTPでリクエストする場合は、以下のような形式になります。
POST /v1alpha1/graphql HTTP/1.1 Host: sampledbforjavawebapi.herokuapp.com {"query": "{orders{order_id,order_date,customer_id,employee_id,order_details{product_id,unit_price,quantity,discount}}}"}
Java クライアントアプリケーションから快適にGraphQLを使う方法
GraphQL API も 言ってしまえば、クエリをPOSTで投げて、JSONを返す API ですので、HTTPクライアントと JSON パーサーを使えば実装できないこともありません。
でも、それだとあまりにも、GraphQLを恩恵を受けられないので、GraphQL API を公開している Shopify が開発しているJava用 GraphQLコードを生成するプログラムを利用して、試してみたいと思います。
これを利用することで、ラムダベースでGraphQL Queryが作成できたり
String queryString = ExampleSchema.query(query -> query .user(user -> user .firstName() .lastName() ) ).toString();
GraphQLスキーマをベースに生成したデシリアライズ用クラスへインプットできたりします。
ExampleSchema.QueryResponse response = ExampleSchema.QueryResponse.fromJson(responseJson); ExampleSchema.User user = response.getData().getUser(); System.out.println("user.firstName() + " " + user.lastName());
GraphQLはその性質上、ネストされた構造体を扱いやすいので、かなり恩恵を受けやすいと思います。
GraphQLJavaGenを使う手順 必要なもの
最終的にはもちろんJavaなんですが、それまでの過程でNode.jsとRubyが必要になります。
ただ、基本的にはコマンドを叩くだけなので、そこまで難しくは無いです。それぞれの言語とパッケージ管理ツールをインストールしておきましょう。
・node.js https://nodejs.org/ja/
・ruby https://www.ruby-lang.org/ja/
・bundler https://bundler.io/
GraphQLJavaGenを使う手順① GraohQL スキーマ JSON形式の生成
まず、対象のGraphQL API から スキーマのJSONファイルを生成します。
node.js環境とnpmが必要になるので、あらかじめインストールしておきましょう。
手順はそんなに難しくありません。まずGraphQL をコマンドから実行できるようにする「graphqurl」をnpmでインストールして
npm install -g graphqurl
以下のようなコマンドで対象のGraphQL API を指定すれば、カレントフォルダにスキーマファイル(JSON形式)が生成されます。
gq https://sampledbforjavawebapi.herokuapp.com/v1alpha1/graphql --introspect --format json > schema.json
ただ、なぜか生成したスキーマファイルは、後ほど実施するCode Cenerator 用として、ルートObjectに「data」が無いので、それを追加しておきます。
あと、現在「smallint」型が対応していないので、Intに置換しました。
GraphQLJavaGenを使う手順② Java ClientSide Code Generator for GraphQL
それでは、実際に GraphQLJavaGen を使ってJavaのソースコードを生成したいと思います。
あらかじめruby と bundlerが必要となるので、インストールしておきます。
ダウンロードした GraphQL スキーマも配置されているフォルダで bundler をinitializeし
bundler init
生成されたGemfileに以下の gem を追加
gem 'graphql_java_gen'
対象ファイルに Ruby ファイル(.rb)を作成して、読み取るファイル、アウトプットするファイル名を指定すればOKです。
require 'graphql_java_gen' require 'graphql_schema' require 'json' introspection_result = File.read("schema.json") schema = GraphQLSchema.new(JSON.parse(introspection_result)) GraphQLJavaGen.new(schema, package_name: "com.company", nest_under: 'GraphQLSchema', custom_scalars: [ GraphQLJavaGen::Scalar.new( type_name: 'Decimal', java_type: 'BigDecimal', deserialize_expr: ->(expr) { "new BigDecimal(jsonAsString(#{expr}, key))" }, imports: ['java.math.BigDecimal'], ), ] ).save("#{Dir.pwd}/GraphQLSchema.java")
実行すると、以下のような Java のソースコードが生成されます。
実装コード
それでは、実際に生成されたソースコードを使って実装してみたいと思います。
必要なライブラリは以下のとおりです。
・OkHttpClient
・GSON
・com.shopify.graphql.support:support:0.2.0
Java クライアントアプリケーションから GraphQL を利用するにあたってのポイント
ライブラリ紹介のところでもすでに伝えていますが、なんといっても Query を Java のラムダで書くことができるのが素晴らしいです。
String graphqlRequest = GraphQLSchema.query(query -> query.orders( orders -> orders .orderId() .orderDate() .customerId() .employeeId() .orderDetails(orderDetails -> orderDetails .productId() .unitPrice() .quantity() .discount() ) )).toString();
ちなみにgraphqlRequest の中身はこんな感じになります。
{orders{order_id,order_date,customer_id,employee_id,order_details{product_id,unit_price,quantity,discount}}}
1:Nで構成されている Order と OrderDetails の関係性もラムダで表現でき、かつGraphQLを書いた場合と変わらずに直感的に記述できるのがいいですね。
この Query を用いて、素直にHTTP POST リクエストでGraphQLサーバーに渡します。
受け取った JSON は「QueryResponse.fromJson.getData().getXXXXXXX()」で各データ格納クラスにList<> で展開することができます。
GraphQLSchema.QueryResponse queryResponse = GraphQLSchema.QueryResponse.fromJson(response.body().string()); List<GraphQLSchema.orders> orderList = queryResponse.getData().getOrders();
このように、GraphQL でリクエストを投げるところから、レスポンスを受け取るところまで、GraphQLの特性を活かしながら実装することができるので、あまりGraphQLだからといって敷居を高く感じることなく、Javaで操作できるのではないでしょうか。
(ただ、生成はちょっと大変・・・。)
おわりに
今まで私自身 node.js でしか GraphQL を触っていなかったのですが、静的型付の Java でもこんな感じで操作できることがわかると、バッチなどの実装しやすさは相当変わるなぁと感じました。
もうちょっと Java のエコシステムにも GraphQL カルチャーが浸透してほしいところではありますが、 GraphQL自体もこれから発展していく様相のプロトコルですので是非今後に期待したいなと思います。
それでは、次回は若干番外編として CData Driver で Web API を叩く方法を紹介したいと思います。