# Query planning
The gateway plans subgraph calls for each operation, fetches any required entity fields, and combines the results into a GraphQL response.
# Explain a query plan
Use explain to see which subgraphs a query will call without executing it. For the lookup example below:
val plan = interpreter.explain("""
query {
product(id: "p1") {
name
reviews { body }
}
}
""")
A plan for this query could look like:
query
fetch catalog at $.product fields [name, id (key)]
fetch reviews after catalog at $.product via Product(id) fields [reviews.body]
Each fetch names a subgraph. $.product is the location in the client response. (key) marks a field needed for a later lookup, even if the client did not request it. after catalog means that reviews must wait for the catalog result.
# Connecting objects across ordinary services
Federation schemas already explain how to fetch an entity from another service. With ordinary GraphQL services, you provide that information using a Lookup.
Suppose the catalog service exposes this schema:
type Query {
product(id: ID!): Product
}
type Product {
id: ID!
name: String!
}
The reviews service adds reviews to Product and exposes a batch lookup:
type Query {
productsByIds(ids: [ID!]!): [Product!]!
}
type Product {
id: ID!
reviews: [Review!]!
}
type Review {
body: String!
}
Use the reviews schema above as reviewsSdl, and describe how the service fetches products:
import caliban.gateway.{ Lookup, Subgraph }
import zio.http._
val reviews = Subgraph
.graphql("reviews", url"http://reviews:8080/graphql", reviewsSdl)
.withLookup(
Lookup.list(
"Product",
"productsByIds",
"ids" -> Lookup.Argument.batch(Lookup.Argument.key("id"))
)
)
Compose the catalog and reviews subgraphs. A client can then ask:
query {
product(id: "p1") {
name
reviews { body }
}
}
The gateway fetches product from the catalog, including id even though the client did not request it. It then calls productsByIds(ids: ["p1"]) on reviews and attaches the returned reviews to that product. The lookup belongs on the service being called, which is reviews here.
Use Lookup.single when the subgraph fetches one object at a time. Use Lookup.list when it accepts several keys in one request:
- Results can arrive in any order: the gateway matches each returned object to its key through the key fields. Return non-null objects and omit missing ones.
Argument.key("id")reads theidfrom the object that the gateway is fetching.Argument.obj(...)builds an input object for the subgraph.Argument.batch(...)builds one argument value for each requested object.Lookup.listreads keys only insideArgument.batch, andLookup.singledoes not accept it. The compiler rejects both mistakes.
For a service that exposes productById(id: ID!): Product, use:
Lookup.single(
"Product",
"productById",
"id" -> Lookup.Argument.key("id")
)
Prefer a batch lookup wherever the subgraph supports one. It collapses several objects into a single subgraph request.
# Operation cache
The gateway reuses validated operations and query plans for repeated requests. Subgraph calls still fetch current data. Set the total estimated cache weight with GatewayConfig.withMaxOperationCacheWeight:
val bounded = gateway.withConfig(_.withMaxOperationCacheWeight(8L * 1024L * 1024L))
If you use Configurator.setValidations, reuse the same validation functions across requests. Creating fresh lambdas prevents cache reuse.
Document resolution runs before cache lookup. Authorization runs before execution, including on cache hits. A resolution hook can disable caching for a request with cacheable = false.
# Supported operations
The gateway supports queries, mutations, and subscriptions. It does not support @defer or @stream incremental responses from subgraphs.