将字段参数(例如分页参数)传递给延迟的“Fetcher”的最佳方式是什么?

What's the best way to pass field arguments (e.g. paging parameters) to a deferred `Fetcher`?

下面是我当前如何处理延迟字段参数的示例。

A Parent class 包含延迟 Page Child object 秒。 children 的分页参数在 children 字段中定义。我需要能够将分页参数连同 parent 的 ID 一起传递给延迟的 Fetcher,所以我将它们捆绑在一个临时的 DeferredChildInput object 中,连同 parent 的 ID,并将它们传递给 Fetcher。对应DeferredChildResultreturns查询结果(Page[Child])和DeferredChildInput(在HasId中使用)。

问题是,是否有更好的方法将字段参数和 parent id 传递给延迟的 Fetcher

case class Page[T](
  data: Seq[T],
  pageNumber: Int,
  pageSize: Int,
  totalRecords: Long
)

case class Parent(
  id: Long,
  children: Page[Children] // this field is deferred
)

case class Child(
  id: Long
  parentId: Long
)

// the field's query parameters
case class DeferredChildInput(
  parentId: Long,
  pageNumber: Int,
  pageSize: Int
)

// used to temporarily hold the result of the deferred resolution
case class DeferredChildResult(
  input: DeferredChildInput // this is used to resolve the HasId check
  page: Page[Child] // this is what we really want
)

trait ChildService {
  def getChildrenByParentId(
    parentId: Long,
    pageNumber: Int,
    pageSize: Int
  ): Page[Child]
}

val childFetcher: Fetcher[MyContext, DeferredChildResult, DeferredChildResult, DeferredChildInput] = Fetcher {
    (ctx: MyContext, inputs: Seq[DeferredChildInput]) =>
      val futures = inputs.map { input =>
        ctx.childService.getChildrenByParentId(
          input.parentId,
          input.pageNumber,
          input.pageSize
        ).map { childPage =>
          DeferredChildResult(input, childPage)
        }
      }

    Future.sequence {
      futures
    }
  }(HasId(_.input))
}

val ChildObjectType = derivedObjectType[Unit, Child]()

val ParentObjectType = deriveObjectType[Unit, Parent](
  ReplaceField(
    fieldName = "children",
    field = Field(
      name = "children",
      fieldType = PageType(childObjectType),
      arguments = List(
        Argument(
          name = "pageNumber",
          argumentType = IntType
        ), Argument(
          name = "pageSize",
          argumentType = IntType,
        )
      ),
      resolve = ctx => {
        // bundle the field/query parameters into a single input object
        val input = DeferredChildInput(
          parentId = ctx.value.id,
          pageNumber = ctx.args[Int]("pageNumber"),
          pageSize = ctx.args[Int]("pageSize")
        )

        DeferredValue(childFetcher.defer(input)).map { results =>
          results.page
        }
      }
    )
  )
)

在这种情况下,您并没有真正从使用提取器中获益,它只是增加了数据提取机制的复杂性。例如,结果不太可能被缓存和重用。您还分别解析了每个 DeferredChildInput,这违背了获取程序的主要目的(即批量获取数据,其中所有 inputs 的数据都是在单个 request/DB 交互中获取的)。

我建议在这种情况下完全避免使用提取器,而是直接从解析函数中提取页面数据。 Fetcher 并不真正支持分页。在某些情况下,在 resolve 函数中获取实体的 ID,然后使用 fetcher 根据已知的 ID 列表获取实体数据可能是可行的。但据我所知,你的情况并非如此。

此外,当使用解析器并返回 N 个父对象时,您最终会为每个子对象获取 N+1 次,这比获取 n 页子对象并按父对象 ID 分组要慢得多。