リポジトリ
https://github.com/gqlkit-repos/gqlkit-store
graphql client storeプラグインです。
これを、各フレームワークのcontextに突っ込んでやろうという魂胆
reduxはreact、vuexはvueという感じですが
クロスフレームワークでかつgraphqlの為の
client&storeというのを作ってみました。
nuxt.jsでは検証済
resolverファーストアーキテクチャのgraphql client設計
.
├── index.js
└── resolvers
├── Mutation
│ ├── createStaff.js
│ └── deleteStaff.js
├── Query
│ └── staffs.js
├── cache.js
└── client.js
たったのこれだけ
と言いたいところですが
これを例えば、nuxtで使うなら、nuxtのpluginsディレクトリに突っ込むので
一見、まぁまぁな規模感あるように見えます。
というか、そもそもなのですが
クライアント側のvuexとかreduxだとかのストアが抱えている課題って
もうストアはストアで別個のフレームワークと捉えて設計した方が良いレベルではないかと思うので
これはなんか正解路線なのでは?と思っています。
ちなみにQueryとMutation以下のjsファイルはデモ用のファイルです。
queryのコードの見た目も、mutationのコードの見た目もほぼ同じ
上のディレクトリツリーのstaffs.jsはこんな感じ
import client from '../client'
import cache from '../cache'
import gql from 'graphql-tag'
export const demand = gql`
query {
staffs {
id
name
age
sex
}
}
`
export default async variables => {
let re
if (!cache.has('staffs').value()) {
const { staffs } = await client.req(demand)
cache.set('staffs', staffs).write()
re = staffs
} else {
re = cache.get('staffs').value()
}
return re
}
mutationにあたるcreateStaff.jsはこんな感じ
mutationはrefetchしたいモジュールファイルをrefetchという名前で読み込みます。
そんでもってqueryでもmutationでも
クライアントツールはclient.req(demand, variables)(variablesが必要ない場合は省略)
import client from '../client'
import cache from '../cache'
import gql from 'graphql-tag'
import refetch from "../Query/staffs";
export const demand = gql`
mutation($name: String!, $age: Int!, $sex: String!) {
createStaff(name: $name, age: $age, sex: $sex) {
id
name
age
sex
}
}
`
export default async variables => {
const res = await client.req(demand, variables)
const staff = res.createStaff
const staffs = cache.get('staffs').value()
staffs.push(staff)
cache.set('staffs', staffs).write()
return refetch()
}
クライアントツールは21行のコードでqueryもmutationもOK!
fetchで書くとこんなシンプルになるとは正直、驚きました。
ただ、今後subscription導入のことも考えると
もうちょっと大きいファイルになる可能性はあります。
まだ、subscriptionは未経験なので。。。
const GQLKIT_SERVER_END_POINT = process.env.GQLKIT_SERVER_END_POINT || 'http://localhost:4000/query'
const method = 'POST'
const headers = {
'Content-Type': 'application/json',
Accept: 'application/json'
}
export default {
async req(demand, variables) {
const res = await fetch(GQLKIT_SERVER_END_POINT, {
method,
headers,
body: JSON.stringify({
query: demand.loc.source.body,
variables
})
})
const { data } = await res.json()
return data
}
}
キャッシュはどうするか?
キャッシュは完全切り分けの方針です。
Apolloのapiはキャッシュと密結合ですが
resolverファーストなgraphql clientアーキテクチャでは
キャッシュは完全別扱いにして
好きなin memory dbを使えば良いという方針で考えます。
resolverを書いて解決すれば良いじゃないか!という方針です。
僕は今のところlowdbを使っています。
ここはお好みで好きなもの使ってよしです。
import low from 'lowdb'
import Memory from 'lowdb/adapters/Memory'
const cache = low(new Memory())
cache.defaults({}).write()
export default cache
まとめ
apolloがなぜ、あれほどまでに複雑なアーキテクチャを取っているのか
僕の知らない何らかの理由でああなっている可能性も大なので
これがベストアンサーだなんて到底言い切る事はできないです。
これはあくまで、1アイデアというものです。
あと、jsのオブジェクトをgormっぽい書き方で読み書き出来るような
ツールをご存知の方おられましたら教えて頂けると嬉しいです。
サーバーとクライアントで違うノウハウというのを最小限に抑えたい
ということで
graphqlサーバーのアーキテクチャを真似たプラグインを作ってみたという話でした。