Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Federation Directives

The gateway supports all Apollo Federation v2 directives through proto annotations.

Directive Reference

DirectiveProto OptionPurpose
@keygraphql.entity.keysDefine entity key fields
@shareablegraphql.field.shareableField resolvable from multiple subgraphs
@externalgraphql.field.externalField defined in another subgraph
@requiresgraphql.field.requiresFields needed from other subgraphs
@providesgraphql.field.providesFields this resolver provides
@extendsgraphql.entity.extendExtending entity from another subgraph

@key

Defines how an entity is uniquely identified:

message User {
  option (graphql.entity) = {
    keys: "id"
  };
  string id = 1;
}

Generated:

type User @key(fields: "id") {
  id: ID!
}

Multiple Keys

message Product {
  option (graphql.entity) = {
    keys: "upc"  // Primary key
  };
  string upc = 1;
  string sku = 2;
}

Composite Keys

message Inventory {
  option (graphql.entity) = {
    keys: "warehouseId productId"
  };
  string warehouse_id = 1;
  string product_id = 2;
  int32 quantity = 3;
}

@shareable

Marks fields that can be resolved by multiple subgraphs:

message User {
  string id = 1;
  string name = 2 [(graphql.field) = { shareable: true }];
  string email = 3 [(graphql.field) = { shareable: true }];
}

Generated:

type User {
  id: ID!
  name: String @shareable
  email: String @shareable
}

When to Use

Use @shareable when:

  • Multiple subgraphs can resolve the same field
  • You want redundancy for a commonly accessed field
  • Different subgraphs have the same data source

@external

Marks fields defined in another subgraph that you need to reference:

message User {
  option (graphql.entity) = { extend: true, keys: "id" };
  
  string id = 1 [(graphql.field) = { external: true }];
  string name = 2 [(graphql.field) = { external: true }];
  repeated Review reviews = 3;  // Your field
}

Generated:

type User @extends @key(fields: "id") {
  id: ID! @external
  name: String @external
  reviews: [Review]
}

@requires

Declares that a field requires data from external fields:

message Product {
  option (graphql.entity) = { extend: true, keys: "upc" };
  
  string upc = 1 [(graphql.field) = { external: true }];
  float price = 2 [(graphql.field) = { external: true }];
  float weight = 3 [(graphql.field) = { external: true }];
  
  float shipping_cost = 4 [(graphql.field) = { 
    requires: "price weight" 
  }];
}

Generated:

type Product @extends @key(fields: "upc") {
  upc: ID! @external
  price: Float @external
  weight: Float @external
  shippingCost: Float @requires(fields: "price weight")
}

How It Works

  1. Router fetches price and weight from the owning subgraph
  2. Router sends those values to your subgraph
  3. Your resolver uses them to calculate shippingCost

@provides

Hints that a resolver provides additional fields on referenced entities:

message Review {
  string id = 1;
  string body = 2;
  
  User author = 3 [(graphql.field) = {
    provides: "name email"
  }];
}

Generated:

type Review {
  id: ID!
  body: String
  author: User @provides(fields: "name email")
}

When to Use

Use @provides when:

  • Your resolver already has the nested entity’s data
  • You want to avoid an extra subgraph hop
  • You’re denormalizing for performance

Complete Example

Products Subgraph:

message Product {
  option (graphql.entity) = {
    keys: "upc"
    resolvable: true
  };
  
  string upc = 1 [(graphql.field) = { required: true }];
  string name = 2 [(graphql.field) = { shareable: true }];
  float price = 3 [(graphql.field) = { shareable: true }];
}

Inventory Subgraph:

message Product {
  option (graphql.entity) = {
    extend: true
    keys: "upc"
  };
  
  string upc = 1 [(graphql.field) = { external: true }];
  float price = 2 [(graphql.field) = { external: true }];
  float weight = 3 [(graphql.field) = { external: true }];
  
  int32 stock = 4;
  bool in_stock = 5;
  float shipping_estimate = 6 [(graphql.field) = {
    requires: "price weight"
  }];
}