React Relay
GraphQLクライアントです。
https://relay.dev/
他に代表的なものとしてApollo clientがありますが現状Relayを素振り中です。
Relayを選択したい(と今のところ思っている)理由
React Relayのフレームワークに乗っかった方が、生産性が高く、メンテするコード量も少なりそうなので、だったら一度全力で乗っかってみようというフェーズです。
Apollo clientと比較する点の一つとして、RelayではGraphQLサーバ側の仕様に制約を設けています。
- 識別子(ID)の定義の仕方
- コネクション(ページ情報)の定義の仕方 (cursor connection)
- mutation 引数の定義の仕方
- などなど...
制約があるというとネガティブな印象もありますが、これらの制約があるおかげで、オブジェクトの再取得を効率化できたり、ページネーション周りで必要なスキーマ定義が整備され、設計・実装時に考えることが(おそらく)減ります。
識別子に関しては、このRelayの制約がそのままベストプラクティスとしてGraphQL公式に書かれたりします。
Lighthouse: LaravelベースのGraphQLサーバー
Lighthouseは、Laravelに組み込めるGraphQLサーバーです。
https://lighthouse-php.com/
Eloquentと統合可能なディレクティブが多数用意されていて、CRUDだけであればコード量も少なく、GraphQLエンドポイントを構築できそうです。
(リレーション定義等は必要なので、全く書かなくて良いわけではありませんでした。)
このディレクティブが非常に楽チンで、@hasManyとか書いておくだけで、Relayのコネクション定義を満たしたスキーマを生成してくれます。
例えばこのようなスキーマを書いたとします。
type User @model {
id: ID! @globalId
name: String!
projects: [Project!] @hasMany(type: "connection", relation: "projects")
}
type Project @model {
id: ID! @globalId
name: String!
status: ProjectStatus!
createdAt: DateTime! @rename(attribute: created_at)
updatedAt: DateTime! @rename(attribute: created_at)
}
Lighthouse側でディレクティブを解釈した結果は以下となります。(関連部分の抜粋)
type User implements Node {
id: ID!
name: String!
ownerName: String!
iconUrl: String
projects(
first: Int!
after: String
): ProjectConnection
}
interface Node {
id: ID!
}
type ProjectConnection {
pageInfo: PageInfo!
edges: [ProjectEdge]
}
type ProjectEdge {
node: Project
cursor: String!
}
type Project implements Node {
id: ID!
name: String!
status: ProjectStatus!
createdAt: DateTime!
updatedAt: DateTime!
}
Relayの制約も既に考慮されていて、Laravelの資産も使えて、効率的にGraphQLエンドポイントが構築できます!
しかし、cursor connection には対応していなかった
これが非常に残念かつ致命的と思われるところで、Lighthouseのドキュメントに以下のような記述がありました。
Lighthouse does not support actual cursor-based pagination as of now, see https://github.com/nuwave/lighthouse/issues/311 for details. Under the hood, the "cursor" is decoded into a page offset.
以下のイシューで議論されているようですが、どうも対応予定がなさそうな感じです。
https://github.com/nuwave/lighthouse/issues/311
connectionの挙動確認
各種スキーマなど
RDBに以下のusersテーブルとレコードが格納されているとします。
`users` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
)
(* 名称は、php fakerでランダム生成したものです。)
id | name |
---|---|
1 | 若松 修平 |
2 | 藤本 千代 |
3 | 廣川 舞 |
4 | 宮沢 浩 |
5 | 小泉 涼平 |
そして、以下のGraphQLスキーマがあるとします。
(model 等のディレクティブはLighthouse固有です。)
type User @model {
id: ID! @globalId
name: String!
}
type Query {
users: [User!]! @paginate(type: "connection")
}
cursor connection 前提のクエリ
まずは以下のクエリを投げてみます。
(まだカーソルは指定していません。)
query users {
users(first: 3) {
pageInfo {
count
currentPage
total
}
edges {
cursor
node {
id
... on User {
name
}
}
}
}
}
その結果が以下です。
{
"data": {
"users": {
"pageInfo": {
"count": 3,
"currentPage": 1,
"total": 10
},
"edges": [
{
"cursor": "MQ==",
"node": {
"id": "VXNlcjox",
"name": "若松 修平"
}
},
{
"cursor": "Mg==",
"node": {
"id": "VXNlcjoy",
"name": "藤本 千代"
}
},
{
"cursor": "Mw==",
"node": {
"id": "VXNlcjoz",
"name": "廣川 舞"
}
}
]
}
}
}
2番目のユーザ(id="VXNlcjoy")について返却されたcursorを指定して、今度はこのcursor移行のデータを要求するクエリを投げてみます。
query users {
users(first: 3 after: "Mg==") {
pageInfo {
count
currentPage
total
}
edges {
cursor
node {
id
... on User {
name
}
}
}
}
}
この結果は、先ほどのクエリと全く変化がありませんでした。
つまり、afterにcursorを指定しても、それが効いていないことになります。
期待動作としては、1番目のユーザ(id="VXNlcjox")が除外されて、2,3,4番目のユーザが返却される、というものでしたが、確かにcursor connectionに対応されていないようです。
非Relayならpaginatorを使用できる
Relayの場合は cursor connectionが必須ですが、そうでない場合(Apollo Clientなどを使用する場合)は、この制約はありません。
Lighthouseでは、cursor connectionの他に、paginatorもサポートしています。
これは、page番号を指定する方式でのページング機能です。
type Query {
users: [User!]! @paginate(type: "paginator")
}
以下のようなクエリが書けて、こちらは期待通りに動作しました。
query users {
users(first: 3 page: 2) {
paginatorInfo {
count
currentPage
total
currentPage
}
data {
id
name
}
}
}
Relay対応の楽チンGraphQLサーバをもとめて...
Laravelという資産も生かせて、かつ簡単にGraphQLサーバ建てられそうで良いなあと思っていましたが、Relay前提では厳しそうです。惜しい。
そして今度は harura がいいかなと見始めています。