エムスリーでマルチデバイスチームのチームリーダーをしている松原@ma2geです。 マルチデバイスチームはこちらのテックブログでは初出なので簡単に紹介すると、iOS や Android 等のデバイス対応を主導する開発するチームで、主に iOS, Android のネイティブアプリ開発から、アプリから叩く API サーバ(いわゆる Backends For Frontends(BFF))、プッシュ通知基盤システムのバックエンドサービスも開発しております。 私自身は3月までは別チームで Rails/Java/Elixir などを触っていましたが、4月から現チームに移動しこちらでもまた新たな挑戦をさせてもらっています。 💪
前提
今回はネイティブアプリ向けの RESTful な API サーバがレガシーとなっており、このサーバのリニューアルを検討している話を書きます。 対象のサーバはフレームワークが古く、諸事情により刷新することを前提としています。 せっかくなので技術的に新しいことも取り入れたく、リニューアルにあたって解決したい課題、選定する技術をチームで議論したところ、RESTful で行くか GraphQL で行くかについて決めかねる状況になっています。
※gRPC も検討時に候補として上がっていますが、今回の記事では触れません。
ちなみに RubyKaigi で「Ask Me Anything about GraphQL」Tシャツを来た方に、2018 年にもなって GraphQL 使っていないとかどういうこと?って煽られたことはしばらく忘れないと思います。
GraphQL については森が入門記事を書いているため、よければご覧ください。
RESTful vs GraphQL
今回のリニューアルに当たって特に改善したい課題は以下の通りです。
- クライアントの自動生成(今までは手作り)
- ドキュメントの管理を同一レポジトリで実装と差分なく行いたい(今までは別となっていた)
これらは GraphQL を使うことでどちらも解決できそうですが、今まで通り RESTful で行く場合も OpenAPI(Swagger) を使うことでほぼ同様の解決を行えます。
GraphQL にすることでさらに以下のメリットがあると理解しつつも、これらは今回は必須ではありません。
また GraphQL は今まで社内での採用事例がなく、RESTful で行くか GraphQL で行くか決定するための情報が不足しており、以下のような点が不明瞭でした。
- ネイティブアプリなどから呼び出すクライアントは本当に自動生成できるのか、それの使い勝手はどうか
- 生成されるドキュメントは要望を満たせるのか
- GraphQL で作ることでバックエンドの作りが複雑になるのではないか(開発のイメージがついていない)
そこで GraphQL についての判断材料を増やすために今回はプロトタイピングを行うことにしました。
GraphQL でのプロトタイピング
プロトタイピングなので手軽な技術を使ってみることにします。 今回対象のサーバは Java で出来ており、弊社の各種 Microservice の API を叩くいわゆる BFF となっています。 そのため Java の資産が使えて、チームの技術スタックとしてもマッチする、Kotlin と Spring Boot をベースとして、GraphQL 部分には graphql-java の各種ライブラリを用いて検証しました。 以下に記載したサンプルコードはこちらへ上げましたので合わせてご覧ください。
GraphQL 実装の下準備
Spring Boot の雛形を https://start.spring.io/ からダウンロードし、下記 GraphQL 関連のライブラリを依存関係に追加することで簡単なサンプルの準備はすぐに出来ます。
- graphql-java-tools
- スキーマファーストな開発を支援するライブラリ
- 内部的に graphql-java へ依存
- ほとんどが Kotlin で実装されています
- graphql-spring-boot-starter
- graphql-java-servlet を有効にしてくれる
- graphiql-spring-boot-starter
- /graphiql エンドポイントを追加してくれ、GraphiQL が動くようになる
# build.gradle から抜粋 dependencies { # 省略... compile "com.graphql-java:graphiql-spring-boot-starter:4.2.0" compile "com.graphql-java:graphql-spring-boot-starter:4.2.0" compile "com.graphql-java:graphql-java-tools:5.1.0" }
GraphQL の簡単なサンプル実装
開発の流れを掴むために簡単な例をあげます。
GraphQL のスキーマ定義は src/main/resources/graphql/ に .graphpqls
拡張子でおきます。
例えば中身はこのような感じです。Author
と Book
の型があり、クエリとして Author
を取得するための getAuthorById
フィールドを定義しています。
type Author { id: ID! name: String! books: [Book]! } type Book { id: ID! name: String! } extend type Query { getAuthorById(id: ID!) : Author }
上記スキーマに対応する処理を書きます。
まずは Author
と Book
に対応するデータクラスを作ります。
data class Author( val id: Int, val name: String ) data class Book( val id: Int, val name: String, val authorId: Int )
そしてクエリのフィールド getAuthorById
に対応するリゾルバを書きます。GraphQLQueryResolver
を継承したクラスにクエリのフィールド名に対応する関数を書くことで、graphql-java-tools が対応する処理を紐づけてくれます。
@Component class AuthorQueryResolver(private val authorDao : AuthorDao) : GraphQLQueryResolver { fun getAuthorById(id: Int) = authorDao.getAuthorById(id) }
AuthorDao
が出てきましたが、ここでは感覚を掴むために実際のデータへ繋ぐのではなく簡易的なサンプルとして以下の実装をしています。
@Component class AuthorDao { private val data = mutableListOf( Author(id = 1, name = "taka"), Author(id = 2, name = "susan") ) fun getAuthorById(id: Int) = data.firstOrNull { author -> author.id == id } }
最後にスキーマの Author
にある books フィールドについてですが、データクラスの Author
には books
プロパティがありません。
そのため対応するリゾルバを定義しますが、こちらはクエリではないので GraphQLQueryResolver
ではなく、GraphQLResolver
を使います。
GraphQLResolver
に対応する型として Author
を指定し、そのフィールド名である books
に対応する関数を定義することで紐づけることができます。
また books
にはそのフィールドを持つ Author
のオブジェクトが渡されてきます。これによって以下の通り N+1 の問題を回避することが可能になっています。
@Component class BookResolver(private val bookDao : BookDao) : GraphQLResolver<Author> { fun books(author: Author) = bookDao.getBooksByAuthorId(author.id) }
BookDao
はこのように実装しています。
@Component class BookDao { private val data = mutableListOf( Book(id = 1, name = "Elixir入門", authorId = 1), Book(id = 2, name = "Erlang入門", authorId = 2), Book(id = 3, name = "Kotlin入門", authorId = 2), Book(id = 4, name = "Rust入門", authorId = 2) ) fun getBooksByAuthorId(authorId: Int) = data.filter { book -> book.authorId == authorId } }
ここまできたら $ ./gradlew bootRun
でアプリケーションを起動をして、http://localhost:8080/graphiql へアクセスします。
左上のペインで以下のクエリを入力して実行すると、右のペインへ結果が表示されるはずです。
{ getAuthorById(id: 2){ id name books{ id name } } }
一連の開発の流れとしてはスキーマ定義し、型に対するモデルを定義、対応するリゾルバを作りモデルを返す、ということが確認できました。
これを元に既存 API を実際に実装してみましたが、大まかな開発のイメージとしては RESTful で今までやっていたものと大きく異なるものではないということが確認できました。 graphql-java-tools のようなライブラリを使うことで、GraphQL だからと言って開発が大変になるようなことはなさそうです。ただしシンプルではない複雑なクエリについては、さらに検証が必要と考えています。
ドキュメンテーション
ドキュメントについては、GraphiQL がとても便利なツールとなっております。 スキーマが定義されていれば、http://localhost:8080/graphiql アクセスした際の右ペインで定義されているスキーマ情報を確認することができます。 ただ、スキーマ情報だけではクエリや型に対する説明が足りておらずドキュメントとして情報が不足しています。 こちらについては以下のように .graphqls ファイルへコメントを加えてあげることで説明文を追加することができるようになっています。ちなみにこちらも graphql-java-tools が頑張ってくれています。そのおかげで Swagger UI のような便利さも手に入れることができていると感じます。
# 著者 type Author { id: ID! # 著者名 name: String! # 著者の本 books: [Book]! } # 本 type Book { id: ID! # 名前 name: String! } extend type Query { # 著者を取得する getAuthorById(id: ID!) : Author }
クライアント側
クライアントの自動生成については Android のプロジェクトで試しました。使用したライブラリは Apollo GraphQL Client for Android を使いました。 今時のライブラリっぽく RxJava2 にも対応しているので、既存のアプリから使うのにもちょうど良いです。
こちらについてアプリへの導入は Apollo の README やサンプルアプリを見ていただくのが良いと思います。
流れとしては .graphql
のファイルにアプリで必要とするクエリを定義してやると、それを元にクライアントが自動生成されます。
この辺りは GraphQL の柔軟さをうまく取り込んでいるなと感じます。
例えば上で例として出した getAuthorById
クエリについて例を出すと、src/main/graphql/com/example/sample/api/API.graphql というファイルを作って以下のように中身を記載します。
query Author($id: ID!){ getAuthorById(id: $id){ id name books{ id name } } }
すると、AuthorQuery
という名前でクエリ用のビルダークラスが生成され、レスポンスについてもクラスが作られ Response<AuthorQuery.Data>
として受け取れるようになります。
今までは各 RESTful API についてシリアライズするようなコードを書いて手作りしていたので、それと比べるとかなり楽をできそうだと想像できました。
プロトタイピングしてみて
プロトタイピングを通して、一通りの開発の流れを追ってみることで、不明瞭だった以下の点について情報量を増やすことができました。
- ネイティブアプリなどから呼び出すクライアントは本当に自動生成できるのか、それの使い勝手はどうか
- 生成されるドキュメントは要望を満たせるのか
- GraphQL で作ることでバックエンドの作りが複雑になるのではないか(開発のイメージがついていない)
ちなみにこの他にも認証をどうするか、並列処理はできるのかなど、あまりクリアでなかった点も合わせて確認したり、メンバーから受けた指摘をさらに検証したりといったことをしています。
まとめ
プロトタイピングした本人というのもありますが、個人的には今のところ GraphQL に傾いています。 ただそのまま1人の意見で決めてしまうのは好ましくないので、チームメンバーにもフィードバックをもらいつつ進めていってます。 エムスリーでは自分たちの環境は、自分たちで良くしていくという文化があると思っています。 その一部を見せられたらなと思い、今回検討途中ではありましたが検討の過程を記事にしてみました。
マルチデバイスチームではエンジニアを募集中です
マルチデバイスチームでは Android、iOS、サーバサイドエンジニアを募集しています。 私も3ヶ月ほど現チームで仕事をしてみて思ったのですが、サーバサイドやりつつ機会があれば Android や iOS にも手を出せる(あるいはその逆も)という環境のあるチームなので、キャリアを広げたい人にも向いている環境ではないかと感じています。 もし興味がわいた方は、カジュアルにお話しましょう。ぜひエンジニア向けフォームよりご連絡お願いします。 もちろんtwitterで私に直接声をかけていただいても構いません。
参考リンク
- 今回のサンプルコード https://github.com/m3dev/graphql-spring-boot-kotlin-sample
- GraphQL入門 - React.js & Express.js & Apollo の簡単チュートリアル - エムスリーテックブログ http://www.m3tech.blog/entry/graphql-apollo-react-express-nodejs
- graphql-java/graphql-java-tools: A schema-first tool for graphql-java inspired by graphql-tools for JS (https://github.com/apollographql/graphql-tools) https://github.com/graphql-java/graphql-java-tools
- graphql-java/graphql-spring-boot: GraphQL and GraphiQL Spring Framework Boot Starters - Forked from oembedler/graphql-spring-boot due to inactivity. https://github.com/graphql-java/graphql-spring-boot
- apollographql/apollo-android: A strongly-typed, caching GraphQL client for Android, written in Java https://github.com/apollographql/apollo-android
- GitHub GraphQL API v4 を Android で遊んでみる - Qiita https://qiita.com/ssoejima/items/0ae0cb97aabfcac11c6a#2-schemajson-%E3%82%92%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89
- GraphQLを使ったAPI仕様中心開発の導入とその効果の紹介 - Kaizen Platform 開発者ブログ https://developer.kaizenplatform.com/entry/laco/2018-06-08
- GraphQL and Authentication – The GraphQLHub – Medium https://medium.com/the-graphqlhub/graphql-and-authentication-b73aed34bbeb
- graphql/graphiql: An in-browser IDE for exploring GraphQL. https://github.com/graphql/graphiql
- How to GraphQL - The Fullstack Tutorial for GraphQL https://www.howtographql.com/