NestJSでスケーラブルなBFFを構築。メルカリShopsエンジニアがGraphQL活用のリアルを語る【技術選定の裏側:GraphQL編 vol.2】
モダンな技術の活用法に迫る、エンジニア座談会企画がスタート!第2弾は、前回に引き続き「GraphQL」の魅力に迫ります。
***
誰でも簡単に売り買いが楽しめる日本最大のフリマアプリ「メルカリ」。2021年10月には、メルカリ内で個人・法を問わずに自身のお店を開くことができる「メルカリShops」がリリースされました。今回は、メルカリShopsの開発を手がけている株式会社ソウゾウより、CTOの名村さん、フロントエンドエンジニアの末田さん、バックエンドエンジニアの市原さんをお招きし、GraphQLの活用法や、実際に利用した上で感じたことをお伺いしました。
■登場人物プロフィール
名村 卓 (Suguru Namura) / suguru
株式会社ソウゾウ 取締役CTO
受託開発経験を経て、2004年株式会社サイバーエージェント入社。各種メディアやゲームなどの新規事業立ち上げの開発を担当。2016年に株式会社メルカリ入社。USに出向し、USのサービス開発を担当。2017年4月CTO就任。2021年1月より現職。
https://twitter.com/snamura
市原 睦美 (Mutsumi Ichihara) / mookjp
Software Engineer
2019年株式会社メルカリ入社。ソフトウェアエンジニアとして出品機能のバックエンド開発に携わる。2020年3月より株式会社メルペイでメルペイスマート払い、定額払い機能のバックエンド開発ののち、2021年5月より現職。
https://twitter.com/mookjp
末田 正樹 (Masaki Sueda) / sue71
Software Engineer
2013年に株式会社サイバーエージェントに入社し、各種ゲーム、メディア新規事業の開発を担当。 株式会社トレタでBtoBのSaaS事業開発、TechLeadに従事した後、2018年に旧ソウゾウ、メルペイに入社。iOS/Webフロントエンドエンジニアとして各種サービスの開発支援を行う。 その後フリーランスを経て2021年7月に現ソウゾウに入社。主にBFF、Webフロントエンドの開発を担当している。
今回の取材はオンラインで実施しました
将来を見越して、効率がよく拡張性のあるGraphQLを採用
──最初にアーキテクチャについてお話いただけますか。
名村:私たちが開発しているメルカリShopsは、フロントエンドはWebベースで展開していて、Next.jsで構築しています。GraphQLはNext.jsとバックエンドの間にBFFとして置いていて、フレームワークとしてNestJSを使用しています。Goで書かれたマイクロサービスがGraphQLの後ろで動いていて、各マイクロサービスから情報を取って結果を返すといった構成です。
──GraphQLを採用された理由はなんだったのでしょう?
名村:メルカリShopsのように複雑なサービスの場合は、REST APIだと効率があまり良くないと思ったからです。
現在は背後に複数のマイクロサービスがあって、それらを統合してAPIとして提供するスタイルがトレンドですし、メリットも多いと言われていますよね。そのような構成をした場合、フロントエンドとしては一つの口にアクセスしたいのですが、REST APIだと一貫性を保つようにAPIを定義しないといけないし、パスやリクエストメソッドの扱いなど、考えることが多いんですよ。
GraphQLであれば、基本的なルールに則ってデータを取得できるし、データに近い構造を持っているので、書いたコードがインターフェースになる。GraphQLの中にも流派があり、考えなくてはいけないこともありますが、REST APIと比べるとそういったものは少ないです。例えば一つのデータ構造に関連性のあるデータを1個加えた際、全てのAPIに追加するのは大変ですよね。でも、GraphQLならリソースの中に関連性を定義すれば終わります。
加えて必要な分だけデータを引くことができるし、関連性のないデータは取得しないようにすることもできる。コードの自動生成もしてくれるので、実装が楽になります。スケーラビリティもあると思ったので、将来のことも考えてGraphQLを採用しました。
──効率性と将来性を考えてGraphQLを採用されたのですね。
末田:ライブラリについても言及すると、BFFの実装にはNestJSのGraphQLのモジュールを使用しています。コードを書いてデコレーターでスキーマ情報を定義し、自動的にスキーマを書き出すコードベースの方式を採用しています。
全体としては、BFFサーバーの中でgRPCクライアントを生成し、そこから各マイクロサービスにリクエストして、各フィールドに必要な情報を取得しています。
──なるほど。技術選定の際、NestJS以外の候補はありましたか?
名村:マイクロサービスに対するアクセスとGraphQLのインターフェースを両方カバーしているフレームワークは、NestJS以外にあまりないんですよ。
また、NestJSはJavaのようなDependency Injection(DI)の考えがあるので、DIの仕組みに乗っかっていれば、マイクロサービスが肥大化してもコード量をたくさん増やさなくてもいい。必要なサービスをGraphQLのリゾルバーで呼び出すことができると思ったので、NestJSを採用しました。
使用する上での制約もありますが、DIのレイヤーで依存関係を構築できたので、NestJSを採用して良かったと思っています。
──概念は少し掴みづらいようにも感じました。
末田:独特なフレームワークだとは思います。しかしNestJSが色々と動いてくれるので、使い勝手がいいんですよ。コンポーネントが揃っていて、各レイヤーに名前がついているので、柔軟なコードを書けます。加えてモジュールベースでDIのシステムも備わっていて、マイクロサービス単位や機能単位など、分離して開発できるので便利ですね。
メルカリShopsのアーキテクチャ全体像。メルカリのエンジニア情報ポータルサイト内「メルカリShops の技術スタックと、その選定理由」より引用
生産性は向上するものの、グラフ構造の設計には工夫が必要
──GraphQLを採用して感じたメリットをお聞かせください。
名村:APIレイヤーに対するディスカッションが減ったことですね。APIドキュメントを見ながら必要な情報を作るのではなく、GraphQLのコンソールでAPIを叩きながら見ることができます。また、コードジェネレーターを有効活用できているように思います。
市原:APIの設計をするに当たって、APIのデザインをスキーマベースで議論できるようになったのは嬉しいポイントですよね。
末田:フロントエンド目線だと、宣言的にUIを構築するReactとGraphQLのグラフ構造は相性が良く、実装を減らすことができるのは魅力ですね。フロントエンド主導でスキーマを定義したり実装したりもできるようにしているので、フロントエンドのキャッシュ構造と、GraphQLのレスポンスの型をある程度一致させられ、効率よくキャッシュできます。
UnionやInterface、継承にも対応していて、スキーマの表現力が高いのもメリットです。柔軟に定義ができる上に、同時にドキュメントも生成してくれるので、生産性の向上にも繋がります。
また、Apollo Clientならキャッシュについても自動でやってくれて、アプリケーションのコードから引き剥がせるのが嬉しいですね。
──反対にデメリットだと感じるところはありますか?
名村:データをどうグラフ構造に反映するかの設計はもう少し統率を取ってやったほうがいいかなと思っています。いかに効率的にリソースを取ってくるべきかみたいなところも、ある程度一貫性を持って表現しないと、グラフ構造の表現方法が箇所によって異なってしまいます。
また、APIのエンドポイントが一つになってしまうため、どういった情報にアクセスしようとしたのかなど、詳細の情報が残らないんですよね。REST APIのように、アクセスログだけを見てトラッキングすることはできない。レスポンスをどうキャッシュにのせるかも工夫が必要です。
末田:スキーマ設計の部分で、リソースの関係性が明確になっていなかったり、重複したりしている部分が生まれているのは課題ですね。スキーマ設計については、事前にスタイルガイドを定義しておけばよかったなと感じています。
市原:バックエンドで考えているデータモデルと、画面のUIで必要なデータモデルは設計の方向性が異なるため、設計方針をしっかり議論しなくてはいけません。
──パフォーマンスの点では特に課題はないでしょうか。
市原:クエリによってパフォーマンスが悪くなってしまうことはあります。メルカリShopsでは、キャッシュを使ってもいいタイプのデータとそうでないデータがあり、前者にはキャッシュを一部使っています。
また、GraphQLサーバーにはDataLoaderといった仕組みがあり、クエリに書いたデータを取得するときに、直列でデータを取ってくるのではなく、バッチで取ってきてくれるんです。そういった仕組みを利用して、無駄な処理がないように工夫はしています。
末田:ちなみに、クエリのパフォーマンスをチェックする際は、Complexityといった概念を利用しています。フィールドの数やネストされているフィールドの数をベースに計算される値で、悪意のあるユーザーが複雑なクエリを投げたとしても、そこに閾値を設けることで障害を防げるようにはなっています。パフォーマンスよりもセキュリティに近い話になるかもしれないですね。
──GraphQLを使っていて過去にトラブルになったことはありますか?
市原:GraphQLに限ったことではないのですが、APIサーバーをリリースするにあたって、後方互換性を保たないと、依存しているサービスやクライアントが想定した挙動になりませんよね。
そのため、CIでGraphQLのスキーマをチェックしていて、後方互換性が保たれていなかったらCIは通らないようにしています。
メルカリShopsのプロダクトコンセプト
マイクロサービスの集約にはGraphQLが向いている
──GraphQLに向いているもの、反対に向いていないのはどういったサービスだと思いますか?
名村:バックエンドの構成に依存すると思うのですが、マイクロサービスでバックエンドのサービスを細分化し、データを分割する場合はGraphQLのようなアグリゲーションのレイヤーがいいのではないでしょうか。
バックエンドがシンプルに完結する場合は、RESTでもいいと思います。RESTを選ぶメリットももちろんありますし、バックエンドやAPIのレイヤーに対する要件によって、向き不向きは変わってきます。
メルカリShopsと似たような要件のサービスを作るのであれば、GraphQL1択ですね。
──なるほど。最後に今後の展望について、一言でお願いします!
末田:最初にお話したスキーマの整合性については、これから整理していきたいですね。データストアと繋げているところとマイクロサービスと繋げている部分があって、テストがしづらいのも課題だと思っているので、そこは解決したいです。
また、フロントエンド面ではクエリを取得して分配していく形が主なんですが、フラグメントを利用して再利用性や可読性をあげられると思うので、そこも進めていきたいと思っています。
──ありがとうございます。まだまだ改善の余地があり、取り組みがいがありそうですね。名村さん、末田さん、市原さん、本日はお時間をいただきありがとうございました!