1

我有一个关于架构/查询设计的问题。

让我们假设有一个Study模型。

Study可以有多个后继者,所以显然有parentId字段。

case class Study(id: StudyId, name: String, parentId: Option[StudyId])

Userfrom context 可以访问研究,因此我们希望将所有可访问的研究作为树返回到 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,它可以计算为所有后继者进度的递归总和,所以我们每次都需要整个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,所以我们会有类似prepare的东西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以验证是否progress正在调用该字段。现在我想知道是否有更好的方法来处理这种情况。

4

2 回答 2

0

我相信您正在尝试避免 N+1 查询。以下是您可以克服的方法:

  1. 通过多个 ID 获取进度列表。 def progresses(ids: Seq[EntityId])
  2. 现在,您可以使用DeferredSangria 提供的来解析StudyNode, 并使用groupByEntityId。

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)))
  }
}
于 2018-07-11T03:46:20.620 回答
0

您可以使用Deferred Resolvers/ Fetchers,它能够在查询执行期间缓存相同 id 的结果,因此您无需两次询问数据库...

有关此的更多信息:
https ://sangria-graphql.org/learn/#high-level-fetch-api
https://www.howtographql.com/graphql-scala/4-deferred_resolvers/

此外,您可以使用任何 3rd 方库进行更有效的获取,例如

http://47deg.github.io/fetch/
https://github.com/getclump/clump

于 2018-07-03T09:47:58.957 回答