Remote Resolvers
Orders knows what Inventory can do from its contract. A resolver answers a different question: how do I reach Inventory when it is not mounted here? It maps the service's protobuf identity to a remote transport.
Watch one call
Keep the Orders handler unchanged. Switch where Inventory runs to see which route the server uses.
const stock = await ctx.call(
'shop.v1.InventoryService/CheckStock',
create(CheckStockRequestSchema, { sku: req.sku }),
);{ units: 7 }7 units of A-1 are in stock.
Illustration, not a live RPC. Run the complete example linked below to make the real call.
How is this call routed?
Two services. One process. Orders and Inventory are mounted on the same server. The catalog call reaches Inventory in-process; no socket or resolver lookup is needed.
- Look up InventoryService/CheckStock in the catalog.
- Find Inventory mounted on this server.
- Call the Inventory handler in-process and return its stock message.
createServer({
services: [orders, inventory],
catalog: serviceCatalog,
remoteResolver: mapResolver({
[InventoryService.typeName]: inventoryTransport,
}),
});inventoryTransport is a configured gRPC transport to the other process. A local target takes priority even when a remote route exists.
Read the scenes without animation
| Scene | Where Inventory is mounted | What the call does |
|---|---|---|
| Together | On the Orders server | Calls the handler in-process. The resolver is not consulted. |
| Separate | On another server, with a configured route | Uses the resolver's gRPC transport. |
| No route | Not on the Orders server; resolver returns null | Rejects with Code.Unavailable, without calling Inventory. |
Choose a routing rule
- Explicit addresses:
mapResolvermaps each known service to a transport. - One upstream:
singleTransportResolversends remote services to one destination. - A naming convention:
dnsResolverbuilds URLs from a DNS-style template. - Deployment configuration:
perServiceEnvResolverreads service URLs from environment variables.
A resolver creates a route, not a health check. It is synchronous and does not perform network discovery itself. Successful routes are cached; a failed RPC does not automatically refresh the route or retry the call.
Go deeper
- Run the complete six-file example.
- Configure resolvers, cache lifetime, endpoint hints and mocks.
- Understand where catalog knowledge comes from.
Find a section from the full guide
The detailed material is preserved on a separate page. Earlier section links still work here.
- The RemoteResolver contract
- Route cache lifetime
- Built-in resolvers
- singleTransportResolver(transport)
- mapResolver({ [typeName]: transport })
- dnsResolver(options)
- perServiceEnvResolver(map, options?)
- Endpoint hints
- Composing resolvers
- Testing with mocks
- Transport-owned vs application-owned interceptors
- Kubernetes, Istio, and service meshes