LayerX エンジニアブログ

LayerX の エンジニアブログです。

GraphQL で REST API を作る - 技術的な挑戦と、それを支える文化の話

バクラク事業部の Product Enabling Team でソフトウェアエンジニアをしている @izumin5210 です。 この記事は LayerXアドベントカレンダー(概念) の54日目の記事です。


バクラクのリソースを提供する REST API を開発するにあたり、その基盤を GraphQL をベースに実装する、ということをしました。 なかなかにチャレンジングな選定であり、これを入社してすぐ作っているという背景も含めておもしろい話だと思うので、技術的な話と文化的な話をあわせて紹介させてください。

「GraphQL で REST API を作る」とは?

まず「GraphQL で REST API を作る」というのが見慣れない文字列なのではないでしょうか? 前提を揃えるために目指す形を明確にしておくと、だいたい以下のようなものです。

  • 外から見た API は REST に従っており、HTTP の上で JSON をやり取りする
  • 内部実装的には GraphQL であり、GraphQL スキーマを定義し、resolver を実装する

図で説明すると以下のような感じでしょうか。 中央の "Partner Gateway" と書いた四角全体が REST API サーバであり、内部は謎の GraphQL - REST adapterGraphQL Schema で構成されています。 この GraphQL Schema に実装されている Resolver がバックエンドの各リソースサーバ*1を叩き、データを取得するイメージです。

GraphQL に REST の皮を被せたサーバ
GraphQL に REST の皮を被せたサーバ
(しれっと gRPC とか書いてますが、実は Connect を利用したアグレッシブな選定です。これも別の機会で紹介できれば…。)

なんとなくイメージは掴めたでしょうか? では、そもそもなぜこんな構成にしたのか。インタフェース・内部実装それぞれについて説明していきます。

なぜ REST API として見せる必要があったか

なぜ REST API としたか。それは、より広く使われる機会を増やすために、シンプルかつ一般に広く親しまれている形式で提供するためです。

冒頭に「バクラクのリソースを提供する REST API」と書きましたが、この API は社外のパートナー向けであり、バクラクと他社システムをデータ連携するためのものでした。 社内向けであればインタフェースは GraphQL でも問題ないのですが、利用者が社外であれば話は別になります。 今後利用していただくパートナーが増えることがあったとして、「GraphQL はちょっと…」というケースが発生するかもしれません。

GraphQLも広く使われるようになってそれなりに経ちます。が、よりシンプルで広く使われている、REST API のようなインタフェースにしておくほうが、多くの機会をいただける可能性は高いと判断しました。

もし今後 GraphQL が一般に広く浸透することがあれば、REST API の皮を剥がすだけで GraphQL 版も提供できるかもしれません*2。そういう意味でも可能性を広く残した結果といえます。

なぜ素直に REST API として実装せず、GraphQL ベースとしたか

なぜここまでして GraphQL で実装したかったか、大きくは生産性の問題と、今後を見据えた挑戦という2つの理由があります。

まず生産性の面。GraphQL のサーバを実装する際、多くの場合はフィールドの Resolver を実装するような API となります。 「Query の Field Resolver はそのクエリ結果のリソースを返し、Model の Field Resolver はリソース間依存の解決をする*3」という GraphQL の実装パターンは、考え方に慣れると 非常に生産性の高いものになります。 よくあるようなリソース間依存解決時の N+1 問題も DataLoader という一般的な解が存在し、手で N+1 問題を解決する実装をするより見通しが良いコードになりやすいです。 LayerX では既に gqlgen による GraphQL サーバの実装は存在するため、メンバーが GraphQL サーバの考え方に既に親しみがあるというのも後押しになりました。

もう1点、「今後を見据えた挑戦」と書きました。 前提として、自分の所属している Enabling Team ではバクラクの可能性を広げつつ、爆速で楽しい開発を実現するための新しいアーキテクチャや開発基盤の検討・構築を進めています。 ここで GraphQL と gRPC を使ったアーキテクチャを採用する方向で話が進んでおり、その試金石的に GraphQL(と gRPC)をこの段階で投入しておきたい という意図がありました。

GraphQL と gRPC を使った新アーキテクチャのイメージ - webapp と Gateway が GraphQL で通信し、Gateway とバックエンドのサービスは gRPC で通信する
GraphQL と gRPC を使った新アーキテクチャのイメージ

開発期間はあまり長くはありませんでしたが、その期間であればイケると判断し、かつ上手く行かない場合は通常の REST API で実装しなおせばいいだけというのもあり、思い切った決断をしました。 (他プロダクトと実装・開発が独立しており、かつ初期段階のコードの規模もそれほど大きくないため、切り戻しやすく挑戦がしやすいプロジェクトでもありました。)

REST の皮を被った GraphQL サーバの実装

REST の皮を被った GraphQL サーバの実装には、ざっくり以下のような技術を使いました。

  • Node.js
  • TypeScript
  • GraphQL Schema: Pothos GraphQL
  • GraphQL -> REST 変換: GraphQL SOFA
  • GraphQL サーバ: GraphQL Yoga (デバッグ用に通常の GraphQL エンドポイントも必要だったため)

ここからは、この構成の肝である GraphQL SOFA について軽く紹介します。 (GraphQL Pothos なんかも面白い選定なので紹介したいところですが、それは別の機会として…。)

GraphQL SOFA

SOFA は JS で GraphQL やってる人ならお世話になっているであろう The Guild のメンバーがメンテナンスしているライブラリです。

the-guild.dev

GraphQL の Query や Mutation の定義から対応する REST API を実装する、という文字にすると極シンプルな責務を持ちます たとえば、以下のような GraphQL のスキーマから、

type Query {
  user(id: ID!): User
  users: [User!]
}

2つの API を生成してくれます。

  • GET /users
  • GET /users/{id}

REST 側に露出させないフィールドを選択したり、パス名を変えたり Prefix をつけたりと、必要そうな機能はわりと揃っていそうです。

また、もう1つ重要な機能として、「OpenAPI によるスキーマ定義を出力できる」というものがあります。 外部提供する API としては API ドキュメントは欠かせないため、この機能は必須でした。 GraphQL スキーマ上のドキュメントは概ね OpenAPI にも出力してくれるのはもちろん、Enum や Custom Scalar といった GraphQL の型もうまく OpenAPI 上に落とし込んでくれます。

…と、最初から対応していたみたいに書いてますが、いくつかは自分で実装して PR を送って取り込んでもらいました。 コードが見やすく、かつ PR をすぐ取り込んでくれるのも選定に自信を持てた理由の一つです。

機能自体はシンプルで、実装もコンパクトで見通しよくまとまっているので、試したい場合は clone してコード読みつつ Example をいじってみるといいでしょう。

github.com

GraphQL SOFA の採用から見る LayerX 羅針盤

「なぜ GraphQL ベースにしたか」はこの記事で説明したとおりですが、やはりそれなりにチャレンジングに見えるのではないでしょうか。 これに OK が出るのは、羅針盤で示されている考え方の方向性や価値観が浸透してるからだなと、いまは感じています。

speakerdeck.com

まず、Bet Tecnhology や Be Animal - 技術に Bet し、変化を厭わず挑戦することが指針として示されており、No じゃなきゃ Go という価値観があったからこそ、こういうチャレンジができたのだと思います。 (一方で、このあたりの文化は濫用せず・あぐらをかかず、誠実に取り組んでいきたいものでもありますね。自戒を込めて。)

NoじゃなきゃGo - 文字通り、Noと言われていないものはGoして良いというのがLayerXの文化です。大前提、上司は全ての正解を知りません。上司がYesといったものしかトライがなされない組織は弱い組織です。自ら主体性を持ち、Be animalに新しいFactを獲得していく。これを組織で実践するには、ボトムアップ型の自律的なトライが不可欠です。
NoじゃなきゃGo

また、入社直後であり LayerX メンバーとしては信頼を積めていない状態の自分がこの方針で進められたのも、Trustful Team - プロとして信頼してくれたというのがあってこそなのでしょう。

技術的な意思決定をすることがある者としては、「大きな失敗を防ぐため、小さく失敗」「小さく失敗し、たくさん学習」という価値観が明言されているのも非常に心強いです。

大きな失敗を防ぐため、小さく失敗しよう - LayerXはチャレンジを推奨する文化です。その中で失敗は必ず起きます。失敗をしてしまうとその後どうしても組織は硬直的になります。失敗からの仕組み化がLayerXの文化ですが、その際、「大きな失敗を防ぐために小さな失敗をたくさんしよう」という考え方が大事です。 小さく失敗し、そこからたくさん学習することで、結果的に大きな失敗を防ぐ・対応力のある強い組織になっていきます。
大きな失敗を防ぐため、小さく失敗しよう

おわりに

若干とりとめのない感じになってしまいましたが、LayerX における挑戦を支える文化と、実際の挑戦として「GraphQL で REST API を作る」という例を紹介させていただきました。

記事内で匂わせるだけになってしまいましたが、この他にも Enabling Team はたくさんの技術的な挑戦と向き合っており、なかなかにエキサイティングです。

この記事を読んで もっと話を聞いてみたい! や GraphQLの話しようぜ! な方、ぜひ Twitter や以下のリンクからお声がけください!

open.talentio.com

open.talentio.com

open.talentio.com

open.talentio.com

*1:今回のケースだと、リソースサーバは既存のバクラクプロダクトのバックエンドになります。なので、 DB や主だったロジックは既に存在している状態。

*2:GraphQL として外部に出すにはセキュリティ等の観点で別の検討事項はは存在するでしょう

*3:Model の Field Resolver は computed field 的に使うケースもありますが、ロジックを後ろに移譲する Gateway サーバとしては依存解決が主になるのではないでしょうか