Edited at

Laravel で GraphQL やるなら、 Lighthouse が良い

Laravel と組み合わせて、 GraphQL サーバーを本番運用しています。

Lighthouse というライブラリを使うと、手軽に構築することができました。そのときに溜めた知見です。


結論


  • Laravel + GraphQL は、 Laravel の柔軟性と GraphQL の堅牢性がいい感じにマッチしている

  • Laravel で GraphQL やるなら、 Lighthouse 良い

  • Schema をダンプしてチームでシェアするといい感じになる


GraphQL を現実世界で使う

GraphQL を使う動機としては、下記があると思います。


  • 型安全な Web API を作りたい

  • 入出力を明確にしたい

  • フロントとサーバー間の仕様書を、動いているコードから明確に作りたい

ただ、障壁となってくるのが


  • 既存のサービスのロジックを使いまわしたい

  • 徐々に移行したい。三ヶ月かかります、みたいなのはきつい

ということがあり、既存で使っているフレームワークと組み合わせて GraphQL を部分的に導入していく、ということは多いのではないかと思います。新規開発の API は GraphQL にしよう、など。

そのような時、開発リソースが限られている場合が多いので、 GraphQL サーバーを手軽に作れることは大事だと思っています。

Laravel 等の LL 系言語のフレームワークを使う理由として、低い学習コストで速く開発できるという理由も多分にあるでしょう。チームの文化としても、時間をかけて正確にやるより、多少バグがあってもいいから速く機能をユーザーに届けるというのが大事にされているかもしれません。その場合、型定義を沢山書く手間がかかる、と思ってしまうと、中々普及しないですよね。

Lighthouse は、型定義の恩恵を受けつつ、最小限の労力で GraphQL サーバーが作れます。他の GraphQL ライブラリと比較していきましょう。


Laravel で GraphQL を使う時の選択肢


Lighthouse の特徴

他2つのライブラリと比べて、非常にシンプルに、 GraphQL っぽく書けるのが特徴。 Node.js の Apollo-Server っぽい。

比較すると分かりやすい。


型定義がシンプルである

laravel-graphql

class UserType extends GraphQLType

{
protected $attributes = [
'name' => 'User',
'description' => 'A user'
];

public function fields()
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The id of the user'
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user'
],
'posts' => [
'type' => Type::listOf(GraphQL::type('Post')),
'description' => 'The posts of user'
]
];
}

protected function resolveEmailField($root, $args)
{
return strtolower($root->email);
}
}

lighthouse

type User {

# The id of the user
id: ID!

# The email of user
email: String

# The posts of user
posts: [Post!] @hasMany
}

圧倒的にシンプルになったのが、おわかりでしょうか。

このように、 GraphQL ファイルを読み込んで使う、という設計になっています。

ちなみに、 resolveEmailField のような特定の Attribute に対する Resolver は無くて、マジックメソッドを使って解決するっぽいです。上の例だと、 User#getEmailAttribute みたいな。


クエリ定義が GraphQL で書ける

laravel-graphql


config/graphql.php

'schemas' => [

'default' => [
'query' => [
'users' => 'App\GraphQL\Query\UsersQuery'
],
'mutation' => [
'updateUserEmail' => 'App\GraphQL\Query\UpdateUserEmailMutation'
]
],
],

lighthouse

type Query {

users: [User] @field(resolver: "App\\GraphQL\\User@resolve")
}
type Mutation {
updateUserEmail(email: String): User @field(resolver: "App\\GraphQL\\Query\\UpdateUserEmailMutation")
}

laravel-graphql は、 config もしくは GraphQL::addSchema('users', ... ファサードを使うかの二択で、 Lighthouse は graphql ファイルで指定する、となっています。

個人的に、 config に書くのもファサードもちょっと違うかなー、できれば GraphQL で定義したいよなー、と思っています。

Lighthouse では、他の graphql ファイルを読み込むこともできるので、分割も簡単、またクエリが何を受け取って何を返すのかが、ひと目でわかります。


Relation 取得時に、 N+1 問題を回避してくれる

GraphQL の Relation は頭が痛い問題で、クエリを解析して必要に応じて Eager Loading をする、ということが多いのではないでしょうか。確か GraphQL-Rails も現状そうで、将来的に改善する、となっていたはず。

laravel-graphql も同じで、こちらのドキュメントには、実際のコード例が示されています。

https://github.com/Folkloreatelier/laravel-graphql/blob/develop/docs/advanced.md#eager-loading-relationships

$fields = $info->getFieldSelection($depth = 3);

$users = User::query();

foreach ($fields as $field => $keys) {
if ($field === 'profile') {
$users->with('profile');
}

if ($field === 'posts') {
$users->with('posts');
}
}

うん。これクエリ作る度にやるの?GraphQL使う意味が・・・

Lighthouse では、これを自動的に行なってくれます。 @hasMany ディレクティブを指定すると、自動的に Relation を解決して、 N+1 問題を解決したクエリを発行してくれます。すごい。

コードを追うと、 Union クエリを使っているみたいですね。実際、SQL のログでもそうなってました。

https://github.com/nuwave/lighthouse/blob/15e6681c0bf54c27641223c17d577040e2f1d4fa/src/Support/DataLoader/QueryBuilder.php#L90-L113


schema.graphql の生成

Lighthouse は、 php artisan lighthouse:print-schema というコマンドが用意されており、これで Schema が出力できます。

GraphDoc 等のツールを使い、これを活きた HTML のドキュメントに出力するといい感じです。

更に、CI で、このようなドキュメントを生成し、 S3 で静的サイトとして保管し、フロントエンドエンジニアに共有すると、イケてるフロントエンド環境が出来上がります。

laravel-graphql には、現時点で Schema を Dump するコマンドは無いようです。


開発が活発である

こちらの laravel-graphql の Issue では、3ヶ月近く更新されてないから、 Lighthouse 使おうぜ、という論調になっています。私もこのスレッドを見て移行しました。(プロジェクトの後半だったから大変だったw)

https://github.com/Folkloreatelier/laravel-graphql/issues/270#issuecomment-397456932

この記事を書いている時点(2018/08/11)では、 Lighthouse は 1日前にコミットされており、 Issue にもすぐ返信が返ってくることから、今後にも期待できそう。

例えば、私が投稿した Issue にもちゃんと返信くれて、今後取り入れたいと言ってくれています。これには感動しました。


I will leave this issue open for now as the basis for discussion around how we can apply Policies to specific model instances. Would love to get some more feedback/hear a bunch more use cases.


https://github.com/nuwave/lighthouse/issues/244

今はスター数があまり多くないので、注目度は低いかもしれませんが。

[追記]

この記事を書いた8ヶ月後(2019/03/21)でも、2日前にコミットされ、かつ ver 3 が Beta に入っているなど、依然として活発です。

調べてみたら、作っているのは Nuwave というアメリカの会社で、 Laravel や GraphQL 周りの受託開発をしている会社のようです。どうりで高いコードのクオリティ、活発な開発、読みやすいドキュメント、親切なコミュニティが提供できているわけですね。


ドキュメントが充実している

laravel-graphql は、ドキュメントが割と簡素です。 ドキュメントという意味では、 Readme.md 及び docs/advanced.md の実質2枚です。

Lighthouse は、専用のリポジトリがあり、かなり読みやすいです。

また、有志による How do I... という Issue があり、こんな時どうする?という情報が集まっています。これにはかなり助かります。

https://github.com/nuwave/lighthouse/issues/106

ただ、ドキュメントへの取り込みが追いついていないようで、隠れた機能がけっこうあるものの、ドキュメントに反映されていない感があります。

独自 Directive を作りたい場合などは、既存のソースコードを読むことになります。とはいっても読みやすく書かれているので、そんなに苦労することはありません。


まとめ

ディレクティブでごりごりやる感じは、もしかしたらロックアップ感があって抵抗がある人もいるかもしれませんが、 Apollo や GraphQL-Ruby と似た形になるので、私は気にしていません。

薄いフレームワークが好きな方や PHP 風にクエリ定義したい方は、もしかしたら laravel-graphql の方が好きかもしれませんが、まあ Laravel 使っている時点でロックアップ感あるし・・・というのはありますよね。OSSですし。

まだまだスター数は少ないですが、注目していきたいライブラリです。

個人的に Lighthouse がめっちゃ好きなので、べた褒めしてしまいましたが、もっと多くの方に使ってもらえたら幸いです。

もし需要があれば、チュートリアルとか作ろうかなー。


ついで

Laravel + GraphQL で開発してみたい方がいれば、 https://lighthouse-php.com/pages/users.html に載っている会社を紹介できるので、お声掛けくださいー