Federation services
This example demonstrates the integration of Apollo Federation services (opens in a new tab) into a stitched schema.
As you get the hang of schema stitching, you may find that Federation services are fairly complex for what they do. The buildFederatedSchema
method from the @apollo/federation
package creates a nuanced GraphQL resource that does not guarentee itself to be independently consistent, but plugs seamlessly into a greater automation package. By comparison, stitching encourages services to be independently valid and self-contained GraphQL resources, which makes them quite primitive and durable. While federation automates service bindings at the cost of tightly-coupled complexity, stitching embraces loosely-coupled bindings at the cost of manual setup. The merits of each strategy are likely to be a deciding factor for developers selecting a platform. Stitching is a library used to build a framework like Federation.
Stitching is less opinionated than Federation, and is made considerably simpler without the complexity added by buildFederatedSchema
. However, when integrating with existing servers or in the process of a migration, nothing says you can't incorporate your existing federation resources into a stitched gateway going through the federation _entities
query (opens in a new tab) – which is fundamentally just a GraphQL service.
This example demonstrates:
- Integrating Apollo Federation services into a stitched schema.
- Fetching and parsing Federation SDLs.
Sandbox
You can also see the project on GitHub here (opens in a new tab).
The following services are available for interactive queries:
- Stitched gateway: http://localhost:4000/graphql
- Products subservice: http://localhost:4001/graphql
- Reviews subservice: http://localhost:4002/graphql
- Users subservice: http://localhost:4003/graphql
This example is based on the Federation intro example (opens in a new tab).
Summary
Ever wonder what Federation is doing under the hood? Visit the products service and check out some User
objects:
query {
_entities(
representations: [{ __typename: "User", id: "1" }, { __typename: "User", id: "2" }, { __typename: "User", id: "3" }]
) {
... on User {
id
recentPurchases {
upc
name
price
}
}
}
}
A federation service automatically configures an _entities
query that recieves typed keys (i.e.: objects with a __typename
), and returns abstract _Entity
objects that may assume the shape of any type in the service. Apollo Gateway (opens in a new tab) then automates the exchange of typed keys for typed results, all going through the dedicated _entities
protocol in each subservice. Stitching can also integrate with this _entities
query by sending it properly formatted keys.
Now go to the gateway and check out the stitched results:
query {
user(id: "1") {
username
recentPurchases {
upc
name
}
reviews {
body
author {
id
username
}
product {
upc
name
acceptsNewReviews
}
}
}
}
The stitched gateway has loaded all federation SDLs, converted them into stitching SDLs, and then integrates them like any other GraphQL service with types merged through their _entities
query.
Adapting Federation services
Federation and Stitching use fundamentally similar patterns to combine underlying subservices (in fact, both tools have shared origins in Apollo Stitching (opens in a new tab)). However, their specific implementations have an important differentiator:
- Apollo Federation uses a centralized approach, where all types have a single "origin" service (i.e.: where the unextended type definition is). Querying for a type builds from its origin service.
- Stitching uses a decentralized approach, where any service may equally originate any type. Regardless of where a typed object is first represented, that original object is filled in with missing details from other services.
How each system handles origins informs how a federation service gets translated into a stitched subschema:
- All types with a
@key
directive become merged types; the key fields go intoselectionSet
. - All fields with a
@requires
directive are made into computed fields. Computed fields are slightly more robust than their federation counterparts because they may resolve dependencies from any number of services. - All fields with an
@external
directive are removed unless they are part of the@key
. Stitching expects schemas to only publish fields that they actually have data for. This is considerably simpler than the federation approach where services may be responsible for data they don't have. - By eliminating the indirection of
@external
fields, the@provides
directive is no longer necessary. The Stitching query planner can automate the optimial selection of as many fields as possible from as few services as possible.
SDL integration
The simplest way to make the above adaptions is to translate a Federation SDL string into a Stitching SDL string, which can be done using the federationToStitchingSDL
utility function from @graphql-tools/stitching-directives
(opens in a new tab) package. A federation service's SDL can be obtained through its _service
API:
query {
_service {
sdl
}
}
Once fetched, it can be translated into a Stitching SDL and then built into a stitched schema:
import { buildSchema } from 'graphql'
import { stitchingDirectives, federationToStitchingSDL } from '@graphql-tools/stitching-directives'
import { buildHTTPExecutor } from '@graphql-tools/executor-http'
const stitchingConfig = stitchingDirectives()
const executor = buildHTTPExecutor({ endpoint: 'http://localhost:4001/graphql' })
const federationSDL = await executor({
document: parse(/* GraphQL */ `
{
_service {
sdl
}
}
`)
})
const stitchingSDL = federationToStitchingSDL(federationSDL, stitchingConfig)
const gatewaySchema = stitchSchemas({
subschemaConfigTransforms: [stitchingConfig.stitchingDirectivesTransformer],
subschemas: [
{
schema: buildSchema(stitchingSDL),
executor
}
]
})
Static config
Written as static subservice configuration, a federation service merges types within a stitched gateway using the following:
import { pick } from 'lodash'
const gatewaySchema = stitchSchemas({
subschemas: [
{
schema: buildSchema(stitchingSDL),
merge: {
Product: {
selectionSet: '{ id }',
fields: {
shippingEstimate: { selectionSet: '{ price weight }', computed: true }
},
fieldName: '_entities',
key: originObj => ({ __typename: 'Product', ...pick(originObj, ['id', 'price', 'weight']) }),
argsFromKeys: representations => ({ representations })
}
}
}
]
})