在GraphQL中表示枚举+对象变体类型

acj*_*jay 1 enums algebraic-data-types discriminated-union graphql

是否有最佳实践来表示变量字段,该字段可以是具有子字段的对象,也可以是一个或多个enum类似的单例值?就像,如果只有一个单例值,可以使用可空union(如果有点笨拙,如果该值不感觉"零"),但是不止一个单例呢?

我的域模型有很多enum像这样的结构,其中一些变体不携带任何数据.我希望答案不是制作虚拟字段,以便每个变体都是一个对象类型,以满足要求union.但也许我错过了一些东西.

acj*_*jay 7

这基本上是如何在GraphQL中模拟代数数据类型的问题.特别是,如何模拟联产品,其中一些可能性是单身,其他是产品.对于那些不熟悉这个术语的人:

product - 一种数据结构,它将必须一起出现的数据组合在一起(另请参见元组,记录,数据类等).

副产物 -表示互斥的变化的数据结构(也参见工会,任一,和类型等)

单身人士 - 只有一个成员的类型.换句话说,除了自己的存在之外,它没有自由参数并且不包含任何数据.

代数数据类型 - 一组产品和联产品类型,它们相互引用(可能是递归的),也适用于单例.这允许对任意复杂的数据结构进行建模.

到目前为止,我的结论是,对此没有完全清晰和通用的答案.以下是一些方法,所有方法都有权衡:

使用null (有限)

如果您的数据结构只有一个非可选单例,则可以使用nullability来表示单例.请参阅[Herku's answer][1]列表.为了完整起见,我已将其包括在内,但它并未概括为具有多个单例的数据结构.在单身变体不一定代表缺席或空虚的情况下,它可能很尴尬.

自定义标量

如果您愿意放弃检查具有属性的变体中的内部数据的需要,则可以使整个变体成为模式中的不透明自定义标量:

scalar MyVariant # Could be whatever!
Run Code Online (Sandbox Code Playgroud)

缺点是如果你想在你的产品变体上添加更丰富的GraphQL行为,那你就不走运了.标量是查询树中的叶节点.

联合中的Singleton对象

您可以将类型表示union为常规对象type,也type可以表示您的单例选项.对于单例,您必须添加某种虚拟字段,因为对象类型必须至少有一个字段.你可以把该领域的类型名称,但是这已经可以作为__typename每个type.我们选择让它成为可空的,其价值始终是null:

union MyVariant = RealObject | Singleton1 | Singleton2

type RealObject {
  field1: String
  field2: Int
}

type Singleton1 {
  singletonDummyField: String # ALWAYS `null`
}

type Singleton2 {
  singletonDummyField: String # ALWAYS `null`  
}

type Query {
  data: MyVariant
}

# query looks like:

{
  data {
    myVariantType: __typename
    ... on RealObject {
      field1
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

因此,查询__typename满足在查询中为对象类型提供至少一个字段的需要.你永远不会查询singletonDummyField,你可以大多忘记它存在.

您可以轻松地在GraphQL服务器中创建一个帮助器,为您实现单件类型 - 它们唯一的变体是它们的名称和元数据.缺点是客户端查询获得样板.

Singleton对象实现接口

如果虚拟字段的想法令人反感,并且您希望创建自己的类型字段,则可以使其interface明确具有类型字段并使用a enum来表示类型.所以:

enum MyVariantType {
  REAL_OBJECT
  SINGLETON1
  SINGLETON2
}

interface MyVariant {
  myVariantType: MyVariantType
}

type RealObject implements MyVariant {
  myVariantType: MyVariantType
  field1: String
  field2: Int
}

type Singleton1 implements MyVariant {
  myVariantType: MyVariantType
}

type Singleton2 implements MyVariant {
  myVariantType: MyVariantType
}

type Query {
  data: MyVariant
}

# query looks like:

{
  data {
    myVariantType
    ... on RealObject {
      field1
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

所以在这里,你查询"真正的"非元myVariantType字段,单例类型有"真实"字段,即使它们在__typename字段方面是多余的.__typename当然,你可以自由地使用这种方法,但重点是什么.这里的缺点是有更多的服务器端样板来实现模式.但这也许可以成为一个帮助因素,只是更复杂一点.

组成

您可以将单例编码为enum包含在仅包含它们的对象类型中.

union MyVariant = RealObject | Singleton

type RealObject {
  field1: String
  field2: Int
}

type Singleton {
  variation: SingletonVariation!
}

enum SingletonVariation {
  SINGLETON1
  SINGLETON2
}

type Query {
  data: MyVariant
}

# query looks like:

{
  data {
    ... on RealObject {
      field1
    }
    ... on Singleton {
      variation
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这具有不求助于内省或冗余字段的优点.这里的缺点是它将单例变体分组,与产品变体分开,其方式可能没有意义.换句话说,模式的结构在GraphQL中实现是实用的,不一定代表数据.

结论

选择你的毒药.据我所知,如何以一种完全没有样板或抽象泄漏的方式做到这一点没有很好的答案.