This example demonstrates the integration of Apollo Federation services into a stitched schema.
⚠️ NOTE: integration with Apollo Federation and its evolving roadmap is NOT a core concern of GraphQL Tools Schema Stitching. While the two systems have plenty of similarities and you can pull many strings to make them talk to each other, there is no formal contract that guarentees their interoperability. The following guide outlines commonalities between the two systems. Buyer beware that you're assuming your own testing and maintenance overhead if you choose to couple these systems in a production environment. Also note that the Federation roadmap outlines several prospective features that will tightly-couple subservices with a proprietary Apollo Gateway implementation.
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 – which is fundamentally just a GraphQL service.
This example demonstrates:
- Integrating Apollo Federation services into a stitched schema.
- Fetching and parsing Federation SDLs.
cd federation-services
yarn install
yarn start
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.
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 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.
Federation and Stitching use fundamentally similar patterns to combine underlying subservices (in fact, both tools have shared origins in Apollo Stitching). 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.
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
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:
const { buildSchema } = require('graphql');
const { stitchingDirectives, federationToStitchingSDL } = require('@graphql-tools/stitching-directives');
const makeRemoteExecutor = require('./lib/make_remote_executor');
const stitchingConfig = stitchingDirectives();
const executor = makeRemoteExecutor('http://localhost:4001/graphql');
const federationSDL = await executor({ document: '{ _service { sdl } }' });
const stitchingSDL = federationToStitchingSDL(federationSDL, stitchingConfig);
const gatewaySchema = stitchSchemas({
subschemaConfigTransforms: [stitchingConfig.stitchingDirectivesTransformer],
subschemas: [{
schema: buildSchema(stitchingSDL),
executor
}]
});
Written as static subservice configuration, a federation service merges types within a stitched gateway using the following:
const { pick } = require('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 }),
}
}
}]
});