Below is an example of how I'm currently handling deferred field arguments.
A Parent
class contains a deferred Page
of Child
objects. The paging parameters for the children are defined on the children
field. I need to be able to pass the paging parameters, along with the parent's id, to the deferred Fetcher
, so I bundle them up in a temporary DeferredChildInput
object, along with the parent's id, and pass them to the Fetcher
. A corresponding DeferredChildResult
returns the result of the query (the Page[Child]
) and the DeferredChildInput
(use in the HasId
).
Question is, is there a better way to pass field arguments and parent id to the deferred 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
}
}
)
)
)
In this scenario, you don't really benefit from using fetchers, it just increases the complexity of data fetching mechanism. For example, it is quite unlikely that results would be cached and reused. You are also resolving each DeferredChildInput
separately which defeats the main purpose of fetchers (which is to batch the data fetching, where data for all inputs
is fetched in a single request/DB interaction).
I would recommend avoiding using fetchers altogether in this scenario and fetch the page data directly from resolve function. Fetchers don't really support pagination. In some scenarios, it might be viable to fetch the IDs of entities in a resolve function and then use fetcher to fetch the entity data based on the already known list of IDs. But as far as I can tell, it is not the case in your scenario.