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

GraphQLスキーマの設計で考えたこと

AnaTofuZ
January 26, 2022

 GraphQLスキーマの設計で考えたこと

2022/01/26 Hatena Engineer Seminar #18
https://hatena.connpass.com/event/235821/

AnaTofuZ

January 26, 2022
Tweet

More Decks by AnaTofuZ

Other Decks in Technology

Transcript

  1. GraphQLスキーマの設計で
    考えたこと
    2022/01/26 Hatena Engineer Seminar #18
    id:AnaTofuZ

    View Slide

  2. 今日の内容

    View Slide

  3. GraphQLスキーマの設計は難しい

    View Slide

  4. 今日の内容
    ● GraphQLスキーマの設計は難しい
    ● DBスキーマと合わせて設計するのが正解? それとも?
    ● 型やフィールドで何を表現するか
    ● バックエンド、フロントエンドの事情とスキーマの表現

    View Slide

  5. こんにちは
    ● id:AnaTofuZ
    ● Webアプリケーションエンジニア
    ● 2021年新卒
    ○ それまでは沖縄にいました
    ○ 出身は山梨です...
    ● アカウント名はアナグラです

    View Slide

  6. もくじ
    ● GraphQLとは
    ● カクヨムとGraphQL
    ● 1から考えるスキーマ設計
    ● まとめ

    View Slide

  7. GraphQL
    ● Facebookによって作られたクエリ言語
    ● GraphQLスキーマがある
    ● スキーマの内扱いたい情報をfragmentで部分的に宣言できる
    ● オブジェクトの取得はQuery/ 更新はMutationで行う
    ● 詳しくは
    ○ GraphQL Highway

    View Slide

  8. GraphQL
    ● Facebookによって作られたクエリ言語
    ● GraphQLスキーマがある
    ● スキーマの内扱いたい情報をfragmentで部分的に宣言できる
    ● オブジェクトの取得はQuery/ 更新はMutationで行う
    ● 詳しくは
    ○ GraphQL Highway

    View Slide

  9. GraphQLのスキーマ
    ● SDL (Schema Definition Language)で
    定義する
    ● interfaceをもつ
    ○ 開発チームで考えると
    ■ Interface Member
    ● 共通部分
    ■ Type Engineer
    ● エンジニア
    ● 好きなプログラミング言語をもつ
    ■ Type Designer
    ● デザイナー
    ● Adobe IDを必ずもつ
    interface Member {
    id: ID!
    name: String!
    githubID: String!
    }
    type Engineer implements Member {
    id: ID!
    name: String!
    githubID: String!
    favoriteProgramingLanguage: String
    }
    type Designer implements Member {
    id: ID!
    name: String!
    githubID: String!
    adobeID: String!
    }

    View Slide

  10. GraphQLのスキーマ
    ● nullable(null許容)な値と
    non-nullable(非null許容)な値がある
    ○ 型名の後ろに ! がついているとnon-nullable
    ○ 👉の例ではid, nameはnon-nullable
    ■ favoriteProgramingLanguageはnullable
    ● バックエンド/フロントエンドともに
    共通してスキーマを使う
    ○ 型情報は両者ともに共通して知っている
    interface Member {
    id: ID!
    name: String!
    githubID: String!
    }
    type Engineer implements Member {
    id: ID!
    name: String!
    githubID: String!
    favoriteProgramingLanguage: String
    }
    type Designer implements Member {
    id: ID!
    name: String!
    githubID: String!
    adobeID: String!
    }

    View Slide

  11. GraphQLのfragment
    ● GraphQLはfragmentを使うと、必要なデータを
    宣言的に定義することができる
    ● 👉ではInterface Memberの中から必要なデータを
    WebAccountとして宣言している
    ○ 全MemberはgithubIDを取得できる
    ○ … on 型名でInterfaceの具体的な型の場合取得するデータを
    宣言することができる
    ■ Type DesignerならAdobe IDが取得できる
    fragment WebAccount on Member {
    id
    githubID
    ... on Designer {
    adobeID
    }
    }

    View Slide

  12. GraphQLをフロントエンドで使う
    ● GraphQL Code Generatorなどを使うとスキーマからTypeScriptの型を生成できる
    ○ スキーマ上の型に対応したTypeScriptの型が生成される
    ○ fragmentに対応した型(fragmentで拾ってきたい情報)も自動生成される
    ● スキーマ上の型がTypeScriptの型に直接対応する
    ○ InterfaceやInterfaceの実装の型はいい感じにTypeScriptの型表現にマッピングされる
    ○ nullable, non-nullableな型も考慮される
    ● 先述のfragmentをうまく使うと、コンポーネントに必要なデータをモジュール化できる
    ○ fragmentはコンポーネントに閉じるので、具体的に何が必要か使う側では知らずに渡せる

    View Slide

  13. GraphQLとフロントエンド
    ● fragmentで引いた値をそのままコンポーネントに渡すことができる
    ○ GraphQLスキーマとコンポーネント (UI)がうまく対応できるようにスキーマを考える必要がある
    👆コンポーネント使う側
    👆コンポーネントの引数 memberにfragmentで引いた値をいれるだけでよい
    interface Props {
    member: WebAccountFragment;
    }
    export const WebAccount: React.VFC = ({ member }) => {
    return (

    {user.__typename === 'Designer' && Adobe ID: {member.adobeID} }


    );
    };
    fragment GitHubName on Member {
    id
    githubID
    }
    👆コンポーネント定義側
    fragment WebAccount on Member {
    id
    githubID
    ... on Designer {
    adobeID
    }
    }

    View Slide

  14. GraphQLとフロントエンド
    ● fragmentで引いた値をそのままコンポーネントに渡すことができる
    ○ GraphQLスキーマとコンポーネント (UI)がうまく対応できるようにスキーマを考える必要がある
    👆コンポーネント使う側
    👆コンポーネントの引数 memberにfragmentで引いた値をいれるだけでよい
    interface Props {
    member: WebAccountFragment;
    }
    export const WebAccount: React.VFC = ({ member }) => {
    return (

    {user.__typename === 'Designer' && Adobe ID: {member.adobeID} }


    );
    };
    fragment GitHubName on Member {
    id
    githubID
    }
    👆コンポーネント定義側
    fragment WebAccount on Member {
    id
    githubID
    ... on Designer {
    adobeID
    }
    }
    … fragment名で、fragmentに定義され
    ているデータを持ってくることができる

    View Slide

  15. GraphQLとフロントエンド
    ● fragmentで引いた値をそのままコンポーネントに渡すことができる
    ○ GraphQLスキーマとコンポーネント (UI)がうまく対応できるようにスキーマを考える必要がある
    👆コンポーネント使う側
    👆コンポーネントの引数 memberにfragmentで引いた値をいれるだけでよい
    interface Props {
    member: WebAccountFragment;
    }
    export const WebAccount: React.VFC = ({ member }) => {
    return (

    {user.__typename === 'Designer' && Adobe ID: {member.adobeID} }


    );
    };
    fragment GitHubName on Member {
    id
    githubID
    }
    👆コンポーネント定義側
    fragment WebAccount on Member {
    id
    githubID
    ... on Designer {
    adobeID
    }
    }
    コンポーネントに必要な fragmentを引い
    ているので、そのままオブジェクトを渡す
    ことができる

    View Slide

  16. GraphQLとフロントエンド
    ● fragmentで引いた値をそのままコンポーネントに渡すことができる
    ○ GraphQLスキーマとコンポーネント (UI)がうまく対応できるようにスキーマを考える必要がある
    👆コンポーネント使う側
    👆コンポーネントの引数 memberにfragmentで引いた値をいれるだけでよい
    interface Props {
    member: WebAccountFragment;
    }
    export const WebAccount: React.VFC = ({ member }) => {
    return (

    {user.__typename === 'Designer' && Adobe ID: {member.adobeID} }


    );
    };
    fragment GitHubName on Member {
    id
    githubID
    }
    👆コンポーネント定義側
    fragment WebAccount on Member {
    id
    githubID
    ... on Designer {
    adobeID
    }
    }
    Interfaceが 特定の型だったときのみ
    持ってくるデータが書ける
    … on Designer と対応した記述ができる
    (designerだったらAdobe IDを表示する)

    View Slide

  17. GraphQLまとめ
    ● GraphQLにはスキーマがある
    ● スキーマにはInterface、Interfaceの実装の型がある
    ● フィールドはnullableとnon-nullableのものがある
    ● フロントエンドではgraphqlのfragmentを使うとコンポーネント内に必要なデータを
    閉じ込めることができる
    ○ コンポーネントに1オブジェクトだけ渡せば済むようになり最高
    ● Interfaceが特定の型だったときに色々する条件がかける
    ○ 特定のデータを持ってくる、型で分岐が書ける

    View Slide

  18. もくじ
    ● GraphQLとは
    ● カクヨムとGraphQL
    ● 1から考えるスキーマ設計
    ● 動かしながらスキーマを変更する
    ● まとめ

    View Slide

  19. カクヨム
    ● KADOKAWA様とはてなで共同開発しているWeb小説サイト
    ○ Webアプリ以外にスマホアプリも提供している

    View Slide

  20. カクヨムとGraphQL
    ● スマホアプリで先行してGraphQLを利用
    ● 最近Webサイト部分でもGraphQLを利用しはじめた

    View Slide

  21. もくじ
    ● GraphQLとは
    ● カクヨムとGraphQL
    ● 1から考えるスキーマ設計
    ● 動かしながらスキーマを変更する
    ● まとめ

    View Slide

  22. 1から考えるスキーマ設計
    ● カクヨムの「近況ノート」に新機能を提供予定
    ● この新機能を加味したGraphQLスキーマを1から考えた
    ○ 今日はこのスキーマ設計で考えたことを話します

    View Slide

  23. 近況ノートとは
    ● 近況ノート
    ○ 小説の紹介や更新情報、読書履歴やおすすめ作品の紹介を投稿でき
    る機能
    ○ 最近画像が投稿できるようになった

    View Slide

  24. カクヨムロイヤルティプログラム第二弾

    View Slide

  25. 限定近況ノート
    ● 普通の近況ノートは誰でも読める
    ● 限定近況ノートは読めないユーザーもいる
    ● 共通して次のようなUIを作りたい

    View Slide

  26. 作りたいUI例
    タイトル
    本文です
    タイトル
    [限定]
    タイトル
    本文です
    [限定]

    View Slide

  27. 作りたいUI例
    タイトル
    本文です
    タイトル
    [限定]
    タイトル
    本文です
    [限定]
    普通の近況ノートは
    本文が読める

    View Slide

  28. 作りたいUI例
    タイトル
    本文です
    タイトル
    [限定]
    タイトル
    本文です
    [限定]
    読めない限定近況ノートはタ
    イトル + 限定であることがわ
    かる

    View Slide

  29. 作りたいUI例
    タイトル
    本文です
    タイトル
    [限定]
    タイトル
    本文です
    [限定]
    読める限定近況ノートは
    本文と限定であることが表示
    される

    View Slide

  30. 👉GraphQLスキーマの前に
    DBスキーマを確認

    View Slide

  31. データベース上の限定近況ノートの扱い
    ● データベース上では限定近況ノートかそうでないかは、近況ノートのテーブルのフラ
    グで管理している
    ○ 実際にこの近況ノートが読めるかどうかはバックエンドで管理している
    ● データベースのスキーマとGraphQLのスキーマを
    一致させる目線に立つと、1つの型で表現する必要
    性が出てくる
    id
    title
    body
    is_limited
    author_account_id

    View Slide

  32. 👉GraphQLスキーマを設計していくぞ!!!

    View Slide

  33. 今日の内容
    ● GraphQLスキーマの設計は難しい
    ● DBスキーマと合わせて設計するのが正解? それとも?
    ● 型やフィールドで何を表現するか
    ● バックエンド、フロントエンドの事情とスキーマの表現

    View Slide

  34. 検討1: 1つの型で表現仕切る
    ● DBの近況ノートのスキーマをGraphQLで表現する形

    View Slide

  35. 検討1: 1つの型で表現仕切る
    ● DBの近況ノートのスキーマをGraphQLで表現する形
    type UserNewsEntry {
    id: ID!
    title: String!
    body: String
    isLimited: bool!
    visitorCanRead: bool!
    }

    View Slide

  36. 検討1: 1つの型で表現仕切る
    ● DBの近況ノートのスキーマをGraphQLで表現する形
    ○ 読めないケースもあるので bodyをnull許容
    ○ 閲覧者が読めるかどうかを別でもたせた
    type UserNewsEntry {
    id: ID!
    title: String!
    body: String
    isLimited: bool!
    visitorCanRead: bool!
    }

    View Slide

  37. 検討1: 1つの型で表現仕切る
    ● pros
    ○ 1つの型で表現仕切ることができる
    ■ DBスキーマと対応
    ○ nullableな型をうまく利用している
    ● cons
    ○ スキーマ上で「読めない限定近況ノートの場合、必ず本文にアクセスできないこと」
    が表現できない
    ■ ともすればアクセスできてしまうのではという不安
    ○ 「限定近況ノートでかつ読めない場合」をフロントで表現しようとすると、判断に必要
    なフィールドが多い
    ■ 型レベルで読める読めないの判断がしづらい
    ● フロントエンドはTypeScriptで開発しているので型を活かしたい

    View Slide

  38. 検討2: 普通/限定の近況ノートで型を分ける
    ● 1つの型ではなくて型を分ける方針で考えた
    ● 今回の型定義は普通/限定に着目した

    View Slide

  39. 検討2: 普通/限定の近況ノートで型を分ける
    interface AbstractUserNewsEntry {
    id: ID!
    title: String!
    }
    # 普通の近況ノート
    type NormalUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    title: String!
    # ここから下がNormal用
    body: String!
    }
    # 限定近況ノート
    type LimitedUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    title: String!
    # ここから下がNormal用
    body: String # ないケースもあるので
    null許容
    }

    View Slide

  40. 検討2: 普通/限定の近況ノートで型を分ける
    interface AbstractUserNewsEntry {
    id: ID!
    title: String!
    }
    # 普通の近況ノート
    type NormalUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    title: String!
    # ここから下がNormal用
    body: String!
    }
    # 限定近況ノート
    type LimitedUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    title: String!
    # ここから下がNormal用
    body: String # ないケースもあるので
    null許容
    }
    共通してアクセス可能な部
    分をInterfaceに集約する

    View Slide

  41. 検討2: 普通/限定の近況ノートで型を分ける
    interface AbstractUserNewsEntry {
    id: ID!
    title: String!
    }
    # 普通の近況ノート
    type NormalUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    title: String!
    # ここから下がNormal用
    body: String!
    }
    # 限定近況ノート
    type LimitedUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    title: String!
    # ここから下がNormal用
    body: String # ないケースもあるので
    null許容
    }
    普通の近況ノートの場合は
    本文は読めるのでbodyは
    nonnullable

    View Slide

  42. 検討2: 普通/限定の近況ノートで型を分ける
    interface AbstractUserNewsEntry {
    id: ID!
    title: String!
    }
    # 普通の近況ノート
    type NormalUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    title: String!
    # ここから下がNormal用
    body: String!
    }
    # 限定近況ノート
    type LimitedUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    title: String!
    # ここから下がNormal用
    body: String # ないケースもあるので
    null許容
    }
    対して限定近況ノートの場合
    は読めない可能性があるの
    で本文はnull許容

    View Slide

  43. 検討2: 普通/限定の近況ノートで型を分ける
    ● pros
    ○ 型レベルで普通か限定かを判断できる
    ■ 共通部分はInterface化しているので扱いやすい
    ○ nullableな値を活かしている
    ● cons
    ○ 限定近況ノートの場合、bodyがnullかどうかで読めるかどうかを判定
    する必要がある
    ○ 画一的にbodyにアクセスしたい場合、分岐が複雑

    View Slide

  44. ヨッシャこれで決まり!!

    View Slide

  45. ヨッシャこれで決まり!!
    ではない!!!

    View Slide

  46. 作りたいUI例
    タイトル
    本文です
    タイトル
    [限定]
    タイトル
    本文です
    [限定]

    View Slide

  47. 作りたいUI例
    タイトル
    本文です
    タイトル
    [限定]
    タイトル
    本文です
    [限定]
    body: String!

    View Slide

  48. 作りたいUI例
    タイトル
    本文です
    タイトル
    [限定]
    タイトル
    本文です
    [限定]
    body: String!
    body: String

    View Slide

  49. 作りたいUI例
    タイトル
    本文です
    タイトル
    [限定]
    タイトル
    本文です
    [限定]
    body: String!
    body: String
    bodyの型が
    コンフリクト

    View Slide

  50. このUIを実現しようとするとエラーが
    ● フロントこのUIを満たすfragmentを定義したところバチバチに怒られる
    ○ 同じbodyというフィールドなのに型が String!とStringで違うから無理!!!



    ● Interfaceの実装の型において同名のフィールドは同じ型の方が望ましそう
    ○ 読めない限定近況ノートの場合は bodyがnullの可能性がある
    ○ 今回のスキーマのままいくと、普通の近況ノートでも bodyをString!にしなければならない
    ● 今回は明らかにこのスキーマだと作りたいUIが表現できないので再考
    GraphQLDocumentError: Fields "body" conflict because they return conflicting types
    "String!" and "String". Use different aliases on the fields to fetch both if this was
    intentional.

    View Slide

  51. 検討3: 限定近況ノートを読める/読めないで分ける
    ● フィールドの型を同じにしたい
    ● 極力型を使って状態を表現したい
    ○ 近況ノートと閲覧者の状況を素直に型にするのを考えた
    ● 状態を型にマッピングすることを考えた

    View Slide

  52. 検討3: 限定近況ノートを読める/読めないで分ける
    interface AbstractUserNewsEntry {
    id: ID!
    title: String!
    }
    type NormalUserNewsEntry implements
    AbstractUserNewsEntry {
    id: ID!
    title: String!
    #近況ノート本文
    body: String!
    }
    type LimitedUserNewsEntryCanRead implements
    AbstractUserNewsEntry {
    id: ID!
    title: String!
    # ここから下がLimitedUserNewsEntryCanRead用
    body: String!
    }
    type LimitedUserNewsEntryCanNotRead
    implements AbstractUserNewsEntry {
    id: ID!
    title: String!
    }
    公開情報をinterfaceに

    View Slide

  53. 検討3: 限定近況ノートを読める/読めないで分ける
    interface AbstractUserNewsEntry {
    id: ID!
    title: String!
    }
    type NormalUserNewsEntry implements
    AbstractUserNewsEntry {
    id: ID!
    title: String!
    #近況ノート本文
    body: String!
    }
    type LimitedUserNewsEntryCanRead implements
    AbstractUserNewsEntry {
    id: ID!
    title: String!
    # ここから下がLimitedUserNewsEntryCanRead用
    body: String!
    }
    type LimitedUserNewsEntryCanNotRead
    implements AbstractUserNewsEntry {
    id: ID!
    title: String!
    }
    普通の近況ノートは必ず
    本文にアクセスできる

    View Slide

  54. 検討3: 限定近況ノートを読める/読めないで分ける
    interface AbstractUserNewsEntry {
    id: ID!
    title: String!
    }
    type NormalUserNewsEntry implements
    AbstractUserNewsEntry {
    id: ID!
    title: String!
    #近況ノート本文
    body: String!
    }
    type LimitedUserNewsEntryCanRead implements
    AbstractUserNewsEntry {
    id: ID!
    title: String!
    # ここから下がLimitedUserNewsEntryCanRead用
    body: String!
    }
    type LimitedUserNewsEntryCanNotRead
    implements AbstractUserNewsEntry {
    id: ID!
    title: String!
    }
    読める近況ノートも同様
    に本文にアクセスできる

    View Slide

  55. 検討3: 限定近況ノートを読める/読めないで分ける
    interface AbstractUserNewsEntry {
    id: ID!
    title: String!
    }
    type NormalUserNewsEntry implements
    AbstractUserNewsEntry {
    id: ID!
    title: String!
    #近況ノート本文
    body: String!
    }
    type LimitedUserNewsEntryCanRead implements
    AbstractUserNewsEntry {
    id: ID!
    title: String!
    # ここから下がLimitedUserNewsEntryCanRead用
    body: String!
    }
    type LimitedUserNewsEntryCanNotRead
    implements AbstractUserNewsEntry {
    id: ID!
    title: String!
    }
    読める近況ノートも同様
    に本文にアクセスできる
    読めない限定近況ノート
    はInterfaceの内容(公開
    情報)しかアクセス出来
    ない

    View Slide

  56. 検討3: 限定近況ノートを読める/読めないで分ける
    ● pros
    ○ 読めない場合にアクセスできない範囲がスキーマで十分表現できてい

    ■ 逆に読める場合にアクセスできる範囲も表現できている
    ○ 型の状況と近況ノートの状態がマッチしている
    ● cons
    ○ 型が多い!!!!!!

    View Slide

  57. 型が多いことによる問題
    Normal
    LimitedCanRead
    Limited
    CanNotRead
    読める近況ノート 限定近況ノート

    View Slide

  58. 型が多いことによる問題
    Normal
    LimitedCanRead
    Limited
    CanNotRead
    読める近況ノート 限定近況ノート
    型と状態のグループ分けをバックエンド /フロントエンド共に行わないといけない !!!
    👉 状態を決定する責務がバックエンド /フロントに散ってしまう

    View Slide

  59. 再考

    View Slide

  60. 検討4: 読める/読めないでの型定義
    ● 閲覧者が対象の近況ノートを読めるか読めないかで型を分けることを考えた
    ● 限定近況ノートはフィールドで表現する

    View Slide

  61. interface AbstractUserNewsEntry {
    id: ID!
    author: User!
    title: String!
    # 限定近況ノートであるかどうか
    isLimited: Boolean!
    }
    # 本文を読むことができる近況ノート
    type ReadableUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    author: User!
    title: String!
    # 限定近況ノートであるかどうか
    isLimited: Boolean!
    # 以下 ReadableUserNewsEntry用
    # 近況ノート本文
    body: String!
    }
    # 本文を読むことができない近況ノート
    # 公開情報しか参照することができない
    type UnreadableUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    author: User!
    title: String!
    # 限定近況ノートであるかどうか
    isLimited: Boolean!
    }

    View Slide

  62. interface AbstractUserNewsEntry {
    id: ID!
    author: User!
    title: String!
    # 限定近況ノートであるかどうか
    isLimited: Boolean!
    }
    # 本文を読むことができる近況ノート
    type ReadableUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    author: User!
    title: String!
    # 限定近況ノートであるかどうか
    isLimited: Boolean!
    # 以下 ReadableUserNewsEntry用
    # 近況ノート本文
    body: String!
    }
    # 本文を読むことができない近況ノート
    # 公開情報しか参照することができない
    type UnreadableUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    author: User!
    title: String!
    # 限定近況ノートであるかどうか
    isLimited: Boolean!
    }
    常にアクセスできる公開情報を
    Interfaceとして定義

    View Slide

  63. interface AbstractUserNewsEntry {
    id: ID!
    author: User!
    title: String!
    # 限定近況ノートであるかどうか
    isLimited: Boolean!
    }
    # 本文を読むことができる近況ノート
    type ReadableUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    author: User!
    title: String!
    # 限定近況ノートであるかどうか
    isLimited: Boolean!
    # 以下 ReadableUserNewsEntry用
    # 近況ノート本文
    body: String!
    }
    # 本文を読むことができない近況ノート
    # 公開情報しか参照することができない
    type UnreadableUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    author: User!
    title: String!
    # 限定近況ノートであるかどうか
    isLimited: Boolean!
    }
    常にアクセスできる公開情報を
    Interfaceとして定義
    限定近況ノートであるかどうかは
    Booleanのフィールドとして定義

    View Slide

  64. interface AbstractUserNewsEntry {
    id: ID!
    author: User!
    title: String!
    # 限定近況ノートであるかどうか
    isLimited: Boolean!
    }
    # 本文を読むことができる近況ノート
    type ReadableUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    author: User!
    title: String!
    # 限定近況ノートであるかどうか
    isLimited: Boolean!
    # 以下 ReadableUserNewsEntry用
    # 近況ノート本文
    body: String!
    }
    # 本文を読むことができない近況ノート
    # 公開情報しか参照することができない
    type UnreadableUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    author: User!
    title: String!
    # 限定近況ノートであるかどうか
    isLimited: Boolean!
    }
    読める近況ノート == 必ず本文に
    アクセスできるのでnonnullで宣言
    している

    View Slide

  65. interface AbstractUserNewsEntry {
    id: ID!
    author: User!
    title: String!
    # 限定近況ノートであるかどうか
    isLimited: Boolean!
    }
    # 本文を読むことができる近況ノート
    type ReadableUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    author: User!
    title: String!
    # 限定近況ノートであるかどうか
    isLimited: Boolean!
    # 以下 ReadableUserNewsEntry用
    # 近況ノート本文
    body: String!
    }
    # 本文を読むことができない近況ノート
    # 公開情報しか参照することができない
    type UnreadableUserNewsEntry implements AbstractUserNewsEntry {
    id: ID!
    author: User!
    title: String!
    # 限定近況ノートであるかどうか
    isLimited: Boolean!
    }
    読める近況ノート == 必ず本文に
    アクセスできるのでnonnullで宣言
    している
    読めない近況ノートは公開範囲
    (Intefaceに定義されている内容 )し
    かアクセスできない

    View Slide

  66. 検討4: 読める/読めないでの型定義
    ● pros
    ○ 読めない場合にアクセスできない範囲がスキーマで十分表現できてい

    ■ 逆に読める場合にアクセスできる範囲も表現できている
    ○ 型の恩恵がフロントエンドで得やすくなった
    ■ 例えば「読める場合は特定のUIをだす」のような分岐が簡単
    ○ 読める/読めない判定が完全にバックエンドの責務になっている
    ○ 型での表現とフィールドを使った表現のバランスを取っている
    ● cons
    ○ Interfaceっぽくはない使い方....

    View Slide

  67. フロントエンドでも安心
    ● bodyにアクセスしたい場合は必ずReadableであることを示す必要がでる
    ○ 型の絞り込みをせずにフィールドにアクセスするとエラーになる
    ○ TypeScriptで開発している限りは型で安心してコードが書ける

    View Slide

  68. 最終的なスキーマ
    ● 「読める/読めないで型を分ける」を採用した
    ○ バックエンドは閲覧者の状況によって型をいい感じに返すように
    ○ 型を分けたことで必ず公開情報しかクライアントに渡らないような安心感が得られた
    ● 今日プロフィールページがReactで動くようになったので実際に動作するように

    View Slide

  69. もくじ
    ● GraphQLとは
    ● カクヨムとGraphQL
    ● 1から考えるスキーマ設計
    ● まとめ

    View Slide

  70. まとめ
    ● 素朴にDBスキーマと合わせるのが必ずしも正解ではない
    ● 型で表現するのとフィールド(素朴なbooleanなど)で表現した方が自然なのかはモ
    ノによって異なる
    ○ 近況ノートでは読めるかどうかは型にすることでアクセス制限が作れた
    ○ 限定近況ノートかどうかはフィールドで判断することで素直にアクセス出来た
    ● スキーマの構成はフロントエンドの使われ方も考慮する必要がある
    ○ 実現したいUIをスキーマで素朴に表現できるのか
    ○ 使ってみるとエラーが出るケースがある
    ○ バックエンド/フロントエンドの責務とスキーマが自然に対応しているか

    View Slide