GraphQL Federation Export

FakeDataDSL can export your schemas to GraphQL Federation 2.0 compatible SDL, perfect for microservices architectures using Apollo Federation, Netflix DGS, or other federated GraphQL implementations.

Quick Start

# Export single schema
fake_data_dsl export schemas/user.dsl -f graphql-federation -o user.graphql

# Export all schemas
fake_data_dsl export schemas/ --all -f graphql-federation -o schema.graphql
# Ruby API
schema = FakeDataDSL.load("schemas/user.dsl")
graphql = FakeDataDSL::Export::GraphQLFederation.new(schema).export

puts graphql

Basic Usage

Single Schema Export

# Input schema (user.dsl)
# User:
#   id: uuid
#   name: name
#   email: email
#   role: enum(user, admin, moderator)
#   active: boolean
#   created_at: timestamp

schema = FakeDataDSL.load("schemas/user.dsl")
exporter = FakeDataDSL::Export::GraphQLFederation.new(schema)
puts exporter.export

Output:

extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0")

type User @key(fields: "id") {
  id: ID!
  name: String!
  email: String!
  role: UserRole!
  active: Boolean!
  created_at: DateTime!
}

enum UserRole {
  USER
  ADMIN
  MODERATOR
}

extend type Query {
  user(id: ID!): User
  users(first: Int, after: String): UserConnection!
}

type UserConnection {
  edges: [UserEdge!]!
  pageInfo: PageInfo!
}

type UserEdge {
  node: User!
  cursor: String!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

Multiple Schemas

# Export multiple schemas as one federated graph
exporter = FakeDataDSL::Export::GraphQLFederation.new(
  FakeDataDSL.load("schemas/user.dsl"),
  FakeDataDSL.load("schemas/order.dsl"),
  FakeDataDSL.load("schemas/product.dsl")
)

puts exporter.export

Federation Directives

@key Directive

Entity keys are automatically inferred from id fields or fields marked with @key:

# In DSL
User:
  id: uuid @key
  email: email @key  # Additional key

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

@external and @provides

For cross-service references:

# In DSL
Order:
  id: uuid
  user_id: Ref(User.id)  # Reference to User service
  items: array(OrderItem, 1..10)
  total: money

# Configure federation
FakeDataDSL::Export::GraphQLFederation.configure do |config|
  config.external_types = ["User"]
  config.provides = {
    "Order" => { "user" => "id name email" }
  }
end

Output:

type Order @key(fields: "id") {
  id: ID!
  user: User! @provides(fields: "id name email")
  items: [OrderItem!]!
  total: Float!
}

type User @key(fields: "id", resolvable: false) @external {
  id: ID!
}

@shareable

Mark types as shareable across services:

# In DSL
Address:
  @shareable
  street: text
  city: city
  country: country

Output:

type Address @shareable {
  street: String!
  city: String!
  country: String!
}

Subgraph Configuration

Service Definition

exporter = FakeDataDSL::Export::GraphQLFederation.new(schema,
  service_name: "users-service",
  service_url: "https://users.example.com/graphql",
  version: "2.0"
)

Multiple Subgraphs

Generate separate files for each subgraph:

# Define subgraph boundaries
subgraphs = {
  "users" => ["User", "Profile"],
  "orders" => ["Order", "OrderItem"],
  "products" => ["Product", "Category"]
}

subgraphs.each do |name, types|
  schemas = types.map { |t| FakeDataDSL.schema(t) }
  exporter = FakeDataDSL::Export::GraphQLFederation.new(*schemas,
    service_name: "#{name}-service"
  )

  File.write("subgraphs/#{name}.graphql", exporter.export)
end

Type Mapping

DSL to GraphQL Types

DSL Type GraphQL Type
uuid ID!
text, name, email String!
number Int!
float, money Float!
boolean Boolean!
date Date!
timestamp DateTime!
enum(...) Custom Enum
array(...) [Type!]!
optional (?) Nullable
json JSON (custom scalar)

Custom Scalars

FakeDataDSL::Export::GraphQLFederation.configure do |config|
  config.custom_scalars = {
    "date" => "Date",
    "timestamp" => "DateTime",
    "json" => "JSON",
    "money" => "BigDecimal",
    "url" => "URL",
    "email" => "Email"
  }
end

Output includes scalar definitions:

scalar Date
scalar DateTime
scalar JSON
scalar BigDecimal
scalar URL
scalar Email

Mutations

Auto-Generated Mutations

exporter = FakeDataDSL::Export::GraphQLFederation.new(schema,
  generate_mutations: true
)

Output:

extend type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!
}

input CreateUserInput {
  name: String!
  email: String!
  role: UserRole
  active: Boolean
}

input UpdateUserInput {
  name: String
  email: String
  role: UserRole
  active: Boolean
}

Custom Mutations

# In DSL with @mutation annotation
User:
  id: uuid
  name: name
  email: email

  @mutations
    create: CreateUser(name!, email!, role?)
    update: UpdateUser(id!, name?, email?, role?)
    deactivate: DeactivateUser(id!, reason?)

Subscriptions

exporter = FakeDataDSL::Export::GraphQLFederation.new(schema,
  generate_subscriptions: true
)

Output:

extend type Subscription {
  userCreated: User!
  userUpdated(id: ID): User!
  userDeleted: ID!
}

Entity Resolution

_entities Query

Federation requires an _entities query for entity resolution:

exporter = FakeDataDSL::Export::GraphQLFederation.new(schema,
  include_entities_query: true  # default: true
)

Output:

union _Entity = User | Order | Product

type Query {
  _entities(representations: [_Any!]!): [_Entity]!
  _service: _Service!
}

type _Service {
  sdl: String!
}

scalar _Any

Interfaces and Unions

Interfaces

# In DSL
Node:
  @interface
  id: uuid

User < Node:
  name: name
  email: email

Product < Node:
  title: text
  price: money

Output:

interface Node {
  id: ID!
}

type User implements Node @key(fields: "id") {
  id: ID!
  name: String!
  email: String!
}

type Product implements Node @key(fields: "id") {
  id: ID!
  title: String!
  price: Float!
}

Unions

# In DSL
SearchResult:
  @union User | Product | Order

Output:

union SearchResult = User | Product | Order

extend type Query {
  search(query: String!): [SearchResult!]!
}

Pagination

Relay-Style Connections

exporter = FakeDataDSL::Export::GraphQLFederation.new(schema,
  pagination_style: :relay  # default
)

Output:

type UserConnection {
  edges: [UserEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type UserEdge {
  node: User!
  cursor: String!
}

Offset Pagination

exporter = FakeDataDSL::Export::GraphQLFederation.new(schema,
  pagination_style: :offset
)

Output:

type UserList {
  items: [User!]!
  total: Int!
  offset: Int!
  limit: Int!
}

extend type Query {
  users(offset: Int, limit: Int): UserList!
}

CLI Options

fake_data_dsl export [SCHEMA_PATH] -f graphql-federation [OPTIONS]

Options:
  -o, --output PATH           Output file path
  --service-name NAME         Service name for subgraph
  --service-url URL           Service URL for federation
  --federation-version VER    Federation version (1 or 2, default: 2)
  --no-mutations              Don't generate mutations
  --no-subscriptions          Don't generate subscriptions
  --no-pagination             Don't generate connection types
  --pagination-style STYLE    Pagination style (relay or offset)
  --external-types TYPES      Comma-separated external type names

Examples:

# Basic export
fake_data_dsl export schemas/ -f graphql-federation -o schema.graphql

# With service configuration
fake_data_dsl export schemas/user.dsl -f graphql-federation \
  --service-name users \
  --service-url https://users.api.example.com/graphql \
  -o users.graphql

# Minimal export (no mutations/subscriptions)
fake_data_dsl export schemas/ -f graphql-federation \
  --no-mutations --no-subscriptions \
  -o types-only.graphql

API Reference

GraphQLFederation.new

FakeDataDSL::Export::GraphQLFederation.new(*schemas, options = {})

Parameters:

  • schemas - One or more Schema objects
  • options[:service_name] - Subgraph service name
  • options[:service_url] - Subgraph service URL
  • options[:version] - Federation version ("1" or "2")
  • options[:generate_mutations] - Include mutations (default: false)
  • options[:generate_subscriptions] - Include subscriptions (default: false)
  • options[:pagination_style] - :relay or :offset
  • options[:include_entities_query] - Include _entities (default: true)
  • options[:external_types] - Array of external type names
  • options[:custom_scalars] - Hash of DSL type => GraphQL scalar

GraphQLFederation#export

exporter.export
# => String containing complete GraphQL SDL

GraphQLFederation#export_to_file

exporter.export_to_file("schema.graphql")

Integration Examples

Apollo Router

# supergraph.yaml
federation_version: 2
subgraphs:
  users:
    routing_url: https://users.example.com/graphql
    schema:
      file: ./users.graphql  # Generated by FakeDataDSL
  orders:
    routing_url: https://orders.example.com/graphql
    schema:
      file: ./orders.graphql

Netflix DGS

// The generated schema works with DGS
@DgsComponent
class UserDataFetcher {
    @DgsQuery
    fun user(@InputArgument id: String): User {
        // Resolver implementation
    }

    @DgsEntityFetcher(name = "User")
    fun user(values: Map<String, Any>): User {
        // Entity resolver for federation
    }
}

Apollo Server

// The generated schema works with Apollo Server
import { buildSubgraphSchema } from '@apollo/subgraph';
import { readFileSync } from 'fs';

const typeDefs = gql(readFileSync('./user.graphql', 'utf8'));

const server = new ApolloServer({
  schema: buildSubgraphSchema({ typeDefs, resolvers })
});

Best Practices

1. One Entity Per Service

# Good: Clear ownership
# users-service owns User, Profile
# orders-service owns Order, OrderItem

# Avoid: Shared ownership
# Both services define User (unless @shareable)

2. Use @key Thoughtfully

# Good: Natural keys
User:
  id: uuid @key
  email: email @key  # Secondary lookup

# Avoid: Composite keys when unnecessary
User:
  id: uuid @key
  tenant_id: uuid @key  # Only if truly needed

3. Keep Schemas Aligned

# Use the DSL as source of truth
# Generate GraphQL from DSL, not the other way around

Troubleshooting

Missing @key Directive

# Make sure id field exists or add @key annotation
User:
  id: uuid  # Auto-detected as key

# Or explicit
User:
  user_id: uuid @key  # Explicit key

Enum Name Conflicts

# Use namespaced enums
User:
  status: enum(active, inactive) @enum_name(UserStatus)

Product:
  status: enum(draft, published) @enum_name(ProductStatus)

Circular References

# Federation handles this via external types
# User service:
User:
  orders: array(Ref(Order))

# Order service:
Order:
  user: Ref(User) @external

See Also