1. ホーム
  2. graphql

[解決済み] GraphQLのinput typeは何のためにあるのか?

2022-06-11 23:54:02

質問

突然変異の入力引数がオブジェクトの場合、なぜ 入力型 ? もっとシンプルに タイプ を再利用するだけです。

例えば

type Sample {
  id: String
  name: String
}

input SampleInput {
  name: String
}

type RootMutation {
  addSample(sample: Sample): Sample  # <-- instead of it should be
  addSample(sample: SampleInput): Sample
}

小さなオブジェクトの場合はいいのですが、スキーマに10以上のプロパティを持つオブジェクトがたくさんある場合は、それが負担になるでしょう。

どのように解決するのか?

仕様書から。

GraphQL の Object 型 (ObjectTypeDefinition) は... [入力として] 再使用するには不適切です。なぜなら Object 型には引数を定義するフィールドや、インターフェースやユニオンへの参照を含むことができますが、どちらも入力引数として使用するには適切ではないからです。このため、入力オブジェクトはシステム内で別の型を持っています。

これが "公式の理由"ですが、オブジェクト型を入力オブジェクト型として使用できない、またはオブジェクト型を入力オブジェクト型として使用できない、いくつかの実際的な理由があります。

機能性

オブジェクトタイプと入力オブジェクトタイプは両方ともフィールドを持ちますが、これらのフィールドはスキーマによってこれらのタイプがどのように使用されるかを反映した異なるプロパティを持ちます。スキーマは潜在的にオブジェクトタイプのフィールドのための引数とある種のリゾルバ関数を定義するでしょうが、これらのプロパティは入力コンテキストでは意味を持ちません (すなわち、入力オブジェクトのために を解決する を解決することはできません -- それはすでに明示的な値を持っているのです)。同様に、デフォルト値は入力オブジェクトタイプのフィールドにのみ提供でき、オブジェクトタイプのフィールドには提供できません。

言い換えれば、これは重複しているように見えるかもしれません。

type Student {
  name: String
  grade: Grade
}

input StudentInput {
  name: String
  grade: Grade
}

しかし、オブジェクト型と入力オブジェクト型のどちらかに特化した機能を追加することで、両者の動作が異なることが明らかになります。

type Student {
  name(preferred: Boolean): String
  grade: Grade
}

input StudentInput {
  name: String
  grade: Grade = F
}

型システムの制限

GraphQL における型は、以下のようにグループ化されています。 出力タイプ 入力タイプ .

出力タイプは、GraphQL サービスによって生成される応答の一部として返される可能性があるタイプです。入力タイプは、フィールドやディレクティブの引数の有効な入力となるタイプです。

これら2つのグループには重複があります(スカラー、列挙、非NULLなど)。しかし 抽象型 は入力コンテキストでは意味をなさないので、入力として使用することはできません。オブジェクト型と入力オブジェクト型を分離することで、入力型が期待される場所で抽象型が使われることがないようにすることができます。

スキーマ設計

スキーマでエンティティを表現するとき、いくつかのエンティティはそれぞれの入力と出力の型の間で実際に "フィールド" を共有する可能性があります。

type Student {
  firstName: String
  lastName: String
  grade: Grade
}

input StudentInput {
  firstName: String
  lastName: String
  grade: Grade
}

しかし、オブジェクト型は非常に複雑なデータ構造をモデル化することができます(そして、現実には頻繁に行われています)。

type Student {
  fullName: String!
  classes: [Class!]!
  address: Address!
  emergencyContact: Contact
  # etc
}

これらの構造体に対して 例えば、生徒のクラスをオブジェクトではなく、クラス ID とセクション ID で指定する必要があるかもしれません。同様に、返したいけれども変異させたくないフィールドがあるかもしれませんし、 その逆もあります (たとえば password フィールドのように)。

さらに、比較的単純なエンティティであっても、オブジェクトタイプとその対応する入力オブジェクトの間で、nullabilityに関する異なる要件があることがよくあります。多くの場合、フィールドが応答でも返されることを保証したいのですが、入力で同じフィールドを必須にしたくありません。例えば

type Student {
  firstName: String!
  lastName: String!
}

input StudentInput {
  firstName: String
  lastName: String
}

最後に、多くのスキーマでは、与えられたエンティティに対してオブジェクトの型と入力オブジェクトの型が一対一に対応するわけではないことがよくあります。よくあるパターンは、スキーマレベルの入力検証をさらに細かく調整するために、異なる操作に対して別々の入力オブジェクトタイプを利用することです。

input CreateUserInput {
  firstName: String!
  lastName: String!
  email: String!
  password: String!
}

input UpdateUserInput {
  email: String
  password: String
}

これらの例はすべて、重要なポイントを示しています。入力オブジェクトの型がオブジェクトの型をミラーすることはあっても、ビジネス上の要求から、本番のスキーマでそれを見ることはあまりないでしょう。