GraphQL(Apollo)

6497 ワード

1.基礎


Apolloは、フロントエンドとバックエンドの間の通信層(=a unified graph)を作成するためのプラットフォームです.graphの中心にはGraphQLというquery言語があります.
GraphQLを使うのはいろいろな理由がありますが、その最大の原因はバックエンドAPIがどれだけ変化しても、フロントエンドの観点からは大きな変化はありません.すなわち、バックエンドでAPIに新しいタイプまたはフィールドを容易に追加することができ、クライアントでも新しい追加/変更されたフィールドを容易に使用することができる.それだけでなく,REST APIが2回要求したとしても,GraphQLを用いて1回のクエリが可能な場合が多い.
また、バックグラウンドで最も必要なフィールドのみが提供されるため、ネットワークコストを節約できます.
しかし、GraphQLを使用すると学習コストが増加し、フロントエンドの要件に従ってバックエンドにデータを準備しようとすると、パフォーマンスに悪影響を及ぼす可能性があるため、アーキテクチャの設計が重要です.また、Webブラウザは通常URLに基づいてキャッシュされるが、GraphQLはWebクライアントで使用する場合、同じURLでも異なるクエリーを送信できるため、キャッシュの結果が信じられないという欠点がある.
GraphQLでは、タイプは大体scalar typeobject typeであり、両者を区別して使用すれば、アーキテクチャを定義する際に大きな困難はありません.また、作成する図でサポートされているクエリーはQueryのspecial type(root type)として宣言でき、サポートされているデータ変更クエリーはMutationのspecial type(root type)として宣言できます.
なお、Queryは複数のサーバを受信したときに同時に実行され、Mutationは順次実行される.すなわち,特定のフィールド値+1を2回行うと,正確には+2となる.これらの部分はGraphQLによって管理されています.詳細については、以下の公式ドキュメントの原文を参照してください...!
A single mutation operation can include multiple top-level fields of the Mutation type. This usually means that the operation will execute multiple back-end writes (at least one for each field). To prevent race conditions, top-level Mutation fields are resolved serially in the order they're listed (all other fields can be resolved in parallel).

2.タイプの種類


GraphQLでは、次のタイプがサポートされています.

(1) Scalar types


一般的なプログラミング言語ではprimitive typeと考えられる.

1) Int: A signed 32-bit intger


2) Float: A signed double-precision floating-point value


3) String: A UTF-8 character sequence


4) Boolean: true or false


5)ID(Serialized as a String):refetchまたはcacheキーとして使用される一意の識別子であり、Stringとして表されるが、読み取り不能である


(2) Object types


GraphQLアーキテクチャで定義したほとんどのタイプはこのタイプです.objecttypeは、それぞれのタイプのフィールドで構成されています.
作成したアーキテクチャ内のすべてのobjecttypeには、対応するobjecttypeの名前をStringに返す__typenameというフィールドが自動的に存在することに注意してください.(ex. Book, Author...)
GraphQLクライアントは、この__typenameをキャッシュに使用するか、または様々なタイプの(unionまたはinterface)フィールドを返すことができるタイプを決定するために使用します.

(3) Query type


このタイプはSpecialobjecttypeと見なすことができる.クライアントがサーバに要求できるクエリーのすべてのtop-levelエントリポイントを示します.

(4) Mutation type


Query typeと同様に、Query typeが可読opeartionのためだけである場合、Mutation typeは書き込み操作に使用される特殊なオブジェクトタイプである.

(5) Input type


Input typeは、フィールドパラメータとして階層化されるデータを提供する特殊なobjectタイプです.たとえば、Input typeは次のように定義できます.
input BlogPostContent {
  title: String
  body: String
  media: [MediaDetails!]
}

input MediaDetails {
  format: MediaFormat!
  url: String!
}

enum MediaFormat {
  IMAGE
  VIDEO
}

type Mutation {
  createBlogPost(content: BlogPostContent!): Post
  updateBlogPost(id: ID!, content: BlogPostContent!): Post
}
QueryタイプとMultiタイプの同じフィールドについては、同じInputを共有するよりも、それらを分離することに注意してください.

(6) Enum types


値のタイプは、特定の値からのみ選択できます.
enum AllowedColor {
  RED
  GREEN
  BLUE
}
Enumは、任意のスカラータイプで有効な場所に使用できます.それはいつもstringにつながっているからです.

(7) Unions and Interfaces


この2つは抽象タイプであり、GraphQL内の特定のフィールドが異なるobjectタイプで1つ戻ることを可能にします.

(1) Uniton type

union Media = Book | Movie

type Query {
  allMedia: [Media] # This list can include both Book and Movie objects
}
Unionに含まれるタイプはobjectタイプでなければなりません.(scalar type X, input type X ...)
unionに含まれるタイプは、必ずしも特定のフィールドを共有する必要はありません.
では、Union typeを返すクエリーについて、どのように要求すればいいのでしょうか.下を見る
query GetSearchResults {
  search(contains: "Shakespeare") {
    # Querying for __typename is almost always recommended,
    # but it's even more important when querying a field that
    # might return one of multiple types.
    __typename
    ... on Book {
      title
    }
    ... on Author {
      name
    }
  }
}
これにより、各タイプに応じて異なるフィールドを設定できます.(現在はinline fragmentを使用しています)

(2) Interface


名前の通り、インタフェースを使用してインタフェースを実装するタイプには、インタフェース内のすべてのフィールドが含まれている必要があります.
interface Book {
  title: String!
  author: Author!
}

type Textbook implements Book {
  title: String!
  author: Author!
  courses: [Course!]!
}

type ColoringBook implements Book {
  title: String!
  author: Author!
  colors: [String!]!
}

type Query {
  books: [Book!]!
}
この場合、bookというクエリーが発行されると、TextBookまたはColoringBookが配列に表示されます.
また、Union typeと同様に、実際のobjecttypeをチェックして、各objecttypeに必要な異なるフィールドを表示することもできます.
query GetBooks {
  books {
    # Querying for __typename is almost always recommended,
    # but it's even more important when querying a field that
    # might return one of multiple types.
    __typename
    title
    ... on Textbook {
      courses { # Only present in Textbook
        name
      }
    }
    ... on ColoringBook {
      colors # Only present in ColoringBook
    }
  }
}
Union typeまたはInterface typeを使用するには、次のように実際のtypeを解析するために__resolveTypeという関数を定義する必要があります.
const resolvers = {
  Book: {
    __resolveType(book, context, info){
      // Only Textbook has a courses field
      if(book.courses){
        return 'Textbook';
      }
      // Only ColoringBook has a colors field
      if(book.colors){
        return 'ColoringBook';
      }
      return null; // GraphQLError is thrown
    },
  },
  Query: {
    books: () => { ... }
  },
};

3. Descriptions(docstrings)


モードには、次のタグ形式のコメントを付けることができます.
"Description for the type"
type MyObjectType {
  """
  Description for field
  Supports **multi-line** description for your [API](http://example.com)!
  """
  myField: String!

  otherField(
    "Description for argument"
    arg: Int
  )
}

4.GraphQL使用時の注意点


(1)GraphQLアーキテクチャは、バックエンドがどのようにデータを格納するかに関係なく、フロントエンドに必要なデータの周りに設計されるべきである.たとえば、各フィールドが実際にサーバに格納されるデータストレージは異なる場合がありますが、フロントエンドはこれらを知る必要はありません.これがGraphQLの目的だからです.
(2)Mutationを使用する場合は,その操作により生成または修正されたデータを応答としてアーキテクチャ設計を行うことが望ましい.これにより、フロントはフォローアップクエリを発行する必要がなく、生成/更新されたデータを表示できます.また、オブジェクトタイプを直接戻りタイプに設定するよりも、
type LikePostMutationResponse {
	code: String!
    success: Boolean!
    message: String!
    user: User
}

成功/失敗の有無と詳細を含むmetaフィールドをこのように追加して定義することが望ましい.