Spring GraphQL introduction

CI for example code

概要

JSUG勉強会 2021年その2 Spring GraphQLをとことん語る夕べでの発表のスライドとコード例です。 発表時はSpring GraphQL 1.0.0-M1でしたがM2、M3とバージョンを重ねているためたまにスライドを更新しています。 JSUGでの発表当時のスライドは20210806-jsugタグを参照してください。

資料のビルド

PlantUMLで描いた図をビルドする。

java -jar ~/plantuml.jar -tsvg docs/plantuml.pu

スライドをビルドする。

npx @marp-team/marp-cli@latest --html --output docs/index.html docs/slide.md

/docsをGitHub Pagesでホスティングするように設定しているので次のURLでスライドが見られる。

デモの手順

準備

./mvnw spring-boot:run

ブラウザで http://localhost:8080/graphiql を開く。

query操作

スライドにもあったクエリーを試す。

query {
  article(id: 1) {
    id
    title
    content
    category {
      id
      name
    }
  }
}

タイトルだけ取得するようにしてみる。

query {
  article(id: 1) {
    title
  }
}

変数を使ってみる。

query GetArticle($id: ID!) {
  article(id: $id) {
    title
  }
}
{
  "id": 1
}

curlでも試してみる。

curl -s http://localhost:8080/graphql -H "Content-Type: application/json" -d '{"query": "{article(id: 1) { id, title, content, category { id, name } }}"}' | jq

subscription

subscription操作も試してみる。

subscription {
  count
}

結果のエリアにカウントアップされて1から10まで表示される。

wscatでも確認してみる。 wscatnpm install -g wscatでインストールできる。

wscat --connect ws://localhost:8080/graphql

subscriptionのプロトコルはまだ理解していないので、Spring GraphQLのコードを読んでわかった手順を実施する。

まずはconnection_initが必要。

{"type": "connection_init"}

それからsubscribe。 待っていると1秒おきにカウントアップする値が返される。

{"type": "subscribe", "id": "...", "payload": {"query": "subscription { count }"}}

もちろん変数も使える。

{"type": "subscribe", "id": "...", "payload": {"query": "subscription Count($size: Int!) { count(size: $size) }", "variables": {"size": 5 }}}

DataLoader

まずはN + 1。

query {
  comics {
    title
    publisher {
      name
    }
  }
}

Fetch Query.comics以降のログを見るとcomicsで1回、comics.publisherで10回のクエリーが発行されていることがわかる。

次にDataLoader版。

query {
  comics {
    title
    author {
      name
    }
  }
}

Fetch Query.comics以降のログを見るとcomicscomics.authorが共に1回ずつのクエリー発行で済んでいることがわかる。

ページング

まずはafterを指定せずクエリーーを発行して返ってくる値を確認する。

query GitCommits {
  history {
    forward(first: 3) {
      edges {
        node {
          hash
          message
        }
        cursor
      }
      pageInfo {
        hasPreviousPage
        hasNextPage
        startCursor
        endCursor
      }
    }
  }
}

それからpageInfoの値を見ながらafterを設定しつつクエリーを試す。

query GitCommits {
  history {
    forward(first: 3, after: "3") {
      edges {
        node {
          hash
          message
        }
        cursor
      }
      pageInfo {
        hasPreviousPage
        hasNextPage
        startCursor
        endCursor
      }
    }
  }
}

後方も試す。

query GitCommits {
  history {
    backward(last: 3, before: "7") {
      edges {
        node {
          hash
          message
        }
        cursor
      }
      pageInfo {
        hasPreviousPage
        hasNextPage
        startCursor
        endCursor
      }
    }
  }
}

WIP: 認証・認可

次のクエリーを実行するとエラー(Unauthorized)になる。

{
  security {
    protected
  }
}

REQUEST HEADERSという場所に次のJSONを書いて実行するとエラーにならず値が返ってくる。

{
  "Authorization": "Basic ZGVtbzpzZWNyZXQ="
}

これは該当のDataFetcher内で呼び出されているコンポーネントのメソッドに@PreAuthorize("isAuthenticated()")を付けている。

カスタムdirectiveで認証を表現した例も作ってみた。 次のクエリーが@authenticatedというカスタムdirectiveで保護したフィールドへのアクセスとなる。

{
  security {
    protected2
  }
}

Authorizationヘッダーの有無による違いを試してみてほしい。

メトリクス

curl -s localhost:8080/actuator/metrics/graphql.request | jq
curl -s localhost:8080/actuator/metrics/graphql.datafetcher | jq
curl -s localhost:8080/actuator/metrics/graphql.error | jq

その他の話題

graphqlvizを使うとGraphQLスキーマを図にできる。

npx graphqlviz http://localhost:8080/graphql | dot -Tpng -o graphql-schema.png

次のような図が生成される。


ライセンス

スライド(docs/配下にあるファイル)はCC BY 4.0、ソースコード(スライド以外のファイル)はMITを適用します。