With Apollo Server, you can create an Apollo Federation for your GraphQL APIs. The pre-requisite for federation would be to have at least two different GraphQL APIs that you want to advertise under the same url. Or if you want to break down a big GraphQL schema into multiple small microservices.
This feature from Apollo GraphQL federation v1 is for scaling up your APIs, let’s review how it works.
Call-flow
Here is a brief flow chart where we have 3 APIs each having part of the graph defined and exposed.
Using the Apollo Gateway (or Graph Router) we can use only one URL. The client does not need additional set up to connect to each microservice, talking to the Gateway is enough to have access to the complete graph.
Find in the documentation how to get started, you can try it one API at a time.
Separation of Concerns
Example
This is one of the most important aspect of the federation. It allows you to use types from different subgraph within one API without having to redefine the whole domain. Let’s say we have:
type User {
id: ID!
name: String!
vehicles: [Vehicle]
}
type Vehicle {
id: ID!
registration: String
driver: User
}
That mean the User API must know about Vehicle
of the Vehicle API, and Vehicle API must know about User
from the
User API. Quite a conundrum 🧐
Instead of redefining User
and Vehicle
you can just define the fields that belong to the API, for example in the
User API:
# User API
type User @key(fields: "id") {
id: ID!
vehicles: [Vehicle]
}
# To define the "driver" field on Vehicle
type Vehicle @extends {
id: ID! @external
driver: User
}
We define only the fields in Vehicle that belong to the User domain, and for the Vehicle API we will have:
# Vehicle API
type Vehicle @key(fields: "id") {
id: ID!
registration: String
}
# To define the "vehicles" field on User
extend type User {
id: ID! @external
vehicles: [Vehicle]
}
This way we can define and declare the vehicles
field on User
within the Vehicle API. The reconciled graph from the
graph router in the API Gateway will allow the client to access the right resolver from the right APIs to get the data.
Subgraph Spec
Directive
You may have seen some directive like @Key
in the previous examples, those are some special directive from the
@apollo/federation package.
They are part of the subgraph specification that are injected in the subgraph schema for federation purpose.
First let’s look at the directives we used and how they are defined:
scalar FieldSet
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @external on FIELD_DEFINITION
directive @extends on OBJECT | INTERFACE
There are three of them:
@key
: which define the key of the object, so that the gateway knows which object to retrieve when queried.- For example for User, you need to have the
id
key in order to resolve the correct one
- For example for User, you need to have the
@external
: to mean that the field is not defined within this API.- For example for Vehicle API the field
User.id
is only defined in the User API, it’s an external field
- For example for Vehicle API the field
@extends
: So that the Gateway understand you are not re-defining the type but extending it. You can either use@extend
or the preferred built-inextend
key word.- For example, by adding
driver
on the typeVehicle
on the User API is extending the type.
- For example, by adding
The extend
is not necessarily federation specific, since you can use it within a non-federated API where you want to
break down your schema into multiple files (like one per query), that way you can extend the Query
type in each file.
Entity Query
After defining the previous directive, you might think that there’s still something obscure or magic with the key. How does the federation know how to fetch the correct entity with the defined key?
That’s when the entity query comes into play! This query is part of the subgraph specification and defined through the @apollo/federation package as well:
# Defined in @apollo/federation
extend type Query {
_entities(representations: [_Any!]): _Entity!
}
The representations
is the type name and the fields from the key directive, which can be anything hence the internal
_Any
type. The _Entity
type is a union of all the types in the Gateway. This definition is pretty generic and allow
for multiple APIs implementation, so you can implement it for multiple types in multiple APIs.
You will need to implement it via it custom resolver for the gateway to properly work, let’s create one in typescript
for the Vehicle
type in the Vehicle API:
type VehicleReference = {
id: string
}
export function __resolveReference(
{ id }: VehicleReference,
{ VehcileDb }: VehicleContext
): Vehicle | undefined {
return vehiclesDb.find(vehicle => vehicle.id === id);
}
Defining the __resolveReference
resolver under Vehicle
will allow you to resolve the Vehicle
type from its id.
This query is internal to the Gateway, but you can still test it. Assuming you have a test app working with your
API, you can make this GraphQL query:
it('resolves entity by federation', async () => {
const vehicle: Vehicle = await client.query({
query: gql`
query {
_entities(representations: [{
__typename: "Vehicle"
id: "3"
}]) {
... on Vehicle { registration }
}
}
`
}).then(result => result.data._entities[0]);
expect(vehicle).toMatchObject({ registration: 'train' });
});
And you should get back the expected vehicle.
Once defined, the __resolveReference
will always be called by the gateway when trying to resolve the object.
Conclusion
You should have a better understanding of the concept of federation within a GraphQL APIs ecosystem. It becomes increasingly interesting to use federation with distributed teams working on their own microservices. Using the Gateway you can effectively scale your company API as a whole.
You can create your own gateway implementing @apollo/gateway
or use the pre-compiled Apollo Router in Rust
by the Apollo team.
And for advanced usage, you can add checks of your schema with rover to make sure they are valid and apply some recommended best practices to deploy and mange your federated schema.