Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GraphQLの静的解析ツールを作った - tenntenn Conference 2022

GraphQLの静的解析ツールを作った - tenntenn Conference 2022

この資料はtenntenn Conference 2022にて発表を行った際に用いた資料です。

■ tenntenn Conferenceとは
tenntenn Conferenceはtenntennが主催し、そしてすべてのセッションがtenntennによる登壇のカンファレンスです。

イベントページ:https://tenntenn.connpass.com/event/226562/
ハッシュタグ:#tennconn
資料(Google スライド):https://tenn.in/gqlanalysis
動画:https://tenn.in/gqlanalysis-video
再生リスト:https://tenn.in/conn22-videolist

■ 内容
これまでGraphQLのスキーマやクエリに対する静的解析ツールを開発したい場合にはGoを使うという選択肢はありませんでした。そこでgqlanalysisというGoでGraphQLの静的解析を行うためのライブラリを開発しました。このセッションではGraphQLの静的解析の必要性やgqlanalysisの解説を行います。

・GraphQL
・静的解析
・スキーマ/クエリ
・gqlanalysis

■ 登壇者&主催者

・名前:tenntenn / 上田拓也
・HP:https://tenntenn.dev
・Twitter:https://twitter.com/tenntenn

メルカリ/メルペイ所属。バックエンドエンジニアとして日々Goを書いている。Google Developer Expert (Go)。一般社団法人Gophers Japan代表。Go Conference主催者。大学時代にGoに出会い、それ以来のめり込む。人類をGopherにしたいと考え、Goの普及に取り組んでいる。複数社でGoに関する技術アドバイザーをしている。大学においてGoに関する集中講義も担当している。マスコットのGopherの絵を描くのも好き。

■ Gopher道場 自習室

https://gopherdojo.org/studyroom/

Gopher道場とは、実践的なGoを体系的に学べる場です。
Gopher道場 自習室では、以下のようなコンテンツや学びの場を提供します。

・Gopher道場の講義を録画した動画(10時間以上分)
・Slackにおける受講者同士のコミュニティ
・Gopher道場卒業生による課題のレビュー(ボランティアでご協力頂いているのでベストエフォートです)

■ Meety(カジュアル面談)

・ソフトウェアエンジニアの地方移住ってどうなの?:https://meety.net/matches/jyZgDkEEwmMk
・メルカリグループにおけるGoの使いどころ:https://meety.net/matches/LbeVbIACxLqk
・地方からの技術コミュニティへの貢献:https://meety.net/matches/gVeMtImLkWJE

■ お仕事の依頼について

副業にて技術顧問やアドバイザーなどを行っています。過去の実績や問い合わせフォームは以下のURLからご確認ください。
https://tenntenn.dev/ja/job/

tenntenn - Takuya Ueda

January 22, 2022
Tweet

More Decks by tenntenn - Takuya Ueda

Other Decks in Programming

Transcript

  1. The Go gopher was designed by Renée French.
    The gopher stickers was made by Takuya Ueda.
    Licensed under the Creative Commons 3.0 Attributions license.
    GraphQLの静的解析
    ツールを作った
    2022/01/15(土)
    tenntenn Conference 2022
    資料:https://tenn.in/gqlanalysis
    動画:https://tenn.in/gqlanalysis-video

    View full-size slide

  2. 上田拓也
    Go ビギナーズ

    Go Conference

    @tenntenn
    tenntenn.dev
    Google Developer Expert (Go)
    一般社団法人 Gophers Japan 代表理事
    Experts Team

    View full-size slide

  3. この活動を支える企業
    ■ 株式会社Appify Technologies
    ● https://appify-inc.com/
    ● 採用情報:https://herp.careers/v1/appify/MDDmrTNJ2hUo
    ● https://github.com/gqlgo

    View full-size slide

  4. GraphQLとは
    ■ APIのためのクエリ言語
    ● RESTと違ってクライアントが要求する情報だけ取得できる
    ● 複数のデータソースから1リクエストで取得できる
    ■ スキーマベースで開発できる
    ● GraphSQLのスキーマからGoの構造体定義を生成できる
    ○ gqlgenを使用する

    View full-size slide

  5. AppifyのGraphQLアーキテクチャ

    View full-size slide

  6. GraphQLの静的解析
    ■ スキーマとクエリの静的解析
    ● https://github.com/gqlgo/gqlanalysis/
    ● Goで書ける
    ○ GraphQLの静的解析ツールはほとんどがJavaScript
    ○ 開発言語にGoを使っている場合はGoで書きたい
    ● パースにgqlparserを使用
    ● go/analysisパッケージと似た形で開発できる
    ○ モジュール化(Analyzer単位での開発)
    ○ テスト
    ● gqlskeletonコマンド
    ○ スケルトンコードを作れる

    View full-size slide

  7. GraphQLの静的解析ツールの例
    ■ lackid
    ● https://github.com/gqlgo/lackid
    ● スキーマにidフィールドを持つのにクエリに指定されていないもの
    query GetA() {
    a { # want "type A has id field but selection a does not have id field"
    name
    }
    }
    $ lackid -schema="server/graphql/schema/**/*.graphql" -query="client/**/*.graphql"
    # イントロスペクションにも対応
    $ lackid -schema="https://example.com" -query="client/**/*.graphql"

    View full-size slide

  8. gqlanalysis.Analyzer構造体
    ■ GraphQLの静的解析ツールの単位
    ● Runフィールドに解析の処理をセットする
    ● 依存するAnalyzerをRequiresフィールドにセットする
    ● 用途はanalysis.Analyze構造体とほとんど同じ
    type Analyzer struct {
    Name string
    Run func(pass *Pass) (interface{}, error)
    Doc string
    Flags flag.FlagSet
    Requires []*Analyzer
    ResultType reflect.Type
    }

    View full-size slide

  9. gqlanalysis.Pass構造体
    ■ 静的解析に使う情報が入った構造体
    ● Analyzer.Runフィールドセットされた関数の引数で用いる
    type Pass struct {
    Analyzer *Analyzer
    Schema *ast.Schema // スキーマの抽象構文木
    Queries []*ast.QueryDocument // クエリの抽象構文木
    Comments []*Comment
    Report func(*Diagnostic) // エラー出力
    ResultOf map[*Analyzer]interface{} // 依存するAnalyzerの結果
    }

    View full-size slide

  10. gqlanalysis.Diagnostic
    ■ ast.Position(位置)に関連付けられた静的解析結果
    ● 任意の位置へのエラーを表現するために使う
    ○ 例:〇行目に〇〇というエラーがあります
    ■ (*gqlanalysis.Pass).Reportfメソッド
    ● Diagnosticを生成するメソッド
    ● fmt.Fprintf感覚で使える
    func (pass *Pass) Reportf(
    pos ast.Position, // 位置
    format string,
    args ...interface{}
    )

    View full-size slide

  11. Analyzerの例
    var Analyzer = &gqlanalysis.Analyzer{ Name:"simple", Doc:"simple", Run:run }
    func run(pass *gqlanalysis.Pass) (interface{}, error) {
    for _, q := range pass.Queries {
    for _, f := range q.Fragments {
    for _, sel := range f.SelectionSet {
    switch sel := sel.(type) {
    case *ast.Field:
    if sel.Name == "name" {
    pass.Reportf(sel.Position, "NG")
    }
    }
    }
    }
    }
    return nil, nil
    }

    View full-size slide

  12. analysistestパッケージ
    ■ Analyzerのテストを簡単に行うためのパッケージ
    ● 実際の解析対象となるソースコードをファイルとして用意すればよい
    ● テストデータはtestdata以下に用意する
    ● コメントによって該当行にDiagnosticが出力されるかチェックする
    func Test(t *testing.T) {
    testdata := analysistest.TestData(t)
    analysistest.Run(t, testdata, simple.Analyzer, "a")
    }

    View full-size slide

  13. テストデータ
    fragment AFragment1 on A {
    name # want "NG"
    }
    fragment AFragment2 on A {
    id
    name # want "NG"
    ...BFragment
    }
    fragment BFragment on A {
    name # want "NG"
    }
    ■ wantで始まるコメントで期待する結果を書く
    ● 対応するDiagnosticが出力されていればOK
    正規表現が使える

    View full-size slide

  14. multichecker
    ■ multichecker.Main
    ● 複数のAnalyzerからなるmain関数を提供する
    ● 各Analyzerはゴールーチンで1回だけ実行される
    package main
    import (
    "github.com/gqlgo/gqlanalysis/multichecker"
    "github.com/gqlgo/lackid"
    "github.com/gqlgo/myanalyzer"
    )
    func main() {
    multichecker.Main(lackid.Analyzer, myanalyzer.Analyzer)
    }

    View full-size slide

  15. gqlskeleton
    ■ gqlanalysis用のスケルトンコードジェネレータ
    ● 簡単にGraphQLの静的解析ツールを始めることができる
    ● Analyzer、テストコード、main.goの雛形作ってくれる
    $ gqlskeleton simple
    simple
    ├── cmd
    │ └── simple
    │ └── main.go
    ├── go.mod
    ├── simple.go
    ├── simple_test.go
    └── testdata
    └── a
    ├── query
    │ └── query.graphql
    └── schema
    ├── model.graphql
    ├── mutation.graphql
    ├── query.graphql
    ├── schema.graphql
    └── subscription.graphql
    $ go install github.com/gqlgo/gqlanalysis/cmd/gqlskeleton@latest

    View full-size slide

  16. gqlskeletonの仕組み
    ■ skeletonkitを使用
    ● https://github.com/gostaticanalysis/skeletonkit
    ● スケルトンコードジェネレーターを作るためのライブラリ
    ○ gostaticanalysis/skeletonでも使っている
    ● テンプレートを用意してあげるだけ楽ちん
    参考:io/fsパッケージを用いた テスタブルなコード生成ツールの開発 - Go Conference Online 2021 Autumn

    View full-size slide

  17. 今後入る予定の機能
    ■ コード生成ライブラリ
    ● 簡単にGraphQL用コード生成器が作れる(PR#7)
    var flagOutput string
    var Generator = &codegen.Generator{ Name: "fragment", Doc: "example of codegen", Run: run }
    func init() { Generator.Flags.StringVar(&flagOutput, "output", "fragment.kt", "output file") }
    func run(pass *codegen.Pass) (rerr error) {
    output, path := pass.CreateTemp("fragment.kt")
    for _, q := range pass.Queries {
    if len(q.Fragments) == 0 { continue }
    tmpl := codegen.NewTemplate(pass, "fragment-template")
    _, err := tmpl.Funcs(funcMap(pass, tmpl)).Parse(tmplStr)
    if err != nil { return err }
    if err := tmpl.ExecuteTemplate(output, "fragments", q.Fragments); err != nil { return err }
    }
    if err := exec.Command("ktfmt", path).Run(); err != nil { return err }
    if _, err := output.Seek(0, io.SeekStart); err != nil { return err }
    if _, err := io.Copy(pass.Output, output); err != nil { return err }
    return nil
    }

    View full-size slide

  18. まとめ
    ■ GraphQLでも静的解析できる!
    ● gqlanalysisを使うと簡単に静的解析できる
    ● gqlskeletonで簡単に始められる
    ● GoでGraphQLの静的解析ツールが作れる

    View full-size slide