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

Entity Resolution

When Apollo Router receives a query that spans multiple subgraphs, it needs to resolve entity references. The gateway includes production-ready entity resolution with DataLoader batching.

How Entity Resolution Works

  1. Router sends _entities query with representations
  2. Gateway receives representations (e.g., { __typename: "User", id: "123" })
  3. Gateway calls your gRPC backend to resolve the entity
  4. Gateway returns the resolved entity data

Configuring Entity Resolution

use grpc_graphql_gateway::{
    Gateway, GrpcClient, EntityResolverMapping, GrpcEntityResolver
};
use std::sync::Arc;

// Configure entity resolver with DataLoader batching
let resolver = GrpcEntityResolver::builder(client_pool)
    .register_entity_resolver(
        "User",
        EntityResolverMapping {
            service_name: "UserService".to_string(),
            method_name: "GetUser".to_string(),
            key_field: "id".to_string(),
        }
    )
    .register_entity_resolver(
        "Product",
        EntityResolverMapping {
            service_name: "ProductService".to_string(),
            method_name: "GetProduct".to_string(),
            key_field: "upc".to_string(),
        }
    )
    .build();

let gateway = Gateway::builder()
    .with_descriptor_set_bytes(DESCRIPTORS)
    .enable_federation()
    .with_entity_resolver(Arc::new(resolver))
    .add_grpc_client("UserService", user_client)
    .add_grpc_client("ProductService", product_client)
    .build()?;

DataLoader Batching

The built-in GrpcEntityResolver uses DataLoader to batch entity requests:

Query requests:
  - User(id: "1")
  - User(id: "2")  
  - User(id: "3")

Without DataLoader: 3 gRPC calls
With DataLoader: 1 batched gRPC call

Benefits

  • No N+1 Queries - Concurrent requests are batched
  • Automatic Coalescing - Duplicate keys are deduplicated
  • Per-Request Caching - Same entity isn’t fetched twice per request

Custom Entity Resolver

Implement the EntityResolver trait for custom logic:

use grpc_graphql_gateway::federation::{EntityConfig, EntityResolver};
use async_graphql::{Value, indexmap::IndexMap, Name};
use async_trait::async_trait;

struct MyEntityResolver {
    // Your dependencies
}

#[async_trait]
impl EntityResolver for MyEntityResolver {
    async fn resolve_entity(
        &self,
        config: &EntityConfig,
        representation: &IndexMap<Name, Value>,
    ) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
        let typename = &config.type_name;
        
        match typename.as_str() {
            "User" => {
                let id = representation.get(&Name::new("id"))
                    .and_then(|v| v.as_str())
                    .ok_or("missing id")?;
                
                // Fetch from your backend
                let user = self.fetch_user(id).await?;
                
                Ok(Value::Object(indexmap! {
                    Name::new("id") => Value::String(user.id),
                    Name::new("name") => Value::String(user.name),
                    Name::new("email") => Value::String(user.email),
                }))
            }
            _ => Err(format!("Unknown entity type: {}", typename).into()),
        }
    }
}

EntityResolverMapping

Configure how each entity type maps to a gRPC method:

FieldDescription
service_nameThe gRPC service name
method_nameThe RPC method to call
key_fieldThe field in the request message that holds the key

Query Example

When Router sends:

query {
  _entities(representations: [
    { __typename: "User", id: "123" }
    { __typename: "User", id: "456" }
  ]) {
    ... on User {
      id
      name
      email
    }
  }
}

The gateway:

  1. Extracts the representations
  2. Groups by __typename
  3. Batches calls to the appropriate gRPC services
  4. Returns resolved entities

Error Handling

Entity resolution errors are returned per-entity:

{
  "data": {
    "_entities": [
      { "id": "123", "name": "Alice", "email": "alice@example.com" },
      null
    ]
  },
  "errors": [
    {
      "message": "User not found: 456",
      "path": ["_entities", 1]
    }
  ]
}

Performance Tips

  1. Use DataLoader - Always batch entity requests
  2. Implement bulk fetch - Have gRPC methods that fetch multiple entities
  3. Cache wisely - Consider caching frequently accessed entities
  4. Monitor - Track entity resolution latency with metrics