Apolloのチュートリアルを読む
チュートリアルの目的
productionでApolloを使う確信を得てもらうために、現実的な例をつくっていく
認証、ページネーション、テストを含むサンプルアプリケーション
所要時間は1時間
前提
APIからデータをfetchしたことがある
Apollo ClientではUI構築にReactは必須ではないが、よく使われる
開発環境
graph APIを管理するためのクラウドサービス
なんでいる?kadoyau.icon
練習用リポジトリをcloneすると、完成品と完成前が入っている
チュートリアルの全体の流れ
Server
スキーマを決める
Query Type
Mutation Type
Data Sourceを作成する
既存のREST APIを叩いて、スキーマの型を返す
実装上のプラクティス:型を返すためのReducerを定義してそれを内部で使うようにすると、型の情報を変更したときの変更箇所がそこだけになるので便利
Resolverを書く
Schemaで定義したQuery Typeのresolverを書く
Data Sourceを使って取得したデータを返す
Data Sourceを登録しておくと、resolverでData sourceを使うことができる
例えば外部のREST APIを使ってレスポンスを作るとかができる(チュートリアルではSpace XのAPIをたたいている)
AuthenticationのためにDatabaseを使うとか
Schemaで定義したMutation Typeのresolverを書く
(Optional)SchemaをApolloのサービスに登録する
登録するとメトリクスが見れたりするのでチューニングに便利(有償)
Client
React componentでQuery/Mutation
同様にSchema(serverのschemaをextendする形)+Resolverを書く
local state/virtual field
local cacheを更新する方法
1. Build a schema
スキーマを定義する
スキーマはそれを使うClientのneedsにあわせて設計されたものがベスト
スキーマはデータ型とリレーションのblueprintのようなもの。スゴイツヨイ型がつけられている
他にも次を定義する
queryで取得するデータ
mutationでupdateできるデータ
Schemaがbackendとfrondの間の中間層を提供する
Schema First Developmentをチームが実践して、API開発を始める前にスキーマに合意するのがおすすめ
アプリに必要なデータについて考えると、必要なのは
来る🚀の発射時刻のすべてを取得できる
IDによる特定の発射の取得できる
ユーザログイン
ログインしていたら
ローンチのtripを予約できること
キャンセルできること
Query Type
クエリの型を定義する
Object & Scalar Type
Object TypeはScalar Type or Object Typeで構成するユーザが決める型定義
Scalar型は、全てのフィールドが解決できるグラフのleafのようなもの
組み込みの型以外にも自分で作成できる
レスポンス型をinterfaceにしたりするかもしれない
Mutation Type
データ変更に使う
Query Typeに似てる形式
レスポンス
Apolloのキャッシュを自動で更新するために、更新したデータは返すのが良い
2. Hook up your data sources
要約
自前のREST APIをGraph APIに紐付ける
Apollo的には従来のRESTはdata sourceという扱いになる
データソースをGraphQL APIに紐づけたい
GraphQL APIはREST/database/gRPC services/businness logicの上にあるレイヤーなのでフレキシブルにつくられている
これらのサービスをGraphに紐付けるためには、data source APIを使う
Apollo data sourceはデータをfetchしたりキャッシュしたりdeduplicationしたりするクラスをカプセル化している REST APIとSQL databaseをApollo Serverに紐付ける例を見ていく
Connect a REST API
REST API向けのfetchに責任を持つRESTDataSourceが使えるようになる
RESTDataSourceはin-memory cacheを設定する。追加の設定なしにRESTリソースのレスポンスをcacheする
partial query cachingと呼んでいる。既存のREST APIが提供するがキャッシュを再利用できる
?kadoyau.icon
Write data fetching methods:自前のREST APIの結果をGraphQLのレスポンスとして乗せる方法
Launch型を返すというQuery Typeをスキーマに書いたので、これに合わせてレスポンスをつくりたい Space XのAPIを叩いて、返ってきた結果をlaunchReducerに食わせて、Launch型のレスポンスをつくる
自前のREST APIからgraph APIにdecouppleするときにはこういうアプローチがおすすめ
code:js
class LaunchAPI extends RESTDataSource {
async getAllLaunches() {} // REST APIをcallして結果を中でlaunchReducerにわたして、戻り値を返す
launchReducer(launch) {} // REST APIの結果をスキーマ(Launch型)の形式にmapする
}
Connect a database
REST APIはread-onlyなのでデータのsave(とuser dataの取得)をするためにgraph APIをdatabaseと連携させる
UserAPIはすでに作成済
ApolloはまだSQL data sourceをサポートしていないので、DataSourceを継承して自前で実装する必要がある
user.jsの中身
initialize()
オプションを渡したいときに使う
例ではgraph APIのcontextを取得している
graph APIのcontexは、GraphQLのリクエストにおけるすべてのresolverが共有しているobject
詳しい説明は次の章
contextはuser情報を保存するのに便利
REST data sourceはビルトインcacheを持ってるけど、一般的にはもっていない。cache primitivesを使って構築することはできる。
3. Write your graph's resolvers
ここまで、graph APIは使いやすくなかった。スキーマは(playgroundで)みられたけど、実際にクエリを投げることはできなかった(APIから取得する部分をかいてなかったので)
schemaとdata sourceをつくったので、graph APIのresolver 関数経由でdata sourceを呼んでビジネスロジックを読んだりデータをfetch/updateしよう
What is a resolver?
ResolverはGraphQLのoperation(query mutation, subscription)をデータに変換する手順 /icons/重要.icon resolverのシグネチャ:fieldName: (parent, args, context, info) => data
parent:親の型のrelverから返される結果を含むObject
args:fieldに渡されるargumentを含んだobject
context:GraphQLのoperationにおけるすべてのresolverで共有されるObject。認証情報のようなリクエストごとに必要な情報をもたせるのと、data sourceにアクセスするのに使う。
info:operationの実行状態を表す情報。応用的なケースにのみ使うべき。
この情報はResolverを書く際に頻繁に参照するkadoyau.icon
resolverでは、contextにアクセスしてこのAPIを呼ぶ
resolver -(context)-> REST API(data sources)
Connecting resolvers to Apollo Server
ApolloServerにresolverを登録すると、ApolloServerは自動的にdataSourcesをresolverのcontextに追加する
Write Query resolvers
エンドポイントごとにdata sourceのAPIを呼ぶだけ
ベストプラクティス
薄く保つ(ロジックはdata sourceに書く)
Queryに宣言的な名前をつけるのはApollo developer toolingで見つけやすくするのに重要
Paginated queries
返される要素が多くなりすぎると遅くなったりして不都合なので、一度にfetchするデータに上限を設定したい
paginationでchunkに分割する。Cursor based paginationがナンバリングされたページにはおすすめ。 理由:itemをskipしたり重複して表示するのを防ぐ
実際に実装する。このあたりからだんだん複雑になるkadoyau.icon
Write resolvers on types
QueryやMutationに限らず、スキーマのどの型に対してもresolverを書くことができる
Schemaで定義したMission typeに対するresolverをresolver.jsに書く例
逆に、すべての型に対してresolverを書いていないのにQueryはうまく動く
GraphQLはデフォルトのresolverを持っている
親のオブジェクトが同じ名前のプロパティを持っていたら、fieldのresolverを書く必要はない
Authenticate users
1. ApolloServerのインスタンスにおけるcontext functionは、GraphQLのオペレーションがAPIを叩くときに毎回reoquestオブジェクト付きて呼ばれる
2. context functiionの中でユーザをAuthenticateする
3. ユーザがAuthenticateされたら、ユーザをobjectにattachしてcontext functionから返す。これによりdata sourcesとresolverから渡されたユーザ情報を読むことができる。よってユーザがデータにアクセスできるかどうかをauthorizeできる。
実装
Authorizationヘッダーをみて有効ならemailからユーザを引いてくる
Authorizationヘッダーは後述のlogin mutationでつける
Write Mutation resolvers
Schemaに定義したMutationに対応するresolverの実装をする
clientから渡されたemailをもとにユーザを引き、あった場合はtokenを返す
注意:サンプルは簡易実装なのでトークンはemailをbase64でエンコードしたものを返している Run mutations in the playground
flontからMutationを利用する
mutationキーワードを使う
Productionではテストを自動化したほうがよい
4. Run your graph in production
1-3章でGraphを構築することができるようになった
Apollo serverをつかってGraphQL APIを作成する
resolverでRESTやSQLのdata sourceを利用すること
SQLは自分では書いてないけどねkadoyau.icon
GrpahQLのqueryをPlayground経由で送ること
この章ではZEITにデプロイする
Publish your schema to Engine
schemaの変更をtrackしたりVSCodeのでいい感じにするために、SchemaをApollo Engineにアップロードする Get an Engine API key
アクセスしてcreate serviceする
キーを.envに転記する
pushする:npx apollo service:push --endpoint=http://localhost:4000
https://gyazo.com/9371be8aff6ed0ae22c65a5a6e68c671
次回以降のpush前にSchemaが破壊されていないことを確認できる
npx apollo service:check --endpoint=http://localhost:4000
nowにデプロイする
手順通りではうまく行かなかった
nowは2018年11月に2.0になっている
関係しそうなissue
5. Connect your API to a client
いままではServer sideの話だった。ここからClient sideにうつる
UIレイヤーは何でも良いけどReactの例
Thanks to its intelligent cache, Apollo Client offers a single source of truth for all of the local and remote data in your application.
次のことを説明する
authentication
pagination
ワークフロー最適化のtips
Set up your development environment
apollo-client: A complete data management solution with an intelligent cache. In this tutorial, we will be using the Apollo Client 3.0 preview since it includes local state management capabilities and sets your cache up for you. react-apollo: The view layer integration for React that exports components such as Query and Mutation graphql-tag: The tag function gql that we use to wrap our query strings in order to parse them into an AST sereverでも使ったkadoyau.icon
Configure Apollo VSCode
Create an Apollo Client
リクエスト先のドメインを指定してClientのインスタンスを作成する
4章のとおり、Zeit nowのデプロイがうまくいかなかったのでローカルに向けておこなう
Clientでクエリを書く
code:js
const cache = new InMemoryCache();
const link = new HttpLink({
})
const client = new ApolloClient({
cache,
link
})
client
.query({
query: gql`
query GetLaunch {
launch(id: 56) {
id
mission {
name
}
}
}
`
})
.then(result => console.log(result));
Connect your client to React
ApolloProviderにclientを渡すっとその子要素ではcontextからclientがつかえる
ここのコード片をコピペしても動かないので無視して先に進もう
インクリメンタルに実行できないチュートリアルになってしまっているkadoyau.icon
6. Fetch data with queries
The Query component
react-apolloのQuery componentはGraphQLのクエリをfetchして結果を返す。UIはこの結果を表示できる 5章ではclientを作成して
クエリからデータをfetchしてロードしてUIにデータをわたすために、render propパターンを使っている
render propパターンを使うと、「Reactに何をrenderしたいか通知する」ための関数をQuery componentの子に追加できる
render prop functionに渡されたオブジェクトからerror, loading, dataにアクセスすることができる
実際に見ていく
Fetchng a list
Build a paginated list
Apollo Clientはpaginationを追加するhelperを提供している
使うためには、Queryのrender prop functionにfetchMorefunctionを渡せばOK
fetchMoreは次を受け取る
variables
updateQuery
cacheにあるlaunchesのlistを更新する方法を教える
前回の結果と今回の結果を混ぜている
ここ難しいのでドキュメントの詳細を掘り下げる必要ありkadoyau.icon
Fetching a single launch
langesではなく単体のlanchのページを作る
routerからpropが渡される
Using fragments to share code
launchとlanchに同じフィールドがたくさんある
GraphQLのoperationに同じフィールドが含まれているならそのフィールドはfragmentをつかうと共有できる
fragment Hoge on Type {}を宣言する
typeはSchemaにしていしてあるもの
fragmentの名前Hogeはなんでもいい
Customizing the fetch policy
cacheしてほしくないときにつかう
定常的に変わるdataとか
ポリシー
cache-first
デフォルトこれ
network requestを送る前に、cacheに結果があるか確認する
network-only
常にgraph APIに問い合わせる
7. Update data with mutations
graph APIからのdataを更新するのは単純にfunctionを呼ぶだけ。さらに、殆どの場合Apollo Client cacheは自動で更新される
この章ではMutation componentをつかってユーザをログインすることを学ぶ
What is a Mutation component?
GraphQLのmutationを実行するcomponent
Query componentとの違いはrender prop functionがmutate functionであること。呼ばれるとmutationをtriggerする
第二引数はmutationの戻り値で、loadingとerrorのstateをもつobject
Update data with Mutation
Mutation componentに渡した、mutationに含まれるGraphQLのmutate functionをrender propsにわたす login情報をsessionの間永続化したいので、login tokenをlocalStorageに保存する
Mutation componentのonCompleted handlerを使って行う
onCompletedにはMutationした結果がわたされる
Expose Apollo Client with ApolloConsumer
react-apolloのhelper componentが提供していないメソッドを使うために、ApolloClientのインスタンスに直接アクセスしたいときがある ApolloConsumer componentを使うと、clientにaccessできる
render prop functionでclient instanceを受け取る
clientをつかってlocal data(ログイン情報)をApolloのcacheに直接書き込みしている
local dataのやりとりは次章のlocal state managimentで詳説する
Attach authorization headers to the request
Playgroundでやっていたときは、userをauthorizeするためにauthorization headerにtokenをつけてGraphQLをリクエストした。これをコードでもやる
8. Manage local state
Apolloのcacheの中にlocal dataを保存でき、GrauhQLでremote dataと一緒にQueryを利用できる
Apollo cacheを管理するためにはReduxのような状態管理ライブラリは非推奨。Aopllo自体が管理することでcacheがsingle source of truthになる
すでにこのチュートリアルで行ったremote dataのmanageと似た方法で管理できる
client schemaとresolverをlocal dataに対して書いて、GraphpQLでqueryを書く。違いは@clientディレクティブをつけるかどうか
Write a local schema
serverのdata modelの定義と同様にclientでlocal schemaを書く
client側のresolver.jsにスキーマを書いていく
extendキーワードを使ってServerのSchemaの型を拡張できる
localでのみつかうfieldを加える
Initialize the store
clientのschemaができたので、Query typeで指定したものに関してApolloのcacheを初期化する(あっためる)
Queryはcomponentがmountされるとすぐ実行されるので、初期化しておかないとerrorになる
Query local data
React componentからlocal dataにqueryする方法
cacheの読み込みは同期的に行われるので、loading stateを気にする必要はない
直後のcart.jsでもlocal stateしか使っていないのに場合分けをしているのはなぜ?kadoyau.icon
Lacalのqueryとremoteのqueryが混じってもOK
Adding virtual fields to server data
このfieldはclientにしか存在しない
serverのdataをlocal stateで補うのに便利
付け方
client schemaでvirtual fieldをつけたいtypeをextendする
clientのresolver APIはserverのそれといっしょ
virtual fieldへのqueryはclientしかないので、@client directiveをわすれないこと
Update local data
cacheのlocal dataへqueryを送ることを説明した。updateすることもできる
direct cache wriwes
単純なbooleanやstringをcacheに書き込むときに使う
client resolver
もっと複雑なケースに使う(例:リストから要素を追加・削除する)
Direct cache writes
client.writeData({ data: {} })にcacheに書き込みたいdataを渡すことで書き込む
Mutation componentのupdate()でもcache.writeDataを使って書き込める
mutationのあと、dataの再取得なしに手動でcacheを更新するのに使える
Local resolvers
local resolverのsignatureは(parent, args, context, info) => dataでserverのresolverと変わらない
違いはApollo cacheがcontextとして追加されていることだけ
resolverの中でcacheをdataの読み書きに使える
cartのitemをリストに加えたり削除したりする例
Congratulations! 🎉 You’ve officially made it to the end of the Apollo platform tutorial. In the final section, we’re going to recap what we just learned and give you guidance on what you should learn next.
これで終わりで最終章はない?kadoyau.icon
疑問
virtual fieldを定義する際に次の手順を行った
Client側でsereverのschemaをextendしてプロパティを増やし
それに対するQuery/Mutationをする際に、要素に@clientディレクティブでプロパティを指定し
そのプロパティに対するresolverを書いた
では、serverがApollo以外(例えばlighthouse)の場合にこれはどうすればよい?
serverのschemaがclientにないし、そもそももできない気がするkadoyau.icon