Morning Girl

Web API, Windows, C#, .NET, Dynamics 365/CRM etc..

Java クライント開発における Web API の実装アプローチ:その5 GraphQL 編

前回は REST ful な API プロトコルの OData でJavaクライアントを実装してみました。

今回は、2015年に公開された割と新しいAPI プロトコルであるGraphQLで実施してみたいと思います。

GraphQL は数年前に比べると海外のカンファレンスも活発に開かれるようになり、エコシステムも充実の兆しを見せています。

最初の記事はこちらから。

bit.ly

GraphQL とは何か?

f:id:sugimomoto:20190122143925p:plain

graphql.org

GraphQL は 2015年 に Facebook が公開した Web APIのための新しいクエリ言語です。

独自の構造的なクエリ言語を用いることで、一つのエンドポイントで複数のリソース、ネストされた構造体のデータを取得することがしやすいように構成されています。

f:id:sugimomoto:20190122144258p:plain

私が手っ取り早く、GraphQLという思想の理解を促そうとするならば、Facebookの画面を見るのが早いと思っています。

FacebookのWeb siteにアクセスすると、以下のような画面が表示されるかと思いますが

f:id:sugimomoto:20190122140152p:plain

これらのデータは、私を中心として、友達や所属しているグループ、私のタイムラインなど、様々なリソースから取得したデータを元に構成されています。

f:id:sugimomoto:20190122140159p:plain

これが、今までのRESTの思想で実装する場合、以下のようにリソースごとのリクエストを都度実施する必要がありました。(まあ、一つのエンドポイントで全部返すってこともREST思想的にはできますけど)

f:id:sugimomoto:20190122140208p:plain 引用元:GraphQL vs REST - A comparison

それをGraphQLであれば、以下のように一つのリクエストで関係性を持つデータを一括で取得して、クライアントサイドへレンダリングするということが実現することができます。

f:id:sugimomoto:20190122140219p:plain 引用元:GraphQL vs REST - A comparison

このように、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があります。

github.com

こちらは認証無しで触れるので、試しに GraphQL を触って見るにはもってこいだと思います。

対象の GraphQL 環境

f:id:sugimomoto:20190122140913p:plain

対象となるGraphQLの環境は Hasura というオープンソースのGraphQL Server アプリケーションで作成しました。

PostgreSQL の既存DBからGraphQL APIを自動生成してくれたり、GraphQLスキーマからDBを生成してくれたりするすぐれものです。

hasura.io

データ構成は、REST や Swagger の記事で公開したものと一緒ですので、シナリオも変わりません。環境の構築方法はまた別の記事にまとめたいと思います。

そして、作成したGraphQL環境はこんな感じです。

f:id:sugimomoto:20190122144744p:plain

たとえば、以下のような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コードを生成するプログラムを利用して、試してみたいと思います。

github.com

これを利用することで、ラムダベースで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/

・npm https://www.npmjs.com/

ruby https://www.ruby-lang.org/ja/

・bundler https://bundler.io/

GraphQLJavaGenを使う手順① GraohQL スキーマ JSON形式の生成

まず、対象のGraphQL API から スキーマJSONファイルを生成します。

node.js環境とnpmが必要になるので、あらかじめインストールしておきましょう。

github.com

手順はそんなに難しくありません。まず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に置換しました。

f:id:sugimomoto:20190122141859p:plain

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ソースコードが生成されます。

f:id:sugimomoto:20190122142317p:plain

実装コード

それでは、実際に生成されたソースコードを使って実装してみたいと思います。

必要なライブラリは以下のとおりです。

・OkHttpClient

・GSON

・com.shopify.graphql.support:support:0.2.0

gist.github.com

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 を叩く方法を紹介したいと思います。