Microservices
Intro
idio-graphql builds on the foundations created by Moleculer.js & enables developer's to distribute parts of a GraphQL schema over various communication channels. Such integration enables developers to serve GraphQLNode's & selected Schema Appliances through a Service Broker.
It's recommended to read the previous guides, grasp the fundamentals of idio-graphql, before reading.
Getting Started
Moleculer it is an optional dependency.
npm install moleculer
You will need to choose & launch your own transport layer, these docs will use NATS.
Serving Your First Node
const User = new GraphQLNode({
name: "User",
typeDefs: `
type User {
name: String
age: String
}
type Query {
getUser: User
}
`,
resolvers: {
Query: {
getUser: () => { ... }
}
}
});
await User.serve({
transporter: "NATS",
gateway: "gateway"
});
Your First Gateway
GraphQLGateway sits at the core of your services, the gateway will introspect each service specified and build a distributed GraphQL schema.
const gateway = new GraphQLGateway(
{
services: {
nodes: [ "User" ]
}
},
{
transporter: "NATS",
nodeID: "gateway"
}
);
const { typeDefs, resolvers, broker } = await gateway.start();
On successful start, you receive merged typeDefs & resolvers, that are mapped to Moleculer service calls.
Gradual Adoption
You may not need or want to distribute all your Nodes. You can use GraphQLGateway to gradually adopt a microservices architecure.
const Post = require("./Post.js");
const gateway = new GraphQLGateway(
{
services: {
nodes: [ "User" ]
},
locals: {
nodes: [ Post ]
}
},
{
transporter: "NATS",
nodeID: "gateway"
}
);
const { typeDefs, resolvers } = await gateway.start();
const User = new GraphQLNode({
name: "User",
typeDefs: "...",
resolvers: { ... }
});
await User.serve({
gateway: "gateway",
transporter: "NATS"
});
const Post = new GraphQLNode({
name: "Post",
typeDefs: "...",
resolvers: { ... }
});
module.exports = Post;
Service Broker
To harness the real power of microservices you should take advantage of the Service Broker, initializing Moleculer microservices outside the bounds of GraphQL, to offload long running business logic. GraphQLNode.serve()
will return a Service Broker, you also have access to a Service Broker inside each resolver through the context.injections
parameter.
const { broker } = await User.serve({
transporter: "NATS",
gateway: "gateway",
});
resolvers: {
Query: {
getUser: (root, args, { injections: { broker } }) => { ... }
}
}
'Local Services' are initialized as a Service Broker.
Scaling
idio-graphql comes with 'effort-less' scaling. You can horizontally scale all idio-graphql services & let the package handle the load balancing.
Load Balancing
Taking advantage of $node.list
your idio-graphql service will keep track of the active nodes, gateways and schema appliances & deliver messages bases on a pseudo-random algorithm.
Heartbeat Interval
The in-memory active node list will be refreshed based on the Moleculer heartbeatInterval
provided at either serve
or start
of a service. Its recommended to experiment with this interval, to achieve maximum reliability, based on the constrains within your distributed system.
View all the broker options here
const gateway = new GraphQLGateway(
{ services: { nodes: ["User"] } },
{
transporter: "NATS",
nodeID: "gateway",
heartbeatInterval: 3 // seconds
}
);
await UserNode.serve({
gateway: "gateway",
transporter: "NATS",
heartbeatInterval: 3
});
Duplicate Services
For the load balancing to be able to deliver messages to only one instance of each node, each service is set up to be unique by its nodeID.
Service ID's adhere to the following schema
<NAME>:<GATEWAY>:<UUID>
const gateway = new GraphQLGateway(
{ services: { nodes: ["User"] } },
{
transporter: "NATS",
nodeID: "gateway",
heartbeatInterval: 3 // seconds
}
);
const { broker } = await gateway.start();
broker.options.nodeID // "gateway:gateway:uuid123"
const { broker } = await UserNode.serve({
gateway: "gateway",
transporter: "NATS",
heartbeatInterval: 3
});
broker.options.nodeID // "User:gateway:uuid123"
Preserving Parameters
GraphQLGateway will forward the GraphQL arguments onto the relevant Node.
Arguments passed between services will be sanitized using
safe-json-stringify
const gateway = new GraphQLGateway(
{
services: {
nodes: [ "User" ]
}
},
...
);
const { typeDefs, resolvers } = await gateway.start();
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
context: () => {
return {
abc: "123"
}
}
});
const User = new GraphQLNode({
name: "User",
typeDefs: "...",
resolvers: {
Query: {
getUser: (root, args, { abc }) => {
abc // 123
}
}
}
});
Subscriptions
GraphQL Subscriptions, through your chosen transport layer, will work out the box with GraphQLNode's. Ensure you return an async iterator from your subscribe
method!
When utilizing Subscriptions you should prefer a native streaming implementation, such as NATS Streaming, for your transport layer.
Schema Appliances
GraphQLGateway supports Schema Appliances.
const gateway = new GraphQLGateway(
{
services: { ... },
locals: {
nodes,
enums,
scalars,
directives,
interfaces,
unions,
types,
schemaGlobals
}
},
{
transporter: "NATS",
nodeID: "gateway"
}
);