我对架构/查询设计有疑问。
让我们假设有一个Study
模型。
Study
可以有多个后继者,因此显然有parentId
字段。
case class Study(id: StudyId, name: String, parentId: Option[StudyId])
来自上下文的 User
可以访问研究,因此我们希望将所有可访问的研究作为树返回到UI。
所以查询字段可以是这样的
Field(name = "accessibleStudies",
fieldType = ListType(StudyNode.Type),
resolve = implicit ctx => inject[StudyQueries].accessibleStudies(ctx))
在accessibleStudies
方法中,我们向DB请求数据并准备可在UI上作为树查看的列表。
class StudyQueries {
def accessibleStudies(ctx: Ctx): Future[Seq[StudyNode]] = {
// 50 lines of data fetching and transformation
}
}
现在有趣的部分。
StudyNode
应该有其他字段 - progress
,可以计算为所有后继者进度的递归总和,因此我们每次都需要整个可访问的研究列表来获取{{1对于单个节点。
像
progress
将导致巨大的开销,因为我们需要为每个节点一次又一次地进行可访问的研究。
但是,在case class StudyNode(entity: Study)
object StudyNode {
val Type = ObjectType[Ctx, StudyNode](
...
Field(name = "progress",
fieldType = ProgressType,
resolve = implicit ctx => inject[ProgressService].progressOfStudy(...))
)
}
内计算进展似乎很方便,所以我们会有类似准备StudyQueries.accessibleStudies
的内容
现在Map[EntityId, Progress]
定义可以像这样修改
StudyNode
但它对我来说看起来不是一个干净的解决方案。
此外,我还必须手动检查case class StudyNode(entity: Study,
progresses: Map[EntityId, Progress])
object StudyNode {
val Type = ObjectType[Ctx, StudyNode](
...
Field(name = "progress",
fieldType = ProgressType,
resolve = implicit ctx => progresses(ctx.value.entity.getId))
)
}
以验证是否正在调用ctx.astFields
字段。
现在我想知道是否有更好的方法来处理这样的情况。
答案 0 :(得分:0)
您可以使用Deferred Resolvers
/ Fetchers
,它们能够在查询执行期间为相同的ID缓存结果,因此您无需两次询问数据库...
有关此的更多信息:
https://sangria-graphql.org/learn/#high-level-fetch-api
https://www.howtographql.com/graphql-scala/4-deferred_resolvers/
此外,您可以使用任何第三方库来进行更有效的获取,例如
http://47deg.github.io/fetch/或
https://github.com/getclump/clump
答案 1 :(得分:0)
我相信您正在尝试避免执行N + 1个查询。 这是您可以克服的方法:
def progresses(ids: Seq[EntityId])
Deferred
来解析StudyNode
,并使用EntityId的groupBy
。 case class ProgressDeferred(entityId: EntityId) extends Deferred[Seq[Progress]]
class ProgressResolver extends DeferredResolver[YourContext] {
override def resolve(deferred: Vector[Deferred[Any]], ctx: YourContext, queryState: Any)(implicit ec: ExecutionContext): Vector[Future[Any]] = {
val entityIds = deferred.map{
case ProgressDeferred(entityId) => entityId
}
val progresses = ctx.progressService.progresses(entityIds).map(_.groupBy(_. entityId))
entityIds.map(id=> progresses.map(_.get(id)))
}
}