使用Sangria进行查询设计以防止开销

时间:2018-03-30 22:33:24

标签: scala graphql sangria

我对架构/查询设计有疑问。

让我们假设有一个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字段。 现在我想知道是否有更好的方法来处理这样的情况。

2 个答案:

答案 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个查询。 这是您可以克服的方法:

  1. 通过多个ID获取进度列表。 def progresses(ids: Seq[EntityId])
  2. 现在,您可以使用Sangria提供的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)))
  }
}