Slide 1

Slide 1 text

GraphQL に 入門 してみた 2024/03/27 第162回 PHP勉強会@東京

Slide 2

Slide 2 text

⾃⼰紹介 ■ ちひろ ■ Twitter: @chiroruxxxx ■ 会社: 株式会社モリサワ

Slide 3

Slide 3 text

話すこと/話さないこと ■ 話すこと – 自分がGraphQL in PHP の勉強をして学んだこと – サンプルコードは chiroruxx/lighthouse-sample に ■ 話さないこと – フロントエンド寄りの話 – 取得以外の処理

Slide 4

Slide 4 text

GraphQL

Slide 5

Slide 5 text

GraphQLの特徴 ■ クライアントが欲しい情報をクエリで送り、サーバがその情報を返す – RESTful API と比べてリクエスト回数を減らせる – 使用しないデータを取得・生成する必要がない ■ HTTPメソッドは基本的にPOSTのみ使用 ■ HTTPステータスコードは基本的に200のみ使用 ■ エンドポイントは基本的に1つのみ使用

Slide 6

Slide 6 text

SELECT id, name FROM users WHERE id = 1; エンジニア SQL データベース query { user(id: 1){ id , name, } } ブラウザ GraphQL Webサーバ

Slide 7

Slide 7 text

リクエストとレスポンス query { user(id: 1){ id, name, } } { "data" : { "user" : { "id" : "1", "name" : "Taro” } } }

Slide 8

Slide 8 text

リクエストとレスポンス query { user(id: 1) { id, name, post(id: 2) { id, title, } } } { "data" : { "user" : { "id" : "1", "name" : "Taro", "post" : { "id" : "2", "title" : "PHP勉強会に参加したよ" } } } }

Slide 9

Slide 9 text

バックエンドの関心事 ■ リクエストボディからクエリを解釈する ■ 必要なデータを取得・生成してレスポンスを返す ライブラリを使う エンジニアが作る

Slide 10

Slide 10 text

lighthouse ■ Laravelのプラグインのひとつ ■ Eloquentと密結合で使うことでほぼコードを書かなくて済む – 今回は理解のためにあえて使用しない

Slide 11

Slide 11 text

今回考える題材 ■ ブログをつくる – ユーザが複数の投稿を持つ – 投稿が複数のコメントを持つ ■ 一度に全部つくらずにインクリメンタルにつくる ■ lighthouseの設定方法は省略

Slide 12

Slide 12 text

ユーザを取得する

Slide 13

Slide 13 text

クエリ query { user(id: 1) { id, name, } }

Slide 14

Slide 14 text

メインのコード ■ App¥GraphQL¥Queries¥User を作成する /** @param array{"id": string} $args */ public function __invoke(null $_, array $args): GraphQLUser { $id = (int)$args['id'] ?? 0; return $this->service->findUser($id); } クエリの引数 (id: 1) DBからデータを取って インスタンスを返す

Slide 15

Slide 15 text

GraphQLUser final readonly class User { public function __construct( public int $id, public string $name, public string $email, ) { } }

Slide 16

Slide 16 text

かんたん!!

Slide 17

Slide 17 text

ユーザの投稿を取得する

Slide 18

Slide 18 text

クエリ query { user(id: 1) { id, name, posts { id, title, content, } } } posts { id, title, content, }

Slide 19

Slide 19 text

元のコード /** @param array{"id": string} $args */ public function __invoke(null $_, array $args): GraphQLUser { $id = (int)$args['id'] ?? 0; return $this->service->findUser($id); } postもJoinして 返せばよい・・︖

Slide 20

Slide 20 text

DBからの取り方 query { user(id: 1) { id, name, } } query { user(id: 1) { id, name, posts { id, title, content, } } } postsテーブルから 取る必要がない postsテーブルからも 取る必要がある クエリによってアクセスする テーブルが変わる Fields の機能を使う

Slide 21

Slide 21 text

Fields ■ 返り値のインスンタンスで追加の属性を取得するロジックを設定できる ■ そのデータが必要な時には呼ばれ、必要ない時には呼ばれない ■ App¥GraphQL¥Types¥User¥Posts クラスを作成してそこに書く

Slide 22

Slide 22 text

Fields public function __invoke(User $user): Collection { return $this->userService->getUserPosts($user); } 最初に書いた処理で 取得したユーザ Where(’user_id’, $user->id) でデータを取ってくる

Slide 23

Slide 23 text

ユーザの投稿のコメントを取得する

Slide 24

Slide 24 text

クエリ query { user(id: 1) { id, name, posts { id, title, content, comments { id, title, } } } } comments { id, title, }

Slide 25

Slide 25 text

メインのロジック ■ 先ほどと同じように Fields を使えば良い? ■ App¥GraphQL¥Types¥Post¥Comments public function __invoke(Post $post): Collection { return $this->service->getComments($post); }

Slide 26

Slide 26 text

ログを見てみると? データの数だけ SQLが発⾏されている (N+1問題)

Slide 27

Slide 27 text

ロジックの流れ 1. ユーザを取得する 2. ユーザの投稿を一括で取得する(投稿A, 投稿B, 投稿C) 3. 投稿Aのコメントを一括で取得する 4. 投稿Bのコメントを一括で取得する 5. 投稿Cのコメントを一括で取得する 投稿の数だけ実⾏される ■ コメントの取得を遅延させて一括で取る必要がある – BatchLoaderを使う

Slide 28

Slide 28 text

元のコード public function __invoke(Post $post): Collection { return $this->service->getComments($post); }

Slide 29

Slide 29 text

BatchLoader

Slide 30

Slide 30 text

BatchLoader public function load(Post $post): Deferred { $this->posts->put($post->id, $post); return new Deferred(function () use ($post): Collection { if (!$this->hasResolved) { $this->resolve(); } return $this->results[$post->id]; }); } WhereIn(’post_id’, $this->posts->pluck(‘id’)) で⼀括取得する

Slide 31

Slide 31 text

ログ N+1問題が解消された︕

Slide 32

Slide 32 text

まとめ

Slide 33

Slide 33 text

まとめ ■ GraphQLはクライアントが欲しい情報をクエリで送り、サーバがその情報を返す ■ PHPでGraphQLを使用するにはlighthouseが便利 ■ クエリに応じて必要な情報だけを取得する ■ ただし、N+1問題が発生しやすい ■ BatchLoaderを使うことで解消できる