Apollo Server
自前で GraphQL API を実装する手段として、Node.js で動くApollo Server (opens in a new tab)がある
Apollo Server で BFF を構築する
Apollo Server で BFF を構築する方法は大きく 2 通り
- サービスに応じた Resolver を実装する
- 各サービスの API を Apollo Server で実装し、Apollo Federation で単一の GraphQL API として振る舞うようにする
- 各サービスを subgraph とし、リクエストを捌く router が BFF に相当する
サービスに応じた Resolver を実装する
- 各サービスは REST API を提供するようにして Apollo Server を BFF とする構成が基本
- Apollo Server が公式でサポートしている DataSource は
RESTDataSource
のみであるため - https://www.apollographql.com/docs/apollo-server/v2/data/data-sources/ (opens in a new tab)
- Apollo Server が公式でサポートしている DataSource は
- 必要なら任意の DataSource を実装できる(コミュニティによって作成・公開されているものもある)ため、柔軟な対応が可能
構築例: TypeScript + Apollo Server v4 で構築した GraphQL API を Serverless Framework で AWS Lambda へデプロイする
公式でAWS Lambda にデプロイする手順 (opens in a new tab)が案内されていたので、Apollo Odyssey の Lift-off で作ったサンプルアプリの API(の一部)を TypeScript で書き直してデプロイしてみた
- Serverless Framework で Lambda + Apollo Server の環境を作りたい (zenn.dev) (opens in a new tab)
- 成果物 https://github.com/qmotas/odyssey-ts-serverless (opens in a new tab)
スキーマ
type Query {
tracksForHome: [Track!]!
}
type Track {
id: ID!
title: String!
author: Author!
thumbnail: String
length: Int
modulesCount: Int
}
type Author {
id: ID!
name: String!
photo: String
}
ハマったとことか
- Serverless Framework のドキュメントの Getting Started (opens in a new tab)で、公式のテンプレート (opens in a new tab)より先にコミュニティのテンプレート(Example) (opens in a new tab)が案内されていて、最終的に採用した
aws-nodejs-typescript
に辿り着けなかった - esbuild を最新化すると serverless-esbuild がこけるので、esbuild のバージョンは 0.16.x にしておく必要がある(2023-02-17 時点、serverless-esbuild は
1.37.3
) - GraphQL/Apollo Server の勉強に使った Apollo Odyssey は Apollo Server v3 を使っていたので、v4 へのマイグレーションガイドをちゃんと確認しないとダメだった
- v3 は
DataSources
をApolloServer
のコンストラクタに渡してたけど v4 はサーバ起動時にContext
を作ってこの中で初期化する
- v3 は
graphql-code-generator init
が JSON のパースエラーでこけていて、これが解消できなかった- 詳細なログが出せなかったので原因箇所は不明(
package.json
くらいしか JSON のエラーが出そうな要素がないのでこれ?) codegen.ts
は生成できていたので、それ以外(package.json
の編集)は手作業で解決した
- 詳細なログが出せなかったので原因箇所は不明(
- Resolver Chain で親 Resolver の型がスキーマから graphql-codegen した型(子 Resolver も解決済みの最終的な形)になっていると type error が出るので、mappers というオプションで Resolver の処理中に扱う中間的な型を指定する必要がある
- 今回のスキーマだと
Track.author
はgetTracksForHome
の Resolver の結果時点ではauthorId
なので、Track
のauthor
をauthorId
に置き換えた型が必要TrackModel
型を作った- べた書きすると codegen の意味がないので graphql-codegen で生成した
Track
をベースにした Omit<Track, "author"> & { authorId: string }
- ドキュメント (opens in a new tab)の例に倣って
TrackModel
と命名したけどあまり適切だと思っていないIntermediateTrackModel
とかTrackUnderResolution
とか?
- べた書きすると codegen の意味がないので graphql-codegen で生成した
- 今回のスキーマだと
- 今回はシンプルなスキーマでも苦労したので、複雑なスキーマになると Resolver の型をちゃんと付けるのかなり大変なんじゃないかと思う
体験がよかったとことか
aws-nodejs-typescript
テンプレートを使ったら out-of-the-box で TypeScript が書けた- serverless-esbuild プラグインのおかげっぽい
- Serverless Framework そのものも serverless-offline プラグインも特に問題なくサッと動いてくれた
- 特にデプロイの手順が簡素で体験がよかった(AppSync の構築で使ったときも感じた)
- AppSync に比べるとローカルで実装/検証できるぶん(実装部分は Controllable なので)トラブルシュートしやすかった
- ただこれくらいシンプルなスキーマだと慣れちゃえば AppSync の方がサッと作れると思う
Apollo Federation で複数の GraphQL API を集約する
https://www.apollographql.com/docs/federation/ (opens in a new tab)
- 複数の subgraph と router で supergraph を構成する
- router が単一の GraphQL API として振る舞い、リクエストを捌く(BFF の立ち位置)
- クライアントからは 1 つの API として透過的に利用できる
- subgraph は Apollo Server で実装すると federation に必要なフィールドが自動で生えるため、複雑な実装は不要
- subgraph 間の解決に必要な entity の定義(スキーマで primary key を指定する)と専用のリゾルバ(
__resolveReference
)を実装する
- subgraph 間の解決に必要な entity の定義(スキーマで primary key を指定する)と専用のリゾルバ(
構築例
Apollo Odyssey(Apollo のチュートリアル)で Apollo Federation をさわってみる(zenn.dev) (opens in a new tab)