Modular Encapsulation in Large-Scale GraphQL Projects

Arda Tanrikulu
Modular Encapsulation in Large-Scale GraphQL Projects - The Guild Blog

TL;DR: If you are writing a large-scale project, it’s necessary to understand the relations between your pieces of code, and how they effect each other. Otherwise, it might be difficult to deal with changes later.

While developing a large-scale project, removing, changing and modifying parts of the project can become a very time consuming and risky task, because understanding the full side-effects of those changes is very hard.

In GraphQL-Modules, you can declare your modules in a feature-based structure, with clear enforced boundaries and avoid unexpected side-effects during development.

Each module can declare it’s own scope of the complete schema:

Each module has its own schema, context, typeDefs, resolvers, and business-logic and each module is encapsulated, which limits the module’s access to only its own parts of the schema.

So, what’s the real benefit of this?

Let’s assume you have an authentication module;

After a while, you decided to use AccountsJS in your GraphQL project which has completely different implementation than the existing one.

This can be a risky change, because you’re afraid of breaking a lot of things and you can’t be sure about the all the places in your code that uses values affected by the authentication module, for example the global context.

With GraphQL-Modules’s approach of encapsulation, even the context is completely encapsulated, so every module that uses your existing AuthenticationModule’s context in defined on theimports of the dependent modules, and its interface is already extended by AuthenticationModule’s context if you’re using TypeScript. When it is removed, you will notice that change on compile-time immediately.

Let’s take a look at some code, to show how GraphQL Modules makes you create those dependencies explicitly.

AppModule is our top Application Module;

import { GraphQLModule } from '@graphql-modules/core';
import { AuthenticationModule } from './modules/auth.module';

const AppModule = new GraphQLModule({
    imports: [
        AuthenticationModule, //this module has `user` in its context
        SomeImportantModuleThatUsesAuthenticatedContext,
        ...
    ],
});

And there is another module that tries to use AuthenticationModule’s context, but it doesn’t import AuthenticationModule. In this case, it is not possible to get anything from AuthenticationModule in the resolvers, because it is not imported. The following module doesn’t know anything about AuthenticationModule.

const SomeImportantModuleThatUsesAuthenticatedContext = new GraphQLModule({
    typeDefs: SOME_TYPE_DEFS_HERE
    imports: [
        ...,
        // This module doesn't import AuthenticationModule
        ...
    ],
    resolvers: {
        ...,
        Query: {
            getUser: (root, args, context) => {
                return context.user; // Oops! Undefined!
            }
        }
        ...
    }
});

To fix this, we need to import AuthenticationModule into that ‘important’ module to make it able to access AuthenticationModule’s context like below;

const SomeImportantModuleThatUsesAuthenticatedContext = new GraphQLModule({
    typeDefs: SOME_TYPE_DEFS_HERE
    imports: [
        ...,
        // This module imports AuthenticationModule
        AuthenticationModule,
        ...
    ],
    resolvers: {
        ...,
        Query: {
            getUser: (root, args, context) => {
                return context.user; // Yes, we have that user HERE!
            }
        }
        ...,
    }
});

So, these examples above show us that the encapsulation can be very important in the long term of our project development.

We think modular approach is not just merging schemas and concatenate context factory functions to each other.

Having a tool that knows how to encapsulate modules and force this policy, makes it much easier to write modular schema, and later, even reuse existing modules and share them across multiple projects.

All posts about GraphQL Modules