GraphQL Schema Federation¶
Schema federation allows us to combine GraphQL Schemas from different distributed services (microservices like Positions, ServiceOrders, etc.) into one schema that includes all the available queries, mutations and subscriptions from the services.
This is particularly useful to create BFF gateways, where we want to combine several services and features into one exposed service that will be used by a particular frontend UI.
Terminology¶
- Federation: a group of schemas composed into one super-schema.
- Federated Schema/Service: a service whose schema was included on a federation.
A note on Redis¶
Schema federation allows the gateway to be notified on real time when there are changes on the schemas of the services. This feature is achieve using a Redis instance as a cache and leveraging its pub/sub pattern.
Configuring Federation on ITsynch Suite¶
The Schema Federation needs to be properly configured on both the Gateway and the microservices.
Important
For completeness below we explain how to configure federations by code and by configuration file, but bear in mine that, for federations, you should almost always use the configuration files, as we want to keep it flexible.
Microservice Configuration¶
On the microservice we need to publish its schema into a Federation. This is
done using the GraphQLModuleOptions
either by code or by configuration file.
Microservice by code¶
We can use the AddFederation
method to publish the GraphQL Schema of the
microservice on a given federation. The FederationName
identifies the
federation, which it is usually a one to one mapping with a given BFF, in the
example below we are allowing positions to be exposed through the AdminCenter
BFF. The SchemaName
field is the name that identifies this particular schema
when combined with others on the BFF.
C# | |
---|---|
Tip
The method is call AddFederation
because you can add as many as needed. For example the positions schema could also by federated into the ServiceOrders BFF.
Microservice by configuration file¶
To keep the main configuration file clean as the number of Federation growths we
have added support for configuring the federations on a separated file called
federations.json
. The equivalent setting for the example above would be:
JSON | |
---|---|
On the array called Federations
we can add multiple federation configurations.
Sometimes it might be necessary to rename a field of the schema being published,
for example, to avoid collisions. For example there could be 2 priorities
fields being published to the same federation, one coming from the
serviceorders
microservice and another one coming from the inspections
microservice. We can change the name for some fields on a federation by adding
them to the RenameFields
list on a federation configuration:
JSON | |
---|---|
Note
HotChocolate will resolve collisions automatically by adding a prefix to one of the colliding fields, but the setting introduced above allows us to control the renaming.
Similarly, you can also rename GraphQL types, using the RenameTypes
list as
follows:
JSON | |
---|---|
Gateway Configuration¶
On the the Gateway we need to configure what Federation the gateway will be
exposing and also the endpoint where to reach each of the schemas added to that
federation. To do so we have to use the GraphQLGatewayModuleOptions
either by
code or by configuration file.
Gateway by code¶
We can use the FederationName
property to indicate which federation the
gateway is going to expose. We can use the AddRemoteSchema
method to match
each schema with the address of its corresponding endpoint:
C# | |
---|---|
Important
Notice how we are using positions-service
for the address, this is because we are relaying on the Service Discovery feature of the ITsynch Suite, so we do not use an actual valid HTTP address, we use the name of the service on the for the Auto Discovery, then on runtime the actual address is automatically resolved based on the deployment setup.
Gateway by configuration file¶
As with any Module Options, we can set the values of the FederationName
property and the entries for the RemoteSchemas
dictionary on the
appsettings.json
using the standard JSON format.
JSON | |
---|---|
Extension files and whitelisting¶
By default, using the configuration described on the previous sections, the microservice will publish all its content to the federation. We rarely want this to be the case. We need to control what queries and mutations are published to the BFF, remember that the BFF is facing out of the intranet so it is good policy to avoid publishing unnecessary APIs to the public.
Microservice Configuration for Whitelisting¶
There are two configuration values we need to set, the IgnoreRootTypes
which
tells the microservice not to publish the queries and mutations to the
federation, and the SchemaExtensionFiles
which is a list of extension files
where we define what is going to be published to the federation.
JSON | |
---|---|
On the extensions files we can publish existing queries and create new ones by
composing them. The framework allows many files to be added so they can be
combined and reused, as one microservice can publish to several federations. The
extension files are written in GraphQL SDL
combined with some
HotChocolate directives.
What we need to do on the files is to extend the Query
and Mutation
types to
add the fields we want to publish. Publishing an already existing query is quite
straight forward, you can copy the field definition from the microservice SDL
(found in the playground or on the /graphql?sdl
path of the microservice)
paste it on the extended type and add the @delegate
directive.
GraphQL | |
---|---|
Info
Technically what we are doing is creating a new field called issues
that
is going to be published to the federation that when it is called it will
redirect (a.k.a delegate) the call to the real issues
query on the microservice. Since both
have the same name and the same list of parameter the @delegate
directive
can automatically find it without us having to pass any parameters to it.
We can pass several parameters to the @delegate
directive to redirect the
query to another one, but if we do not pass any parameters, then they are
inferred, which is great for when we want to publish an existing query as is.
If we want to change the name of the field, either to avoid collision or to give
it an appropriate name in the context of the target BFF, we can change it, but
since the name will no longer match the real field, we need to tell the
@delegate
directive where to find it, for example we can rename issues
to
serviceOrderIssues
as follows:
GraphQL | |
---|---|
On the snippet above, the path
parameter tells the directive how to find the
target query.
We can also create new fields with completely different names and parameters. A
good example of this is the me
query on the Profiles
microservice. In it, we
are creating a new query without parameters and delegating it to the
userProfileFor
query. The userProfileFor
query needs a user ID, which we are
taking from the scoped context, this way we return the profile for the current
logged in user on the me
query.
GraphQL | |
---|---|
Gateway Configuration for Whitelisting¶
On the previous section we reviewed how to setup a microservice to publish only
what is defined on the extension files by extending the Query and Mutation
types. We need to also setup the BFF to properly receive it. On the microservice
we set the IgnoreRootTypes
parameter to true, so we need to somehow make sure
there are root types (Query and Mutation) so they can be extended. To do so we
need to set the parameter RegisterRootTypes
to true on the BFF, either by
configuration file or by code:
JSON | |
---|---|
C# | |
---|---|
Warning
If we set the BFF to register root types, and one of the microservices
publishing to its federation is not ignoring the root types, then there
would be to sets of Root Types, one coming from the microservice that is not
ignoring them and one published by the BFF itself, this would cause a
critical error. If you set the RegisterRootTypes
to true, then make sure
all the microservices publishing to that BFF have the IgnoreRootTypes
set
to true for the corresponding federation.