$30 off During Our Annual Pro Sale. View Details »

GraphQLのあまり知られていない魅力 (スキーマの表現力編) / domain modeling with GraphQL Schema

adwd
February 25, 2022

GraphQLのあまり知られていない魅力 (スキーマの表現力編) / domain modeling with GraphQL Schema

adwd

February 25, 2022
Tweet

More Decks by adwd

Other Decks in Programming

Transcript

  1. GraphQLのあまり知られていない魅力

    (スキーマの表現力編)


    
ZOZO Tech Talk #4 - Webフロントエンド×新規事業

    株式会社ZOZO

    計測プラットフォーム本部 計測システム部


    西田 雅博
    Copyright © ZOZO, Inc.

    View Slide

  2. © ZOZO, Inc.
    株式会社ZOZO

    計測プラットフォーム本部 計測システム部

    西田 雅博

    GraphQL+Reactを極めたい

    Webフロントエンドエンジニア


    Rustもやりたい


    2

    View Slide

  3. © ZOZO, Inc.
    目次

    1. GraphQLとは

    2. 一般的に知られているGraphQLのメリット

    3. たまたま実感した予想外のGraphQLのメリット

    4. こんなGraphQLスキーマです

    5. GraphQLスキーマの表現力から得られたメリット

    6. まとめ



    3

    View Slide

  4. © ZOZO, Inc.
    GraphQLとは

    4

    View Slide

  5. © ZOZO, Inc.
    GraphQLとは

    5
    ● Meta(Facebook)が開発したクライアントが指定
    したデータの形でサーバーがレスポンスを返す
    クエリ言語

    ● https://graphql.org/

    ● https://spec.graphql.org/October2021/




    View Slide

  6. © ZOZO, Inc.
    一般的に知られていそうなGraphQLのメリット

    ● 画面に必要なデータを取得するのに1度のリクエストで済む

    ● リクエスト、レスポンスに静的型が付くのでTypeScriptなどとの相性がいい

    ● GraphiQLやApollo Sandboxといったプレイグラウンドが便利

    ● Apollo FederationやSchema Stitchingなどマイクロサービスを束ねる仕組みがある




    ついでにデメリットも

    ● すべてが POST /graphql になるのでキャッシュや監視のやり方が変わる

    ● クライアント、サーバーともに実装が変わったりと学習コストが必要


    6

    View Slide

  7. © ZOZO, Inc.
    たまたま実感した予想外のGraphQLのメリット

    ● ある新規事業の小さいサービスをREST APIからGraphQLに移行しようとしている

    ● それほど複雑なQuery, MutationがないのでGraphQL特有のメリットはあまりなく、練習くらい
    になると思っていた


    ところが、

    ● GraphQLスキーマの表現力が高く、REST APIでは表現しきれなかったサーバーとクライアン
    ト間のドメインロジックが表現できた


    7
    次のスライドからZOZOMAT for Handsというとある小さいサービスと、そのサー
    ビスのドメインロジックを表現できたGraphQLスキーマを紹介します


    View Slide

  8. © ZOZO, Inc.
    8
    ● 足のサイズを測ってピッタリのサイズの靴を買えるZOZOMATの指輪版


    View Slide

  9. © ZOZO, Inc.
    9
    ● 足のサイズを測ってピッタリのサイズの靴を買えるZOZOMATの指輪版

    ● マットの上に手をおいて、音声のガイドに従ってスマホのカメラで色んな
    方向から手を撮影すると最適な指輪のサイズを出してくれる

    ● まだ一般リリースしてなくてこっそりやってます



    View Slide

  10. © ZOZO, Inc.
    ZOZOMAT for Handsの測定はステップや状態が多い

    ユーザーに色んな角度から手をスマホで撮影してもらう必要
    がある


    1. 真上から撮影して左右どっちの手か、マットが平たく置い
    てあるかなどを判定する

    2. マットの色に沿っていろんな角度から手を撮影する

    3. 最後に真上からもう一度撮影して完了


    このステップごとにサーバーと通信していて、クライアントは今
    どのステップにいるのか、次はどのステップかを知る必要があ
    る



    10

    View Slide

  11. © ZOZO, Inc.
    ZOZOMAT for Handsはエラーがたくさんある


    
 ● 最初の真上から撮影するとき

    ○ 人差し指と中指の間隔が開きすぎている

    ○ カメラが手に近すぎる

    ● いろんな角度から撮影しているとき

    ○ 指定した色の方向から撮影していない

    ○ カメラの角度が高すぎる

    ● などなど

    ● それぞれ対応した音声ガイドを再生するのでエラーハン
    ドリングが重要



    11

    View Slide

  12. © ZOZO, Inc.
    こんなGraphQLスキーマです

    12

    View Slide

  13. © ZOZO, Inc.
    type Mutation {
    """
    TODOを追加します
    """
    addTodo(text: String!): Todo
    }
    type Todo {
    id: ID!
    text: String!
    status: TodoStatus!
    foo: FooUnion!
    }
    union FooUnion = BarType | BazType | QuxType
    GraphQLスキーマの基本的な読み方



    ● Mutation: 実行でデータが変化する操作

    ○ RESTでいうとPUT/POST/DELETE

    ○ QueryはGET

    ● addTodoミューテーション

    ○ NonNullなStringを引数にとって

    ○ NullableなTodoを返す

    ● type Todoで型を宣言

    ○ id, text, status, fooフィールドがある

    ○ どれもNonNull

    ● statusはTodoStatus enum

    ● FooUnionは3つのうちいずれかになる



    13
    enum TodoStatus {
    TODO
    DONE
    }

    View Slide

  14. © ZOZO, Inc.
    type Mutation {
    """
    processSession
    計測ステップを進行します。
    sessionIdを指定し、撮影した画像をimageに入れて測定ステップを進めてください。
    """
    processSession(sessionId: ID!, image: Image!): ProcessSessionResult!
    }
    union ProcessSessionResult =
    ProcessSessionProgressed | ProcessSessionFailed | ProcessSessionCompleted
    どんなGraphQLスキーマになったか



    ※説明用に簡略化していますが実際のスキーマと基本的には同じです

    14

    View Slide

  15. © ZOZO, Inc.
    type Mutation {
    """
    processSession
    計測ステップを進行します。
    sessionIdを指定し、撮影した画像をimageに入れて測定ステップを進めてください。
    """
    processSession(sessionId: ID!, image: Image!): ProcessSessionResult!
    }
    union ProcessSessionResult =
    ProcessSessionProgressed | ProcessSessionFailed | ProcessSessionCompleted
    processSession ミューテーション



    15
    ダブルクォート3つで囲んだところはdescription

    コメントのようなものだけどMarkdownも使えるれっきとしたドキュメント

    GraphQLの型で表現しきれないビジネスロジックは無理せずdescriptionを活用する


    View Slide

  16. © ZOZO, Inc.
    type Mutation {
    """
    processSession
    計測ステップを進行します。
    sessionIdを指定し、撮影した画像をimageに入れて測定ステップを進めてください。
    """
    processSession(sessionId: ID!, image: Image!): ProcessSessionResult!
    }
    union ProcessSessionResult =
    ProcessSessionProgressed | ProcessSessionFailed | ProcessSessionCompleted
    processSession ミューテーション



    16
    descriptionに書いてあるとおりの処理を行うprocessSessionミューテーション

    ※GraphQLでファイルアップロードは現状できないので、graphql-multipart-requestを使います


    View Slide

  17. © ZOZO, Inc.
    type Mutation {
    """
    processSession
    計測ステップを進行します。
    sessionIdを指定し、撮影した画像をimageに入れて測定ステップを進めてください。
    """
    processSession(sessionId: ID!, image: Image!): ProcessSessionResult!
    }
    union ProcessSessionResult =
    ProcessSessionProgressed | ProcessSessionFailed | ProcessSessionCompleted
    processSession ミューテーション



    17
    processSessionミューテーションの結果はセッションの途中、失敗、完了の3種類


    View Slide

  18. © ZOZO, Inc.
    union ProcessSessionResult =
    ProcessSessionProgressed | ProcessSessionFailed | ProcessSessionCompleted
    type ProcessSessionProgressed {
    nextInstruction: CaptureInstruction!
    }
    type ProcessSessionFailed {
    failure: ProcessSessionFailure!
    }
    type ProcessSessionCompleted {
    # このIDで session Query を実行すると測定結果が取得できます。
    completedSessionId: ID!
    }
    ProcessSessionResult ユニオン

    18
    processSessionミューテーションの結果は

    セッションの途中、失敗、完了の3種類


    View Slide

  19. © ZOZO, Inc.
    union ProcessSessionResult =
    ProcessSessionProgressed | ProcessSessionFailed | ProcessSessionCompleted
    type ProcessSessionProgressed {
    nextInstruction: CaptureInstruction!
    }
    type ProcessSessionFailed {
    failure: ProcessSessionFailure!
    }
    type ProcessSessionCompleted {
    # このIDで session Query を実行すると測定結果が取得できます。
    completedSessionId: ID!
    }
    ProcessSessionResult ユニオン

    19
    セッションの途中の場合は次の測定の指示


    enum CaptureInstruction {
    TOP
    BLUE
    # ...
    }

    View Slide

  20. © ZOZO, Inc.
    union ProcessSessionResult =
    ProcessSessionProgressed | ProcessSessionFailed | ProcessSessionCompleted
    type ProcessSessionProgressed {
    nextInstruction: CaptureInstruction!
    }
    type ProcessSessionFailed {
    failure: ProcessSessionFailure!
    }
    type ProcessSessionCompleted {
    # このIDで session Query を実行すると測定結果が取得できます。
    completedSessionId: ID!
    }
    ProcessSessionResult ユニオン

    20
    測定ステップ失敗の場合はその理由

    enum ProcessSessionFailure {
    CAMERA_PITCH_TOO_LOW
    DISTANCE_TOO_CLOSE
    # ...
    }

    View Slide

  21. © ZOZO, Inc.
    union ProcessSessionResult =
    ProcessSessionProgressed | ProcessSessionFailed | ProcessSessionCompleted
    type ProcessSessionProgressed {
    nextInstruction: CaptureInstruction!
    }
    type ProcessSessionFailed {
    failure: ProcessSessionFailure!
    }
    type ProcessSessionCompleted {
    # このIDで session Query を実行すると測定結果が取得できます。
    completedSessionId: ID!
    }
    ProcessSessionResult ユニオン

    21
    測定完了の場合は結果を取得するためのID

    (結果を出すのに数秒かかるので終わったことだけ通知する)


    View Slide

  22. © ZOZO, Inc.
    type Mutation {
    """
    processSession
    計測ステップを進行します。
    sessionIdを指定し、撮影した画像をimageに入れて測定ステップを進めてください。
    """
    processSession(sessionId: ID!, image: Image!): ProcessSessionResult!
    }
    union ProcessSessionResult =
    ProcessSessionProgressed | ProcessSessionFailed | ProcessSessionCompleted
    ここまでのまとめ

    ● processSessionを繰り返し使って計測を進める

    ● processSessionは3パターンの結果を返す

    ● 計測途中、失敗、完了それぞれ次の動作に必要な情報
    が入っている



    22

    View Slide

  23. © ZOZO, Inc.
    type Query {
    """
    計測が完了したセッションを取得します。
    """
    session(id: ID!): Session
    }
    type Session {
    id: ID!
    analysis: SessionAnalysis!
    }
    union SessionAnalysis =
    AnalysisInProgress | AnalysisCompleted | AnalysisFailed
    測定結果を取得する session クエリ

    23

    View Slide

  24. © ZOZO, Inc.
    type Query {
    """
    計測が完了したセッションを取得します。
    """
    session(id: ID!): Session
    }
    type Session {
    id: ID!
    analysis: SessionAnalysis!
    }
    union SessionAnalysis =
    AnalysisInProgress | AnalysisCompleted | AnalysisFailed
    測定結果を取得する session クエリ

    24
    測定の結果が分析途中、分析完了、分析失敗の3パターンにな
    ることがわかる。

    それぞれの型の中身は省略するが、途中なら数秒後クエリを再
    実行、完了なら結果を表示、失敗ならエラー表示すれば良いこ
    とがわかる



    View Slide

  25. © ZOZO, Inc.
    type Mutation {
    processSession(sessionId: ID!, image: Image!): ProcessSessionResult!
    }
    union ProcessSessionResult =
    ProcessSessionProgressed | ProcessSessionFailed | ProcessSessionCompleted
    type Query {
    session(id: ID!): Session
    }
    union SessionAnalysis =
    AnalysisInProgress | AnalysisCompleted | AnalysisFailed
    測定の一連の流れがスキーマから読み取れる

    25

    View Slide

  26. © ZOZO, Inc.
    GraphQLスキーマの表現力から得ら
    れたメリット

    26

    View Slide

  27. © ZOZO, Inc.
    ドメインモデリングともいえる議論が活発にできた

    ● 最初はREST APIをそのまま移したようなスキーマだったがGitHubのPR上で100件近いコメン
    トで盛んな議論ができた🔥


    27

    View Slide

  28. © ZOZO, Inc.
    ドメインモデリングともいえる議論が活発にできた

    ● 最初はREST APIをそのまま移したようなスキーマだったがGitHubのPR上で100件近いコメン
    トで盛んな議論ができた🔥

    ● 紹介したスキーマは割とシンプルですが原型はかなり違った

    ○ Union、EnumといったGraphQLの型システムの恩恵

    ○ 今回は使ってないですがディレクティブも強力です

    ● 以前のREST APIのときは入社間もないこともあって正直言ってあまりドメインを詳細まで理
    解できていなかったが、この議論とGraphQLスキーマからよく理解できた



    28

    View Slide

  29. © ZOZO, Inc.
    DDDのドメインモデリングにも使えるのではないか

    ● 先日読んだDomain Modeling Made Functionalという本はF#の型システムを利用してドメイン
    モデリングしていた

    ● GraphQLくらいの型システムでも実現可能と思える

    ● マイクロサービスが境界づけられたコンテキストに該当するならそのコンテキストが外部に提
    供する能力を可読性高く表現できる…気がする

    ● GraphiQLやApollo Studioで動作が確認しやすいこと、スキーマのドキュメント性が高いことも
    ドメインエキスパートにやさしいのでは


    29

    View Slide

  30. © ZOZO, Inc.
    まとめ

    ● 小さなサービスにGraphQLを導入した

    ● 予想していなかった効果として、スキーマの表現力によってドメインに関する議論が盛んに
    行えた

    ● GraphQLおすすめです!



    30

    View Slide

  31. View Slide