日本語

多様なグローバルオーディエンスに対応する、堅牢で保守性の高いAPIを構築するための、スケーラブルなGraphQLスキーマ設計パターンを学びます。スキーマスティッチング、フェデレーション、モジュール化をマスターしましょう。

GraphQLスキーマ設計:グローバルAPIのためのスケーラブルなパターン

GraphQLは、クライアントが必要なデータを正確にリクエストできる柔軟性を提供し、従来のREST APIに代わる強力な選択肢として登場しました。しかし、GraphQL APIが複雑化し、その範囲が広がるにつれて – 特に多様なデータ要件を持つグローバルなオーディエンスにサービスを提供する場合 – 慎重なスキーマ設計が保守性、スケーラビリティ、パフォーマンスのために不可欠になります。この記事では、グローバルアプリケーションの要求に対応できる堅牢なAPIを構築するのに役立つ、いくつかのスケーラブルなGraphQLスキーマ設計パターンを探ります。

スケーラブルなスキーマ設計の重要性

適切に設計されたGraphQLスキーマは、成功するAPIの基盤です。それは、クライアントがあなたのデータやサービスとどのように対話できるかを規定します。不適切なスキーマ設計は、以下のような多くの問題を引き起こす可能性があります:

グローバルなアプリケーションでは、これらの問題はさらに増幅されます。地域によってデータ要件、規制上の制約、パフォーマンスへの期待が異なる場合があります。スケーラブルなスキーマ設計により、これらの課題に効果的に対処することができます。

スケーラブルなスキーマ設計の主要原則

特定のパターンに飛び込む前に、スキーマ設計を導くべきいくつかの主要な原則を概説しましょう:

スケーラブルなスキーマ設計パターン

以下に、堅牢なGraphQL APIを構築するために使用できる、いくつかのスケーラブルなスキーマ設計パターンを紹介します:

1. スキーマスティッチング

スキーマスティッチングを使用すると、複数のGraphQL APIを単一の統一されたスキーマに結合できます。これは、データの異なる部分を担当する異なるチームやサービスがある場合に特に便利です。これは、いくつかのミニAPIを持ち、それらを「ゲートウェイ」APIを介して結合するようなものです。

仕組み:

  1. 各チームまたはサービスは、独自のスキーマを持つ独自のGraphQL APIを公開します。
  2. 中央のゲートウェイサービスは、スキーマスティッチングツール(Apollo FederationやGraphQL Meshなど)を使用して、これらのスキーマを単一の統一されたスキーマにマージします。
  3. クライアントはゲートウェイサービスと対話し、ゲートウェイサービスはリクエストを適切な基礎となるAPIにルーティングします。

例:

商品、ユーザー、注文用に別々のAPIを持つeコマースプラットフォームを想像してみてください。各APIには独自のスキーマがあります:

  
    # 商品API
    type Product {
      id: ID!
      name: String!
      price: Float!
    }

    type Query {
      product(id: ID!): Product
    }

    # ユーザーAPI
    type User {
      id: ID!
      name: String!
      email: String!
    }

    type Query {
      user(id: ID!): User
    }

    # 注文API
    type Order {
      id: ID!
      userId: ID!
      productId: ID!
      quantity: Int!
    }

    type Query {
      order(id: ID!): Order
    }
  

ゲートウェイサービスは、これらのスキーマをスティッチングして、統一されたスキーマを作成できます:

  
    type Product {
      id: ID!
      name: String!
      price: Float!
    }

    type User {
      id: ID!
      name: String!
      email: String!
    }

    type Order {
      id: ID!
      user: User! @relation(field: "userId")
      product: Product! @relation(field: "productId")
      quantity: Int!
    }

    type Query {
      product(id: ID!): Product
      user(id: ID!): User
      order(id: ID!): Order
    }
  

Order型が、UserProductが別のAPIで定義されているにもかかわらず、それらへの参照を含んでいることに注目してください。これは、スキーマスティッチングのディレクティブ(この例では@relationなど)を通じて実現されます。

利点:

考慮事項:

2. スキーマフェデレーション

スキーマフェデレーションは、スキーマスティッチングのいくつかの制限に対処するために設計された、その進化形です。GraphQLスキーマを構成するための、より宣言的で標準化されたアプローチを提供します。

仕組み:

  1. 各サービスはGraphQL APIを公開し、そのスキーマにフェデレーションディレクティブ(例:@key@extends@external)で注釈を付けます。
  2. 中央のゲートウェイサービス(Apollo Federationを使用)は、これらのディレクティブを使用して、フェデレーションされたスキーマ全体を表すスーパーグラフを構築します。
  3. ゲートウェイサービスは、スーパーグラフを使用してリクエストを適切な基礎となるサービスにルーティングし、依存関係を解決します。

例:

同じeコマースの例を使用すると、フェデレーションされたスキーマは次のようになります:

  
    # 商品API
    type Product @key(fields: "id") {
      id: ID!
      name: String!
      price: Float!
    }

    type Query {
      product(id: ID!): Product
    }

    # ユーザーAPI
    type User @key(fields: "id") {
      id: ID!
      name: String!
      email: String!
    }

    type Query {
      user(id: ID!): User
    }

    # 注文API
    type Order {
      id: ID!
      userId: ID!
      productId: ID!
      quantity: Int!
      user: User! @requires(fields: "userId")
      product: Product! @requires(fields: "productId")
    }

    extend type Query {
      order(id: ID!): Order
    }
  

フェデレーションディレクティブの使用に注目してください:

利点:

考慮事項:

3. モジュラースキーマ設計

モジュラースキーマ設計は、大きなモノリシックなスキーマを、より小さく管理しやすいモジュールに分割することを含みます。これにより、フェデレーションスキーマに頼ることなく、APIの個々の部分を理解、変更、再利用しやすくなります。

仕組み:

  1. スキーマ内の論理的な境界(例:ユーザー、商品、注文)を特定します。
  2. 各境界に対して別々のモジュールを作成し、その境界に関連する型、クエリ、ミューテーションを定義します。
  3. (GraphQLサーバーの実装に応じて)インポート/エクスポートメカニズムを使用して、モジュールを単一の統一されたスキーマに結合します。

例(JavaScript/Node.jsを使用):

各モジュール用に別々のファイルを作成します:

  
    // users.graphql
    type User {
      id: ID!
      name: String!
      email: String!
    }

    type Query {
      user(id: ID!): User
    }

    // products.graphql
    type Product {
      id: ID!
      name: String!
      price: Float!
    }

    type Query {
      product(id: ID!): Product
    }
  

次に、メインのスキーマファイルでそれらを結合します:

  
    // schema.js
    const { makeExecutableSchema } = require('graphql-tools');
    const { typeDefs: userTypeDefs, resolvers: userResolvers } = require('./users');
    const { typeDefs: productTypeDefs, resolvers: productResolvers } = require('./products');

    const typeDefs = [
      userTypeDefs,
      productTypeDefs,
      ""
    ];

    const resolvers = {
      Query: {
        ...userResolvers.Query,
        ...productResolvers.Query,
      }
    };

    const schema = makeExecutableSchema({
      typeDefs,
      resolvers,
    });

    module.exports = schema;
  

利点:

考慮事項:

4. インターフェース型とユニオン型

インターフェース型とユニオン型を使用すると、複数の具象型によって実装できる抽象型を定義できます。これは、文脈に応じて異なる形をとることができる多態的なデータを表現するのに便利です。

仕組み:

例:

  
    interface Node {
      id: ID!
    }

    type User implements Node {
      id: ID!
      name: String!
      email: String!
    }

    type Product implements Node {
      id: ID!
      name: String!
      price: Float!
    }

    union SearchResult = User | Product

    type Query {
      node(id: ID!): Node
      search(query: String!): [SearchResult!]!
    }
  

この例では、UserProductの両方が、共通のidフィールドを定義するNodeインターフェースを実装しています。SearchResultユニオン型は、UserまたはProductのいずれかであり得る検索結果を表します。クライアントはsearchフィールドをクエリし、その後__typenameフィールドを使用して、受け取った結果のタイプを判断できます。

利点:

考慮事項:

5. コネクションパターン

コネクションパターンは、GraphQL APIでページネーションを実装するための標準的な方法です。大量のデータリストをチャンクで取得するための一貫性のある効率的な方法を提供します。

仕組み:

例:

  
    type User {
      id: ID!
      name: String!
      email: String!
    }

    type UserEdge {
      node: User!
      cursor: String!
    }

    type UserConnection {
      edges: [UserEdge!]!
      pageInfo: PageInfo!
    }

    type PageInfo {
      hasNextPage: Boolean!
      hasPreviousPage: Boolean!
      startCursor: String
      endCursor: String
    }

    type Query {
      users(first: Int, after: String, last: Int, before: String): UserConnection!
    }
  

利点:

考慮事項:

グローバルな考慮事項

グローバルなオーディエンス向けのGraphQLスキーマを設計する際には、以下の追加要素を考慮してください:

たとえば、商品説明フィールドを考えてみましょう:


type Product {
 id: ID!
 name: String!
 description(language: String = "en"): String!
}

これにより、クライアントは特定の言語で商品説明をリクエストできます。言語が指定されていない場合、デフォルトで英語(`en`)になります。

結論

スケーラブルなスキーマ設計は、グローバルアプリケーションの要求に対応できる、堅牢で保守性の高いGraphQL APIを構築するために不可欠です。この記事で概説した原則に従い、適切な設計パターンを使用することで、理解しやすく、変更・拡張が容易で、優れたパフォーマンスとスケーラビリティを提供するAPIを作成できます。スキーマをモジュール化し、構成し、抽象化し、グローバルなオーディエンスの特定のニーズを考慮することを忘れないでください。

これらのパターンを取り入れることで、GraphQLの潜在能力を最大限に引き出し、今後何年にもわたってアプリケーションを支えることができるAPIを構築できます。