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

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. © ZOZO, Inc. 目次
 1. GraphQLとは
 2. 一般的に知られているGraphQLのメリット
 3. たまたま実感した予想外のGraphQLのメリット


    4. こんなGraphQLスキーマです
 5. GraphQLスキーマの表現力から得られたメリット
 6. まとめ
 
 
 3
  2. © ZOZO, Inc. 一般的に知られていそうなGraphQLのメリット
 • 画面に必要なデータを取得するのに1度のリクエストで済む
 • リクエスト、レスポンスに静的型が付くのでTypeScriptなどとの相性がいい
 • GraphiQLやApollo

    Sandboxといったプレイグラウンドが便利
 • Apollo FederationやSchema Stitchingなどマイクロサービスを束ねる仕組みがある
 
 
 
 ついでにデメリットも
 • すべてが POST /graphql になるのでキャッシュや監視のやり方が変わる
 • クライアント、サーバーともに実装が変わったりと学習コストが必要
 
 6
  3. © ZOZO, Inc. たまたま実感した予想外のGraphQLのメリット
 • ある新規事業の小さいサービスをREST APIからGraphQLに移行しようとしている
 • それほど複雑なQuery, MutationがないのでGraphQL特有のメリットはあまりなく、練習くらい

    になると思っていた
 
 ところが、
 • GraphQLスキーマの表現力が高く、REST APIでは表現しきれなかったサーバーとクライアン ト間のドメインロジックが表現できた
 
 7 次のスライドからZOZOMAT for Handsというとある小さいサービスと、そのサー ビスのドメインロジックを表現できたGraphQLスキーマを紹介します
 

  4. © ZOZO, Inc. ZOZOMAT for Handsの測定はステップや状態が多い
 ユーザーに色んな角度から手をスマホで撮影してもらう必要 がある
 
 1.

    真上から撮影して左右どっちの手か、マットが平たく置い てあるかなどを判定する
 2. マットの色に沿っていろんな角度から手を撮影する
 3. 最後に真上からもう一度撮影して完了
 
 このステップごとにサーバーと通信していて、クライアントは今 どのステップにいるのか、次はどのステップかを知る必要があ る
 
 
 10
  5. © ZOZO, Inc. ZOZOMAT for Handsはエラーがたくさんある
 
 
 • 最初の真上から撮影するとき


    ◦ 人差し指と中指の間隔が開きすぎている
 ◦ カメラが手に近すぎる
 • いろんな角度から撮影しているとき
 ◦ 指定した色の方向から撮影していない
 ◦ カメラの角度が高すぎる
 • などなど
 • それぞれ対応した音声ガイドを再生するのでエラーハン ドリングが重要
 
 
 11
  6. © 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 }
  7. © ZOZO, Inc. type Mutation { """ processSession 計測ステップを進行します。 sessionIdを指定し、撮影した画像をimageに入れて測定ステップを進めてください。

    """ processSession(sessionId: ID!, image: Image!): ProcessSessionResult! } union ProcessSessionResult = ProcessSessionProgressed | ProcessSessionFailed | ProcessSessionCompleted どんなGraphQLスキーマになったか
 
 
 ※説明用に簡略化していますが実際のスキーマと基本的には同じです
 14
  8. © 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を活用する

  9. © 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を使います

  10. © ZOZO, Inc. type Mutation { """ processSession 計測ステップを進行します。 sessionIdを指定し、撮影した画像をimageに入れて測定ステップを進めてください。

    """ processSession(sessionId: ID!, image: Image!): ProcessSessionResult! } union ProcessSessionResult = ProcessSessionProgressed | ProcessSessionFailed | ProcessSessionCompleted processSession ミューテーション
 
 
 17 processSessionミューテーションの結果はセッションの途中、失敗、完了の3種類

  11. © 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種類

  12. © 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 # ... }
  13. © 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 # ... }
  14. © 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
 (結果を出すのに数秒かかるので終わったことだけ通知する)

  15. © ZOZO, Inc. type Mutation { """ processSession 計測ステップを進行します。 sessionIdを指定し、撮影した画像をimageに入れて測定ステップを進めてください。

    """ processSession(sessionId: ID!, image: Image!): ProcessSessionResult! } union ProcessSessionResult = ProcessSessionProgressed | ProcessSessionFailed | ProcessSessionCompleted ここまでのまとめ
 • processSessionを繰り返し使って計測を進める
 • processSessionは3パターンの結果を返す
 • 計測途中、失敗、完了それぞれ次の動作に必要な情報 が入っている
 
 
 22
  16. © ZOZO, Inc. type Query { """ 計測が完了したセッションを取得します。 """ session(id:

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

    ID!): Session } type Session { id: ID! analysis: SessionAnalysis! } union SessionAnalysis = AnalysisInProgress | AnalysisCompleted | AnalysisFailed 測定結果を取得する session クエリ
 24 測定の結果が分析途中、分析完了、分析失敗の3パターンにな ることがわかる。
 それぞれの型の中身は省略するが、途中なら数秒後クエリを再 実行、完了なら結果を表示、失敗ならエラー表示すれば良いこ とがわかる
 
 

  18. © 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
  19. © ZOZO, Inc. ドメインモデリングともいえる議論が活発にできた
 • 最初はREST APIをそのまま移したようなスキーマだったがGitHubのPR上で100件近いコメン トで盛んな議論ができた🔥
 • 紹介したスキーマは割とシンプルですが原型はかなり違った


    ◦ Union、EnumといったGraphQLの型システムの恩恵
 ◦ 今回は使ってないですがディレクティブも強力です
 • 以前のREST APIのときは入社間もないこともあって正直言ってあまりドメインを詳細まで理 解できていなかったが、この議論とGraphQLスキーマからよく理解できた
 
 
 28
  20. © ZOZO, Inc. DDDのドメインモデリングにも使えるのではないか
 • 先日読んだDomain Modeling Made Functionalという本はF#の型システムを利用してドメイン モデリングしていた


    • GraphQLくらいの型システムでも実現可能と思える
 • マイクロサービスが境界づけられたコンテキストに該当するならそのコンテキストが外部に提 供する能力を可読性高く表現できる…気がする
 • GraphiQLやApollo Studioで動作が確認しやすいこと、スキーマのドキュメント性が高いことも ドメインエキスパートにやさしいのでは
 
 29