DEV Community

WunderGraph
WunderGraph

Posted on • Originally published at wundergraph.com

How the Fission Algorithm Works: Top-Down GraphQL Federation Design

TL;DR

Fission inverts GraphQL Federation. Instead of composing subgraphs into a supergraph, you design the supergraph first and Fission decomposes it into subgraph specs, handling entity keys, directives, and validation automatically.


In a previous post, I argued that Federation's composition model is backwards. It builds the supergraph from the bottom up when it should be designed from the top down.

This post is a technical deep dive into how the Fission algorithm actually does that.

The Problem Fission Solves

To understand Fission, first you need to understand what makes schema changes expensive in Federation.

Consider a federated graph with three subgraphs:

# Subgraph: Users
type User @key(fields: "id") {
  id: ID!
  name: String!
  email: String!
}

type Query {
  user(id: ID!): User
  users: [User!]!
}
Enter fullscreen mode Exit fullscreen mode
# Subgraph: Orders
type User @key(fields: "id") {
  id: ID!
  orders: [Order!]!
}

type Order @key(fields: "id") {
  id: ID!
  total: Float!
  status: OrderStatus!
  createdAt: DateTime!
}

enum OrderStatus {
  PENDING
  SHIPPED
  DELIVERED
}
Enter fullscreen mode Exit fullscreen mode
# Subgraph: Products
type Order @key(fields: "id") {
  id: ID!
  items: [OrderItem!]!
}

type OrderItem {
  product: Product!
  quantity: Int!
}

type Product @key(fields: "id") {
  id: ID!
  name: String!
  price: Float!
}
Enter fullscreen mode Exit fullscreen mode

Composition produces a supergraph where you can query:

query {
  user(id: "1") {
    name
    orders {
      total
      status
      items {
        product {
          name
          price
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now a frontend team wants to add shipping tracking. This is their dream query:

query {
  user(id: "1") {
    orders {
      shipping {
        carrier
        trackingNumber
        estimatedDelivery
        currentLocation
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

In the traditional workflow, the frontend team has to figure out:

  • shipping should be a field on Order, but which subgraph owns Order?
  • Both the Orders subgraph and the Products subgraph define Order as an entity.
  • carrier and trackingNumber come from the shipping provider.
  • estimatedDelivery might come from logistics.
  • currentLocation requires real-time tracking data.
  • Should this be a new subgraph? An extension of Orders? Both?

The frontend team can't answer these questions, so the platform team needs to be brought in. Meetings are scheduled and the process begins.

Fission automates the hardest parts of this process.

What Fission Actually Is

Before we walk through an example, it's important to understand what Fission is and what it isn't.

Fission is not just the opposite of composition, a batch algorithm that takes a supergraph and splits it into subgraphs. It's a stateful schema graph engine, an in-memory system that maintains two synchronized views of your federated graph at all times:

  1. The supergraph view — the unified, consumer-facing schema. This is the canonical source of truth for what the API looks like.
  2. The subgraph views — one per service, representing what each team is responsible for implementing.

These two views are kept in sync. When you make a change to either one, Fission will automatically propagate the consequences: renaming a type cascades to every subgraph and every field reference. Adding a type to a subgraph pulls in all its transitive dependencies. Entity @key directives propagate automatically.
@shareable directives are added bidirectionally when fields exist in multiple subgraphs.

Every edit follows the same pattern: validate first, mutate second, update indexes, and return a structured result. If validation fails, no state changes happen. This ensures you never have a half-applied edit.

The key insight is the synchronization contract:

The supergraph view represents the union of all subgraph views,
plus any types and fields not yet assigned to a subgraph.

This means the architect can design the complete API shape first and then decide how to distribute the implementation across subgraphs.

The supergraph doesn't have to be assembled from the bottom up.
It can be designed from the top down.

How It Works in Practice

Let's walk through the shipping example step by step.

Step 1: Design on the Supergraph

The architect adds the new types and fields directly on the supergraph:

# New additions to the supergraph
type Order @key(fields: "id") {
  id: ID!
  shipping: ShippingInfo! # new field
}

type ShippingInfo { # new type
  carrier: String!
  trackingNumber: String!
  estimatedDelivery: DateTime!
  currentLocation: String
}
Enter fullscreen mode Exit fullscreen mode

At this point, ShippingInfo exists in the supergraph but isn't assigned to any subgraph yet. The shipping field on Order is defined but no team is responsible for implementing it.

The supergraph is the API you want.

Step 2: Assign to Subgraphs

The architect assigns the new type to an existing subgraph, or if necessary, creates a new one.

Let's say they assign ShippingInfo and the shipping field on Order to a new "Shipping" subgraph.

When this happens, Fission automatically does several things:

Transitive dependency propagation:
ShippingInfo references String, DateTime, and Boolean.
These are all scalars that are already available. But if ShippingInfo had a field like warehouse: Warehouse!, and the Warehouse type doesn't exist in the Shipping subgraph yet, Fission will automatically pull it in, along with anything Warehouse references. This propagation is cycle-safe; mutual type references don't cause infinite loops.

Entity key propagation:
Order is an entity with @key(fields: "id") in the Orders and Products subgraphs. When the Shipping subgraph extends Order, Fission automatically adds @key(fields: "id") to the Shipping subgraph's Order definition. The entity remains resolvable across subgraph boundaries.

Shareable propagation:
If the shipping field ends up defined in multiple V2 subgraphs, @shareable is added bidirectionally to all subgraphs containing that field, not just the new one. This ensures the rendered SDL is always valid for composition.

Step 3: Continuous Validation

Every edit is validated before it's applied.

Fission enforces a comprehensive set of invariants:

  • Reference integrity: you cannot remove a type that's still referenced by other fields. The error includes the exact coordinates that still reference it.
  • Last-child protection: you cannot remove the last field from a type that's still referenced elsewhere.
  • Key field validity: you cannot rename or change the type of a field that participates in a @key field set. The key selection tree would break.
  • Type-kind compatibility: you cannot have User as an Object in one subgraph and an Interface in another.
  • Interface consistency: when a type implements an interface, it must have all the interface's fields with compatible types.

If any validation fails, the edit is rejected and the state remains unchanged. The error message includes enough information for the Hub UI to show exactly what went wrong and suggest a fix.

Cascading Renames

Renaming is the most complex operation, and it's where Fission's
reference tracking really shines.

When a type is renamed at the supergraph level — say Order becomes CustomerOrder — Fission cascades the rename:

  1. Every subgraph containing Order has its node renamed
  2. Every field typed as Order or [Order!]! is updated
  3. Every @key field set referencing Order is regenerated from its selection tree
  4. Interface implementations and union memberships are updated
  5. All reverse indexes are re-keyed

The SDL is not stored as text, it's regenerated from structured state. This means @key(fields: "id") always reflects the current field names. If you rename the id field to orderId, the key directive automatically becomes @key(fields: "orderId").

A Multi-Team Example

Let's trace through a more complex scenario to see how these steps work together.

Starting state: the three subgraphs from above (Users, Orders, Products).

Dream query:

query OrderDetails($orderId: ID!) {
  order(id: $orderId) {
    total
    status
    items {
      product {
        name
        price
        inventory {
          # new
          inStock
          warehouseLocation
        }
      }
    }
    shipping {
      # new
      carrier
      trackingNumber
      estimatedDelivery
    }
    payment {
      # new
      method
      last4
      receiptUrl
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Three new capabilities: inventory, shipping, and payment.

Step 1: Design on the supergraph

The architect adds all new types and fields to the supergraph:

  • Product.inventory: InventoryInfo! (new field on existing entity)
  • InventoryInfo with inStock and warehouseLocation (new type)
  • Order.shipping: ShippingInfo! (new field on existing entity)
  • ShippingInfo with carrier, trackingNumber, estimatedDelivery (new type)
  • Order.payment: PaymentInfo! (new field on existing entity)
  • PaymentInfo with method, last4, receiptUrl (new type)
  • Query.order(id: ID!): Order (new root field)

At this point, the supergraph describes the ideal API. None of these new types are assigned to subgraphs yet.

Step 2: Assign to subgraphs

The architect assigns each new type to a subgraph on the Hub canvas:

New item Assigned to What Fission automates
InventoryInfo + Product.inventory New Inventory subgraph Product @key(fields: "id") is propagated to the Inventory subgraph. Product entity is automatically resolvable.
ShippingInfo + Order.shipping New Shipping subgraph Order @key(fields: "id") is propagated. If ShippingInfo referenced other types, they'd be pulled in transitively.
PaymentInfo + Order.payment New Payments subgraph Order @key(fields: "id") is propagated.
Query.order Orders subgraph Orders already owns the Order entity. Field is added, references are tracked.

The architect decides that Product.inventory goes to a new Inventory subgraph because the Products team doesn't have warehouse data.

Continuous validation

Every assignment is validated as it happens:

  • Entity keys are resolvable across the new subgraph boundaries
  • No type-kind conflicts (e.g., Order is an Object in all subgraphs)
  • Naming conventions pass (Hub's governance layer flags inStock — should it be isInStock? Architect approves the exception)
  • Reference integrity is maintained

If the architect tried to assign ShippingInfo to a subgraph in a way that would break composition, the edit would be rejected with a specific error before any state changes.

From Design to Subgraph Specs

At any point, Fission can render valid GraphQL SDL from structured state. This means the output reflects every cascading update, renamed field, and propagated directive.

Subgraph SDL Specifications

For each affected subgraph, Fission renders the exact SDL that the team needs to implement:

# New subgraph: Shipping
type Order @key(fields: "id") {
  id: ID!
  shipping: ShippingInfo!
}

type ShippingInfo {
  carrier: String!
  trackingNumber: String!
  estimatedDelivery: DateTime!
}
Enter fullscreen mode Exit fullscreen mode
# New subgraph: Payments
type Order @key(fields: "id") {
  id: ID!
  payment: PaymentInfo!
}

type PaymentInfo {
  method: String!
  last4: String!
  receiptUrl: String!
}
Enter fullscreen mode Exit fullscreen mode
# New subgraph: Inventory
type Product @key(fields: "id") {
  id: ID!
  inventory: InventoryInfo!
}

type InventoryInfo {
  inStock: Boolean!
  warehouseLocation: String!
}
Enter fullscreen mode Exit fullscreen mode
# Modified subgraph: Orders (new root field)
type Query {
  order(id: ID!): Order # new
}
Enter fullscreen mode Exit fullscreen mode

Each new subgraph automatically includes @key(fields: "id") on the entities it extends, because Fission propagated these from the existing subgraphs.

Supergraph Preview

Fission can also render the full supergraph SDL at any time. The architect can verify that the dream query will actually work before any code is written. Because the supergraph is the source of truth, not a composition result.

Design Decisions

A few architectural choices in Fission that are worth explaining:

The supergraph is the source of truth.
In traditional Federation, the supergraph is a side effect of composition. In Fission, it's the canonical representation. Removing a type from a subgraph does not remove it from the supergraph. You can design the complete API shape first, then decide how to distribute implementation. This is backwards from composition.

Fission automates consequences, not decisions.
The architect decides where types and fields belong. Fission automates everything that follows: transitive dependency propagation, entity key propagation, @shareable management, reference tracking, and cascading renames. The goal is to automate the mechanical work and surface the judgment calls to humans.

Validate first, mutate second.
Every edit validates all preconditions before making any state change. If validation fails, the state is unchanged. Because invalid states are caught at design time, the "composition fails after three teams implement their parts" scenario is prevented.

Federation directives are structured state, not text.
@key, @shareable, @inaccessible, @override, and @provides are modeled as structured state with their own selection trees, indexes, and validation rules. When you rename a field that participates in @key(fields: "id name"), the key field set is automatically updated.

Fission tracks the full lifecycle.
From proposal to review to implementation to composition, the entire workflow is tracked in one place. When a subgraph team ships their implementation, it's checked against the original specification. The proposal isn't complete until all specifications are met and the final composition succeeds.

The Bigger Picture

Fission is the algorithmic counterpart to Hub's collaboration model.

Hub provides the canvas, the collaboration workflow, and the governance layer. Fission provides the ability to take a high-level design intent and translate it into concrete, actionable subgraph specifications.

Together, they make it possible to design like a monolith and implement as microservices.

The supergraph is designed as a coherent whole. The implementation is distributed across teams. The algorithm handles the translation between the two.

If you want to see Fission in action, watch the webinar
or schedule a walkthrough.

Top comments (0)