【问题标题】:combining resolvers for interface and concrete types结合接口和具体类型的解析器
【发布时间】:2019-10-15 11:08:19
【问题描述】:

由于某种原因,我很难弄清楚如何将 GraphQL 接口的解析器和实现该接口的类型结合起来。

假设我有以下架构:

interface IPerson {
  id: ID!
  firstName: String!
  lastName: String!
}

type ClubMember implements IPerson {
  ...IPerson fields
  memberType: String!
  memberSince: DateTime!
}

type StaffMember implements IPerson {
  ...IPerson fields
  hireDate: DateTime!
  reportsTo: StaffMember
}

extend type Query {
  people(ids: [Int!]): [IPerson]
}

包含所有字段的完整 ClubMember 查询,例如:

query {
  people(ids: [123456,234567,345678]) {
    id
    firstName
    lastName
    ... on ClubMember {
      memberType
      memberSince
    }
  }
}

会产生如下响应:

[
  {
    "id": 123456,
    "firstName": "Member",
    "lastName": "McMemberface",
    "memberType": "VIP",
    "memberSince": "2019-05-28T16:05:55+00:00"
  },
  ...etc.
]

我已经使用了apollo-server 中的makeExecutableSchema()inheritResolversFromInterfaces: true,并且我希望能够通过支持IPersonClubMember 的模型类来为每个接口/类型使用默认解析器, etc. 返回的对象只包含与每种类型相关的字段,即IPerson 的模型类只获取IPerson 所需的字段等。也就是说,上面的响应将执行 2 条 SQL 语句:

SELECT id, firstName, lastName FROM Contacts WHERE id IN(?);

SELECT contactId, memberType, memberSince FROM Members WHERE contactId IN(?);

当然,我可以通过在数据库级别执行 JOIN 来获取一条 SQL 语句中的所有数据,但我真的希望有一种(也是唯一一种)方法来解析 IPerson 所需的字段,并让其他类型使用自己的解析器来扩充该数据。

我的问题是,我是否需要自己在解析器中为 people 查询类型将结果对象“加入”在一起?例如

const resolvers = {
  Query: {
    people: function( parent, args, context, info ) {
      let persons = context.models.Person.getByIds( args.ids );
      let members = context.models.Member.getByIds( args.ids );
      /*
      return an array of {...person, ...member} where person.id === member.id
      */
    }
  }
}

或者阿波罗有什么方法可以为我们处理这个问题?我想要apollo-resolvers 之类的东西吗? docs on unions and interfaces 不是很有帮助;我在IPerson 上有__resolveType,但文档没有指定如何解析每种具体类型的字段。有没有更好的方法来使用 Dataloader 或其他方法来实现这一点?

我认为this question 与我的问题有关,因为如果查询不通过片段请求该类型的任何字段,我不想获取具体类型的数据。 Github 上还有this issue

非常感谢!

编辑: __resolveType 如下所示:

{
  IPerson: {
    __resolveType: function ( parent, context, info ) {
      if ( parent.memberType ) {
        return 'ClubMember';
      }
      ...etc.
    }
  }
}

【问题讨论】:

  • 你的 __resolveType 函数是什么样的?
  • 抱歉格式化:{ IPerson: { __resolveType: function( parent, context, info ) { if ( parent.memberType ) { return 'ClubMember'; } ...等等。 }

标签: graphql apollo-server


【解决方案1】:

这个问题确实不是 Apollo Server 甚至 GraphQL 所特有的——查询多个表并获得一组结果,尤其是在处理多个数据模型时,会变得很棘手。

当然,您可以分别查询每个表并组合结果,但这并不是特别有效。我认为迄今为止处理这种情况最简单的方法是在数据库中创建一个视图,例如:

CREATE VIEW people AS
    SELECT club_members.id           AS id,
           club_members.first_name   AS first_name,
           club_members.last_name    AS last_name,
           club_members.member_type  AS member_type,
           club_members.member_since AS member_since,
           null                      AS hire_date,
           null                      AS reports_to,
           'ClubMember'              AS __typename
    FROM club_members
    UNION
    SELECT staff_members.id         AS id,
           staff_members.first_name AS first_name,
           staff_members.last_name  AS last_name,
           null                     AS member_type,
           null                     AS member_since,
           staff_members.hire_date  AS hire_date,
           staff_members.reports_to AS reports_to
           'StaffMember'            AS __typename
    FROM staff_members;

您也可以只使用单个表,但视图允许您将数据保存在单独的表中并一起查询它们。现在您可以为您的视图添加一个数据模型并使用它来查询所有“人”。

注意:为方便起见,我在此处添加了__typename 列——通过返回__typename,您可以省略指定自己的__resolveType 函数——GraphQL 将在运行时为您分配适当的类型。

【讨论】:

  • 我不确定我是否可以选择创建新视图,但我一定会看看是否可以。我想我只是希望我有类似工厂/子类的 DI 就像您在其他一些数据访问模式中发现的那样,这样生成的具体实例将匹配架构所需的内容,从而可以利用默认解析器。我认为 GraphQL 接口的许多文档/示例并不擅长指定如何解析额外字段,同时保持接口字段的解析器干燥。
  • 此外,是否真的有理由从查询中返回接口类型,而不是简单地公开更具体的查询类型?那么,为什么不用query { people(ids: [...]) { ... } } 而不是members() 查询和staff() 查询呢?至少那时您会立即知道您必须为每个查询获取特定数据,而挑战将是尽可能多地使常见的 SQL 可重用。
  • 写到您的第二条评论,这完全取决于使用 API 的客户端的需求
  • 写到您的第一条评论,您真的不应该为firstNamelastName 等字段编写解析器。理想情况下,您的模型已经返回具有名为firstName、@ 的属性的数据987654331@ 等 如果您确实需要一个跨多个类型共享的解析器,这些类型都实现了相同的接口,那么您可以利用inheritResolversFromInterfaces 并在您的解析器映射中为接口类型而不是每个实现类型提供一个解析器.
  • 是的,我提到我正在使用 inheritResolversFromInterfaces 并且我的模型已经返回具有正确属性的数据 IPerson 这样我就不必编写那些解析器,正如你所说,以及模型,例如StaffMember 正在为 那些 字段返回具有正确属性的数据,因此我也不必为这些字段编写解析器。如果解析器映射中的接口上没有实际的数据获取逻辑,我想我不明白如何“继承”接口的默认解析器。
猜你喜欢
  • 2020-01-22
  • 2013-06-14
  • 1970-01-01
  • 2019-05-04
  • 1970-01-01
  • 1970-01-01
  • 2011-02-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多