加入我们,于10月8日至10日在纽约市,了解关于 GraphQL Federation 和 API 平台工程的最新技巧、趋势和新闻。加入2024年纽约市的 GraphQL 峰会
文档
开始免费使用

基于响应的代码生成


从您的 操作中获取操作,为它们生成相应的Kotlin模型,并从您的JSON响应中实例化它们,使您能够以类型安全的方式访问数据。

实际上有3个不同的域在进行

  • GraphQL域:
  • Kotlin域:模型
  • JSON域:响应

默认情况下, Apollo Kotlin 生成的模型与您的 GraphQL 操作完全匹配。内联和命名的 生成合成 ,因此您可以使用Kotlin代码访问GraphQL片段,例如 data.hero.onDroid.primaryFunction 是可以跨不同 操作 重用的类。这个代码生成引擎(codegen)称为 operationBased,因为它与GraphQL操作匹配。

尽管如此,JSON响应的形状可能与您的GraphQL操作不同。这在使用合并 字段片段时是这种情况。如果您想以JSON响应中的样子访问您的Kotlin属性,Apollo Kotlin提供了 与JSON响应完全匹配的 responseBased 代码生成。 GraphQL 片段用Kotlin接口表示,因此您可以使用Kotlin代码访问它们的字段,例如 (data.hero as Droid).primaryFunction。由于它们映射到JSON响应,responseBased 模型允许JSON流和/或映射到动态JS对象。但是,由于 GraphQL 是一种非常表达的语言,也很容易创建一个生成非常大型JSON响应的GraphQL查询

因此,及其他限制,我们建议默认使用operationBased代码生成

此页面首先回顾了如何operationBased代码生成工作,然后再解释responseBased代码生成。最后,列出使用responseBased代码生成时不同的限制,以便您可以做出明智的决定,是否使用此代码生成器。

要使用特定的代码生成器,请在您的Gradle脚本中配置codegenModels

build.gradle.kts
apollo {
service("service") {
// ...
codegenModels.set("responseBased")
}
}

operationBased代码生成(默认)

The operationBased代码生成会根据操作的形式生成模型。

  • 每个复合字段选择将生成一个模型。
  • 碎片扩展和内联碎片将分别作为自己的类生成。
  • 合并字段在每次查询时存储多次。

例如,给定以下查询

HeroQuery.graphql
query HeroForEpisode($ep: Episode!) {
search {
hero(episode: $ep) {
name
... on Droid {
name
primaryFunction
}
...HumanFields
}
}
}
fragment HumanFields on Human {
height
}

代码生成器生成了以下类

HeroQuery.kt
class Search(
val hero: Hero?
)
class Hero(
val name: String,
val onDroid: OnDroid?,
val humanFields: HumanFields?
)
class OnDroid(
val name: String,
val primaryFunction: String
)
HumanFields.kt
class HumanFields(
val height: Double
)

注意 onDroidhumanFieldsHero 类中是可空的。这是因为它们是否存在取决于返回的超级英雄的具体类型:

val hero = data.search?.hero
when {
hero.onDroid != null -> {
// Hero is a Droid
println(hero.onDroid.primaryFunction)
}
hero.humanFields != null -> {
// Hero is a Human
println(hero.humanFields.height)
}
else -> {
// Hero is something else
println(hero.name)
}
}

基于响应的代码生成器

基于响应的代码生成器与基于操作的代码生成器有以下不同之处:

  • 生成的模型与操作响应中接收到的JSON结构具有 1:1映射
  • 多态性通过生成 接口 来处理。可能的形状被定义为实现相应接口的不同类。
  • 片段也作为 接口 生成。
  • 任何合并 字段 仅在生成的模型中 出现一次

让我们通过使用片段的例子来突出这些差异。

内联片段

考虑这个 query

HeroQuery.graphql
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
... on Human {
height
}
}
}

如果我们对这个操作运行基于响应的代码生成器,它会生成一个具有三个实现类的 Hero 接口:

  • DroidHero
  • HumanHero
  • OtherHero

因为 Hero 是一个具有不同实现的接口,所以可以 when子句来处理每个不同的案例:

when (hero) {
is DroidHero -> println(hero.primaryFunction)
is HumanHero -> println(hero.height)
else -> {
// Account for other Hero types (including unknown ones)
// Note: in this example `name` is common to all Hero types
println(hero.name)
}
}

访问器

作为方便起见,基于响应的代码生成器会生成名称模式为 as<ShapeName>(例如,asDroidasHuman)的方法,这使您无需手动强制转换:

val primaryFunction = hero1.asDroid().primaryFunction
val height = hero2.asHuman().height

命名片段

考虑这个例子

HeroQuery.graphql
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
...DroidFields
...HumanFields
}
}
fragment DroidFields on Droid {
primaryFunction
}
fragment HumanFields on Human {
height
}

基于响应的代码生成器为 DroidFieldsHumanFields片段生成接口:

interface DroidFields {
val primaryFunction: String
}
interface HumanFields {
val height: Double
}

这些接口由生成的 HeroForEpisodeQuery.Data.Hero 的子类(以及使用这些片段的任何操作的任何模型)实现:

HeroForEpisodeQuery.kt
interface Hero {
val name: String
}
data class DroidHero(
override val name: String,
override val primaryFunction: String
) : Hero, DroidFields
data class HumanHero(
override val name: String,
override val height: Double
) : Hero, HumanFields
data class OtherHero(
override val name: String
) : Hero

可以这样使用

when (hero) {
is DroidFields -> println(hero.primaryFunction)
is HumanFields -> println(hero.height)
}

访问器

为了方便起见,responseBased 代码生成器按照名称模式 <fragmentName> (例如,针对名为 DroidFields,使用 droidFields)。这使得您可以将调用链接在一起,如下所示:

val primaryFunction = hero1.droidFields().primaryFunction
val height = hero2.humanFields().height

《responseBased》代码生成的局限性

  1. 由于 GraphQL 是一门非常 expressive 的语言,因此很容易创建一个生成非常大的 JSON 响应的 GraphQL 查询。如果使用大量的嵌套 fragments,生成的代码大小将与嵌套层级成指数级增长。我们曾看到相对较小的 GraphQL 查询破坏了 JVM 的限制,如 最大方法大小
  2. 当使用 fragments 时,必须为每个使用 fragments 的操作生成数据类。为了避免名称冲突,模型是嵌套的,这会带来两个副作用:
    • 生成的 .class 文件名可能非常长,会超出 macOS 的默认最大文件名长度 256。
    • 相同名称的接口可能会嵌套(针对 fragments)。虽然在 Kotlin 中这是有效的,但是 Java 不允许这样做,并且如果使用 kapt,则会导致编译失败。
  3. @include@skip@defer 指令在 代码生成器中不支持 fragments。支持这些指令会在使用这些指令时生成两倍数量的模型。
上一页
JavaScript 互操作性
下一页
Apollo Kotlin 星系
时速文章时速在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,也就是Apollo GraphQL。

隐私政策

公司