Skip to main content

Apollo Federation Concepts

The SensorUp GraphQL API is built on Apollo Federation v2.3, which enables multiple independent GraphQL services (subgraphs) to be composed into a single, unified graph.

What is Apollo Federation?

Apollo Federation is an architecture for building distributed GraphQL APIs. Instead of one monolithic GraphQL server, Federation allows you to:
  • Separate concerns: Each subgraph owns a specific domain
  • Scale independently: Subgraphs can be deployed and scaled separately
  • Compose seamlessly: The gateway stitches subgraphs into one unified API
  • Extend types: Subgraphs can add fields to types defined elsewhere

Architecture Overview

When you query the gateway:
  1. Gateway receives your GraphQL query
  2. Creates a query plan across relevant subgraphs
  3. Executes queries against subgraphs (in parallel where possible)
  4. Stitches results together
  5. Returns unified response

Key Federation Concepts

Entities

Entities are types that can be referenced and extended across subgraphs. They’re marked with the @key directive.
type Asset @key(fields: "profile id") {
  profile: ID!
  id: ID!
  displayName: String!
}
The @key directive specifies the fields needed to uniquely identify an instance of this type.

Type Extensions

Subgraphs can extend types defined in other subgraphs using @extends:
type Asset @key(fields: "profile id") @extends {
  profile: ID! @external
  id: ID! @external
  issueSubject: IssueSubject!  # Added by su-issues subgraph
}
This allows the su-issues subgraph to add the issueSubject field to the Asset type originally defined in su-assets.

Entity Resolution

When a subgraph needs to resolve an entity, it provides a reference resolver:
// In su-issues subgraph
{
  Asset: {
    __resolveReference(reference) {
      // reference = { profile: "wells", id: "well-123" }
      // Return asset-specific issue information
      return {
        profile: reference.profile,
        id: reference.id,
        issueSubject: getIssueSubjectForAsset(reference)
      }
    }
  }
}

SensorUp Federation Structure

Entity Ownership

EntityOwnerExtended By
Assetsu-assetssu-issues, su-eventhub
Usersu-usersu-auth
Sitesu-sitessu-methane-ldar-backend
Issuesu-issues(none)
EmissionObservationsu-methane-ldar-backend(none)

Shared Types

Some types are marked @shareable, meaning multiple subgraphs can define them:
type PageInfo @shareable {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}
Common shareable types:
  • PageInfo - Pagination metadata
  • GeoJSON* types - Geospatial types
  • Scalar types - DateTime, JSONObject

Cross-Subgraph Queries

Federation enables queries that span multiple subgraphs seamlessly.

Example: Asset with Issue Information

query GetAssetWithIssues($profile: ID!, $assetId: ID!) {
  assetProfiles {          # su-assets subgraph
    byId(profile: $profile) {
      assetById(id: $assetId) {
        id
        displayName      # su-assets
        properties       # su-assets
        issueSubject {   # su-issues extends Asset
          type
          reference
          issues {       # su-issues
            id
            title
            status
          }
        }
      }
    }
  }
}
The gateway:
  1. Queries su-assets for asset data
  2. Takes the asset reference
  3. Queries su-issues for issue information
  4. Merges the results

Example: User with Authentication

query GetUserDetails($userId: ID!) {
  user(id: $userId) {        # su-user subgraph
    id
    name                     # su-user
    email                    # su-user
    devices {                # su-auth extends User
      deviceKey              # su-auth
      name                   # su-auth
      lastSignedInAt         # su-auth
    }
  }
}

Federation Directives

@key

Marks a type as an entity with a unique key:
type Asset @key(fields: "profile id") {
  profile: ID!
  id: ID!
  # ...
}
Multiple keys are supported:
type Asset
  @key(fields: "profile id")
  @key(fields: "catalogId dataset assetId") {
  # ...
}

@extends

Indicates a type is being extended from another subgraph:
type Asset @key(fields: "profile id") @extends {
  profile: ID! @external
  id: ID! @external
  newField: String!
}

@external

Marks fields that are defined in another subgraph:
type Asset @key(fields: "profile id") @extends {
  profile: ID! @external
  id: ID! @external
  computedField: String! @requires(fields: "profile id")
}

@requires

Specifies fields needed from the base type to resolve a field:
type Asset @key(fields: "profile id") @extends {
  profile: ID! @external
  displayName: String! @external
  enrichedName: String! @requires(fields: "displayName")
}

@provides

Optimizes queries by providing fields from related entities:
type AssetProfile @key(fields: "profile") {
  upSpecification: UpSpecification @provides(fields: "catalogId dataset")
}

@shareable

Allows multiple subgraphs to define the same type/field:
type PageInfo @shareable {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
}

Query Planning

The gateway creates an optimized query plan for each request.

Simple Query

query {
  session {
    username
    authenticated
  }
}
Query plan:
QueryPlan {
  Fetch(service: "su-auth") {
    { session { username authenticated } }
  }
}

Cross-Subgraph Query

query {
  assetProfiles {
    byId(profile: "wells") {
      assets(first: 10) {
        edges {
          node {
            id
            displayName    # su-assets
            issueSubject { # su-issues
              issues {
                id
                title
              }
            }
          }
        }
      }
    }
  }
}
Query plan:
QueryPlan {
  Sequence {
    Fetch(service: "su-assets") {
      { assetProfiles { byId(profile: "wells") {
          assets(first: 10) {
            edges {
              node { __typename profile id displayName }
            }
          }
        }
      }}
    },
    Flatten(path: "assetProfiles.byId.assets.edges.@.node") {
      Fetch(service: "su-issues") {
        { ... on Asset { __typename profile id issueSubject {
            issues { id title }
          }
        }}
      }
    }
  }
}

Performance Considerations

Parallel Execution

Federation executes independent fetches in parallel:
query {
  session {        # su-auth - fetched in parallel
    username
  }
  assetProfiles {  # su-assets - fetched in parallel
    all(first: 10) {
      edges {
        node {
          title
        }
      }
    }
  }
}

N+1 Problem

Be aware of the N+1 problem with entity references:
query {
  issues {
    all(first: 100) {
      edges {
        node {
          id
          subject {
            assetReference {
              asset {              # Potentially 100 lookups to su-assets!
                displayName
              }
            }
          }
        }
      }
    }
  }
}
Solution: Federation batches entity resolutions using DataLoader-like mechanisms.

Debugging Federation

Query Plan Visualization

Use Apollo Studio to visualize query plans and identify performance bottlenecks.

Subgraph Errors

When a subgraph fails, the error includes the service name:
{
  "errors": [
    {
      "message": "Failed to fetch from subgraph 'su-assets'",
      "extensions": {
        "serviceName": "su-assets",
        "code": "SUBREQUEST_HTTP_ERROR"
      }
    }
  ]
}

Trace Subgraph Calls

Monitor subgraph execution times:
{
  "extensions": {
    "ftv1": "base64-encoded-trace-data"
  }
}

Best Practices

  1. Design entity keys carefully: Choose stable, immutable fields for @key
  2. Minimize entity hops: Deeply nested cross-subgraph queries can be slow
  3. Use @provides sparingly: Only when it significantly reduces fetches
  4. Batch related queries: Combine multiple operations in one request
  5. Monitor query plans: Use Apollo Studio to identify inefficient plans
  6. Handle partial failures: Subgraph failures may return partial data
  7. Version subgraph changes: Coordinate breaking changes across subgraphs
  8. Test entity resolution: Ensure reference resolvers handle all key combinations

Differences from Schema Stitching

FeatureFederationSchema Stitching
Type ownershipClear owner per typeAmbiguous
Type extensionNative supportManual
Query planningAutomaticManual
PerformanceOptimizedVariable
DXBetterComplex

Resources

Studio Access

For detailed query planning and performance monitoring, access Apollo Studio: https://studio.apollographql.com/graph/su-graphql-t912yf/ (Contact your SensorUp account team for access)