Apollo federation: Field/resolver that returns entities from two separate services that implement an interface

789 views Asked by At

Summary

In GraphQL Apollo managed federation, I'm trying to figure out if it's possible for one service to have an aggregate field, that returns an array of entities two separate services. As in, I'm trying to support this use case:

query FromTwoServices {
   aggregateField {
      id
      ...on Service1Entity {
          field1
      }
      ...on Service2Entity {
          field2
      }
   }
}

where both

type Service1Entity implements Aggregation

and

type Service2Entity implements Aggregation

And under the hood, the resolver for aggregateField (which lives on Service 2) calls down down to Service 1 to Service1Entitys. So this field is aggregating two data sets.

Example Problem

In my current GraphQL setup, I have all of this defined in one service:

interface Animal {
   id: ID!
   name: String!
}

type Cat implements Animal {
   id: ID!
   name: String!
   meows: Boolean
}

type Dog implements Animal {
   id: ID!
   name: String!
   barks: Boolean
}

When I query my service, it hits an animals resolver, and looks like:

query Animals {
  animals {
    id
    ...on Cat {
      meows
    }
    ...on Dog {
      barks
    }
  }
}

Now I'm creating a Dogs service. I would like Dogs to come from the Dog service, and Cats to come from the Cats service. Additionally, I would still like to support my animals aggregation field, and I'd like to move it to the Dogs service.

To support this, I've made Cat an entity, and duplicated the interface across both services. To allow Dog service to resolve the Cat type, I've used "referencing" to tell Dog service that Cat lives somewhere else.

Cat Service (marking Cat as entity with @key):

interface Animal {
   id: ID!
   name: String!
}

type Cat implements Animal @key(fields: "id") {
   id: ID!
   name: String!
   meows: Boolean
}

Dog Service:

interface Animal {
   id: ID!
   name: String!
}

type Cat implements Animal @key(fields: "id") {
   id: ID! @external
}

type Dog implements Animal {
   id: ID!
   name: String!
   barks: Boolean
}

The problem with this setup is the Dog service fails to start, because of this GraphQL error:

Error: Interface field Animal.name expected but Cat does not provide it.

In my Dog service, if I add an external name field, like so:

type Cat implements Animal @key(fields: "id") {
   id: ID! @external
   name: String! @external
}

Then the two services fail to compose in managed federation:

This data graph is missing a valid configuration. [dog] Cat.name -> is marked as @external but is not used by a @requires, @key, or @provides directive.

I sort of feel like I'm going down the wrong path here. Is it possible to have an aggregate field like this, where one service can return the its own entities, combined with the entities of another service?

We already have lots of clients querying for the animals field, so we're trying to support this change without requiring client changes, aka without restructuring the use of an interface.

0

There are 0 answers