How to use graphql types across services

Hi everyone! I’m trying to figure out if it’s possible to share type definitions between backend services, but not getting anywhere.

Basically, my company has our graphql services split over two different backends, which each define their own queries, mutations, types, etc. We’re trying to consolidate all our logic into a single backend, but, because that takes a lot of time, we’re in a weird state where some of the data lives in backend 1 while some of the mutations are defined in backend 2 (which calls backend 1). So we make a query to get a list of data from backend 1, but update some of that data via a mutation in backend 2. Backend 2 then makes a graphql request to backend 1 and receives the updated data.

To make the apollo caching on the client happy, I want the mutation in Backend 2 to pass that updated data back to the client, using the data type defined in Backend 1. That way, the frontend has its list of MyDataObjects in the cache, and, when Backend 2 sends back an updated MyDataObject(id=123), the frontend cache can be updated. But I can’t figure out how to write my schemas in Backend 2 in such a way that the mutation can send back the data it receives from Backend 1. Schema loading looks like it should support this, but I haven’t been able to make it work. I want something like

type Mutation {
    "update the data in other backend"
    updateMyData(input: ID!): MyDataObject! (the type defined in the other backend)
  }

How do I write a schema definition such that I can define the return type as a type defined in a different service?

Thanks!

Hey @EmberRandall

This is something you can achieve with Apollo Federation, but you would need to ensure your servers are building their schemas with the appropriate subgraph library.

In your Backend 2, you would reference the MyDataObject as an entity. This means you have to return the {id} field in your Mutation.updateMyData resovler. We can also set the entity to not be resolvable:

extend schema 
	@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])

type Query {
	...
}
type Mutation {
	updateMyData(input: ID!): MyDataObject!
}
type MyDataObject @key(fields:"id", resolvable: false) {
	id: ID
}

In Service 1, you would also have MyDataObject defined as an entity. This is
where you would need to define a MyDataObject.__resovleReference resolver to retrieve MyDataObject based on it’s id:

extend schema 
	@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])

type MyDataObject @key(fields:"id", resolvable: false) {
	id: ID
        ...other fields
}

Example __resolveRefernce if you were using JS/TS like Apollo Server:

const resolvers = {
	MyDataObject: {
		__resolveReference({id},context){
			return context.getMyDataObject(id);
		}
	}
}

What would be happening here is the Apollo Router would make the mutation to Service 2, get the response and take that id and send it to Service 1. Here is the query plan that would be executed:

QueryPlan {
  Sequence {
    Fetch(service: "two") {
      {
        updateMyData(input: 1) {
          __typename
          id
        }
      }
    },
    Flatten(path: "updateMyData") {
      Fetch(service: "one") {
        {
          ... on MyDataObject {
            __typename
            id
          }
        } =>
        {
          ... on MyDataObject {
            name
          }
        }
      },
    },
  },
}

If you want to talk about the schema patterns more, we started a Discord server and you can ping me (@watson) and we can talk more. I’m doing a stream in the Discord on Thursday on schema design actually!

Thank you! We are sadly not on Apollo 2.0, which means we can’t use that solution yet, but we do want to migrate there eventually.