Skip to content

PATTERN Cited by 1 source

Module-based GraphQL decentralization

When to use

You have a large organisation with many teams contributing to a single GraphQL schema, and you want each team to be able to ship changes to their part of the schema without operating a GraphQL server of their own.

Pre-conditions:

  • A platform team that can run a multi-tenant GraphQL runtime (see patterns/multi-tenant-graphql-runtime).
  • Tenant teams that don't want / shouldn't have to operate GraphQL infrastructure.
  • A schema-governance discipline strong enough to enforce per-module ownership boundaries.

The pattern

Distribute schema-development through modules-in-runtime rather than subgraphs-in-services. Each contributing team ships a module: a self-contained directory containing the team's SDL + resolvers + tests. The runtime composes module SDLs into the runtime's global schema and routes incoming GraphQL requests to the right module's resolvers.

The contrast is named explicitly in Viaduct's 1.0 announcement (verbatim): "Federation distributes development through services. Each team owns and operates its own GraphQL subgraph server; those subgraphs are composed by a federation router into a single unified graph. Viaduct distributes development through modules. A shared multi-tenant runtime hosts tenant modules that define and implement portions of the schema."

And the load-bearing one-liner: "Federation distributes development by distributing servers. Viaduct distributes development by distributing modules." (Source: sources/2026-05-13-airbnb-viaduct-1-0-and-the-future-of-airbnbs-data-mesh)

What goes inside a module

A canonical module from Viaduct's contribution contract is intentionally minimal:

my-team/
├── schema.graphqls   # SDL: Types, Queries, Mutations
└── resolvers/        # Implementation: how each field is fetched
    ├── BookingResolver.kt
    ├── GuestResolver.kt
    └── ...

That's it. The module owner is responsible for:

  • The schema slice they declare (Types / Queries / Mutations).
  • The resolvers that implement those fields.
  • The tests for their module's behaviour.

The module owner does not operate:

  • A GraphQL server.
  • A federation router.
  • An RPC contract with other modules.
  • Any GraphQL infrastructure.

The runtime owner (the platform team) handles execution, scaling, integration, and the cross-module composition.

What you get from modules over subgraphs

Compared with the subgraph-per-domain pattern:

  • Lower per-team operational cost. No subgraph server to run, patch, monitor, or scale per team.
  • Lower domain-team expertise overhead. Domain teams write domain logic, not GraphQL infrastructure code.
  • Faster on-ramp. A new team adopts the platform by adding a directory, not by spinning up a service.
  • Tighter cross-module composition. Modules in the same runtime can share types and reference each other's fields directly, without crossing a network boundary or going through a Federation router.

What you give up:

  • Per-team release autonomy. Modules in the same runtime ride the runtime's release train. (Cross-runtime composition via Federation can recover this for runtimes serving genuinely independent organisations.)
  • Per-team isolation by default. A bad resolver in module X shares the runtime process with module Y. Subgraph isolation is process-level by construction; module isolation requires the runtime to enforce per-module budgets explicitly.

What you get from modules over a single-service UBFF

Compared with the UBFF pattern:

  • Schema-level ownership clarity. A module is a hard boundary: this team owns these Types, these Queries, these Mutations. UBFF schemas with shared-ownership monorepos blur this boundary.
  • Per-module testing and CI. Modules can be tested independently without spinning up the whole UBFF.
  • More than one runtime. UBFF is one runtime by definition; multi-tenant runtimes can be partitioned so closely-related modules share a runtime and unrelated modules don't.

What you give up:

  • One-codebase simplicity. UBFF is the simplest mental model (it's just one service); module-based decentralisation requires a module abstraction that the runtime understands.
  • Single-deploy-artifact governance. UBFF has one deploy artifact; a multi-tenant runtime with N modules has N module versions inside one runtime.

Canonical wiki instance: Viaduct

Viaduct is the wiki's canonical instance. The post's contribution-contract description is the canonical articulation of the pattern: "A team wanting to contribute simply creates a directory for their module, defines their schema definition language (SDL) and resolvers, and they are ready to serve."

Wider applicability

The module-based decentralisation pattern is not GraphQL- specific in shape — the same trade-off shows up wherever a shared runtime / library / framework lets domain teams contribute modules instead of standing up their own service:

  • Plugin architectures in IDEs, build tools, and editors — one runtime, many plugin modules. Same trade.
  • Embedded workflow engines like Skipper — Skipper is the in-process cousin: rather than running a Temporal/Cadence cluster (the "distribute development through services" equivalent), Skipper runs as a library inside the host service (the "distribute development through modules" equivalent).
  • Monorepo ownership systems — a monorepo with codeowners is a similar pattern at the source-control altitude.
  • Service-mesh extensibility frameworks (e.g., Envoy extensions, Wasm filters) — one runtime, many extension modules.

What's distinctive at the GraphQL altitude is that the schema is the unit of decentralised contribution; that's what makes modules a coherent decentralisation primitive instead of just an architectural label.

Hard problems

The same hard problems that patterns/multi-tenant-graphql-runtime surfaces apply here — this pattern is the contribution-model face of that runtime-substrate pattern. The two patterns are intentionally separated on the wiki because:

  • The runtime pattern can in principle host modules from different contribution models (modules built with different SDL conventions, different testing standards, etc.).
  • The module-based decentralisation pattern can in principle be implemented by different runtimes (Viaduct, but also hypothetical alternatives that aren't GraphQL-based).

In practice, Viaduct co-instantiates both.

Seen in

Last updated · 542 distilled / 1,571 read