🎨

Yelp GraphQL Styleguide - 🎨スキーマ設計

2022/01/12に公開・約9,600字

これは元記事(GraphQL@Yelp Schema Design GuidelinesのGraphQL Styleguide)を著者の一人 Mark Larahさんから許可をいただき日本語訳したものです。

https://yelp.github.io/graphql-guidelines/schema-design.html

🎨 スキーマ設計

GraphQLスキーマは、Yelpのエンジニアリング全体に対してグローバルです。最終的にはユーザー向けビジネスデータ全体をGraphQLでモデル化出来るようにしたいと考えています。このページには、クリーンで使いやすくスケーラブルなスキーマを提供するために、どのように考るかについてのガイダンスが含まれています。

先に進む前に、スキーマがどのように機能するかを理解するために、そのテーマに関する公式ドキュメントをざっと読むことを強くお勧めします。

Contents

型の種類

一般的に、Yelpには2つの型の種類があります😃追加する型の種類を検討する必要があります:

  1. コア型
    Business, User 等は、Yelpにおいて多くのページやチーム、コンポーネントに共通している型です
  2. ドメイン固有の型
    これらは、1つまたは少数のコンポーネントやチームでのみ使用される型です。例えばBusinessClaimabilityやEventsPageLeftRailAttendWidgetです。

型についてこのように考えることは、私たちがコミュニケーションを取り、オーナーシップを確立するのに役立ちます。たとえば、これは私たちが以下のようなことを決定するのに役立ちます:

  • スキーマ変更に関するコードレビューでどの程度精査する必要があるか
  • リゾルバが常に失敗するようになってしまった場合の影響は何ですか

コア型は最も広く使用されており、多くのクエリのルートにあたります。そのため、通常はスキーマガイドラインを満たすための基準が高く、実行時の問題は早急にトリアージする必要があります。

トップレベルのクエリを最小に保つ

可能な限りルートノードとしてコア型を使用してYelpのデータをツリーとしてモデル化する必要があります。新しいトップレベルのクエリを追加する前に、代わりの既存のタイプの属性として追加するほうが理にかなっているかどうか検討してください。

Why?

そうすることでスキーマがトップレベルで読みやすくなり、データの構成方法の一貫性を保つことが出来ます

Example

rootの下のトップレベルのクエリとしてcanClaimBusinessを追加する代わりに、これを既存のBusinness型の属性(claimability)とする事ができます。

型の命名は具体的にする

型の名前を具体的に指定してください。(妥当な範囲で)可能な限り名前空間に!

Why?

どの型が他の型に関連しているかを明確にするのに役立ち、名前の衝突を減らします。(たとえば Price はビシネスの価格帯ですか?それともYelpイベントのチケット価格ですか?それとも他の何か?)

Example

Categoryの代わりにBusinessCategoryを優先する

フィールドに名前空間をつけない

上記の補足として、フィールドは過度に具体的/名前空間的である必要はありません。

Why?

フィールドはフィールドを持つ型によって既に暗黙的に名前空間がつけられています。つまり、フィールド名のコンテキストで親の型が何かを常に知っています。(対象的に、型はどこからでも返すことができます。どのフィールドがそれを返すかはわかりません。)

Example

  • こちらより
type BusinessAddress {
    """
    Formatted stringy version of address based on locale
    It reflects a new line using a new line character
    Example: 196 N Pleasant St\nAmherst, MA 01002
    """
    formattedBusinessAddress: String
}
  • こちらが好ましい
type BusinessAddress {
    """
    Formatted stringy version of address based on locale
    It reflects a new line using a new line character
    Example: 196 N Pleasant St\nAmherst, MA 01002
    """
    formatted: String
}

これの例外は、ルートタイプ(Query, Mutation, Subscription)のフィールドの場合です。このような「トップレベル」フィールドはグローバルであり、おそらく名前空間が必要です。

Viewではなくデータを説明する

Why?

APIが最初に作られるインターフェースの要求とあまり密接にリンクされていないことを確認してください。iOSアプリ用のAPIを構築している場合は、後でデスクトップPCやフィーチャーフォン等の他のデバイスに移植する可能性があります。クエリを作成する時は、アプリケーションの表現方法ではなく、元になるデータに注目してください。
https://www.graphql.com/articles/4-years-of-graphql-lee-byron

Example

  • Yelpの用語で言えば、Profileコンポーネントでユーザーの情報をレンダリングするためのデータを返すためにUserProfileCard型を追加しないでください。
  • 代わりに既存のUser型を使用してデータを返し、Profile cardコンポーネントにデータを入力します。

IDではなく型を介してリンクする

ある型を別の型から参照する場合は、その型に直接リンクしてください。

Why?

これにより、開発者はデータをフェッチする際の柔軟性が高まります。リンクしている型のIDのみを指定する場合、ブラウザはそのエンティティに関するデータをフェッチするために追加でクエリを実行する必要があることを意味します。

Example
こちらより

type Review {
    """
    ID of the business that this review is about
    """
    businessId: Int
}

こちらが好ましい

type Review {
    """
    The business that this review is about
    """
    business: Business
}

既存の標準化された型とスカラーを使用する

以下を表現するための既存の標準化された型があるとします。

  • 写真
  • 日付/時間

そのような情報を表すときはそれらの既存の標準化された型を使用します。

Why?

これらの型を使用すると、スキーマ全体で共通のデータ型をモデル化して使用する方法を標準化できます。これにより、作業と概念の重複を回避できます。

Example

  • こちらより
type Review {
    """
    A photo that the user uploaded with this review
    """
    uploadedPhotoUrl: String

    """
    When was this review posted?
    """
    createdAt: String
}

こちらが好ましい

type Review {
    """
    A photo that the user uploaded with this review
    """
    uploadedPhoto: BusinessPhoto

    """
    When was this review posted?
    """
    createdAt: DateTime
}

関連するフィールドをグループ化する

関連するフィールドを新しい型にグループ化し過ぎると失敗します。スキーマにネストを追加することを恐れないでください。

Why?

Business や User などのコアタイプが肥大化することを減らしたい。コアタイプのフィールド数を最小限に抑えて、既存の関連するフィールドを共通のプレフィックスの下にサブグループ下することで発見しやすくする事をお勧めします。(GraphiQLの数百のフィールドでドキュメントのサイドバーをスクロールすることを想像してみてください!)

次に、発見のしやすさは誤って他の場所に重複するフィールドを追加する事を回避することに役立ちます。

新しい型から初めて、それをまだサブグループ化する必要があるかどうか分からない場合は、Rule of threeを検討してみてください。最初に型に直接追加してもいいし、将来他の関連するタイプを追加する必要がある場合は、それを非推奨にして、この新しいパターンに移行してください。

Example

こちらより

extend type Business {
    """
    Is this business an advertiser with us?
    """
    isAdvertiser: Boolean

    """
    How many times have users clicked this business's advertisement
    """
    adClicks: Number

    """
    Amount of estimated revenue the business has earned from a campaign
    """
    revenueFromCampaign: Number

    """
    Search keywords that we should ads for this business on
    """
    adKeywords: [String]
}

こちらが好ましい

type BusinessAdvertising {
    """
    Is this business an advertiser with us?
    """
    isAdvertiser: Boolean

    """
    How many times have users clicked this business's advertisement
    """
    clicks: Number

    """
    Amount of estimated revenue the business has earned from a campaign
    """
    revenueFromCampaign: Number

    """
    Search keywords that we should ads for this business on
    """
    keywords: [String]
    }

    extend type Business {
    """
    Information related to our Business Advertising product
    """
    advertising: BusinessAdvertising
}

型がさらに大きくなると、これは時間とともに更に細かく分割される可能性があります(そしてそうするべきです!)

先行技術

説明には三重引用符(""")を使用する

フィールとに説明を追加するときは、三重引用符(""")を使用してください。 #を使用しないでください。

Why?

  • GraphQLにコメントを追加するには、引用符("""like this""")とハッシュタグ(# like this)の2つの方法があります。ただし、構文的には異なります。
  • """this is a description"""は説明であり、フィールドと型を文章化するために使用する必要があります。
  • # this is a commentはParserによっては無視されるため、スキーマのドキュメントには使用しないでください。

Example

こちらより

extend type Business {
    # The name of the business (e.g. "The French Laundry")
    name: String

    "The rating of the business"
    rating: Float
}

こちらが好ましい

extend type Business {
    """
    The name of the business (e.g. "The French Laundry")
    """
    name: String

    """
    The rating of the business
    """
    rating: Float
}

フィールドの説明はスペースを空ける

複数のフィールドと説明を追加する場合は、スペースを開けてください!(つまり、節ごとの改行)

Why?

読みやすいから!

Example

こちらより

type YelpProsEntryPointBanner {
    "Yelp Pros logo image url"
    logoUrl: String
    "Part of the heading for the banner - starting part"
    headingStart: String
    "Part of the heading for the banner - ending part"
    headingEnd: String
    "Sub heading of the banner"
    subHeading: String
}

こちらが好ましい

type YelpProsEntryPointBanner @owners(teams: "services-marketplace") {
    """
    Yelp Pros logo image url
    """
    logoUrl: String

    """
    Part of the heading for the banner - starting part
    """
    headingStart: String

    """
    Part of the heading for the banner - ending part
    """
    headingEnd: String

    """
    Sub heading of the banner
    """
    subHeading: String
}

コメントと説明で超明確にする

追加する型、フィールド、パラメーターなどについて、できるだけ多くのコンテキストを提供します。該当する箇所の設計思想から説明しましょう。

考えるべきいくつかの例:

  • 追加されるものの明確な説明
  • チーム固有の頭文字を避ける
  • 様々な選択肢がなにを表すのか分かるように、全てのEnum値について説明する
  • データや概念を定義または文章化するより正確なソースへのパーマリンク

新入社員がschemaを読んでいるとして、彼らが持っている唯一のコンテキストはあなたが書いたものだけです。あなたのコメントだけで、彼らはそれらを使用する方法を理解するのに十分な情報を得られるでしょうか?

Why?

作成したばかりの新しいスキーマを使用する将来のすべての人が、あなたと同じ知識を持っていると思い込まないでください。新入社員の立場に立ってください-スキーマが何を表しているのかを理解し、それを使用できるようにするために、できるだけ多くのコンテキストが必要です😃

Example

こちらより

extend type Business {
    """
    Return a list of Yelfies, given a yelfieType
    """
    getYelfies(
        # yelfie type
        yelfieType: String!
    ): [Yelfie]
}

こちらが好ましい

extend type Business {
    """
    Return a list of Yelfies.

    A Yelfie is a user photo taken at a business, with some special filters
    applied. See y/yelfies for more info about this feature.
    """
    getYelfies(
        """
        Provide the type of Yelfie to filter for.
        e.g. “smilingFace”, “eatingFood”

        The yelfieTypes are defined in the yelfie.json config file - see y/yelfie-type-docs
        """
        yelfieType: String!
    ): [Yelfie]
}

複雑なビジネスオブジェクトをモデル化する場合、通常は簡単な要約と詳細を見つけるためのリンクで十分です。

デフォルトでは、ほとんどのオブジェクトフィールドにnull許容型を使用しますが、多くの例外があります

詳細な推奨事項については、Nullabilityを参照してください。
Nullability

GitHubで編集を提案

Discussion

ログインするとコメントできます