# How Connection Management Works in the fabrica-kit Router: A Deep Dive into gRPC Client Orchestration

> Explore fabrica-kit router connection management. Discover how gRPC client orchestration utilizes service discovery, load balancing, and route-table coordination for deterministic routing.

- Repository: [Pantheon/fabrica-kit](https://github.com/go-pantheon/fabrica-kit)
- Tags: deep-dive
- Published: 2026-03-02

---

**The fabrica-kit router manages gRPC connections through a layered architecture combining service discovery, load-balancing, route-table coordination, and middleware to ensure deterministic routing across distributed services.**

The `go-pantheon/fabrica-kit` router provides a sophisticated connection management system for gRPC clients in distributed environments. Unlike standard gRPC connections that rely solely on DNS resolution, this router implements a custom balancing layer that integrates with route tables to enforce sharding semantics and single-writer guarantees. Understanding this connection management mechanism is essential for building reliable microservices that require deterministic routing based on object IDs and color keys.

## The Connection Lifecycle Architecture

Connection management in fabrica-kit follows a staged pipeline that transforms a service name into a fully operational gRPC connection with integrated observability.

### Balancer Registration and Type Selection

When initiating a connection, the router first determines the operational mode through `balancer.Type` configuration. In [`router/conn/conn.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/conn/conn.go), the `NewConn` function inspects the requested balancer type to determine route-table interaction permissions.

If `balancer.TypeMaster` is specified, the router registers a master balancer via `balancer.RegisterMasterBalancer` (defined in [`router/balancer/master.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/master.go)). This grants the connection write access to the route table, allowing it to claim ownership of specific routes. Conversely, `balancer.TypeReader` triggers `balancer.RegisterReadOnlyBalancer` (from [`router/balancer/readonly.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/readonly.go)), creating a connection that can only read existing route mappings without modifying them.

### Route-Table Integration

The balancer receives a `routetable.ReadOnlyRouteTable` interface (or `MasterRouteTable` for master types) during initialization. This table serves as the authoritative source for mapping `(oid, color)` tuples to concrete service addresses.

In [`router/balancer/balancer.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/balancer.go), the `weightBalancer.Pick` method queries this table to determine the appropriate endpoint for each request. The route table implementation—such as the Redis-backed version in [`router/routetable/redis/redis.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/routetable/redis/redis.go)—provides the distributed consistency required for multi-client coordination.

## gRPC Connection Establishment

Once the balancer is configured, the router establishes the underlying gRPC transport with custom discovery and filtering mechanisms.

### Service Discovery and Node Filtering

The actual gRPC dialing occurs in [`router/conn/conn.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/conn/conn.go) (lines 46-53), where the router invokes the Kratos gRPC client with a `discovery:///serviceName` scheme. This delegates endpoint resolution to the provided `registry.Discovery` implementation, which returns the current set of available nodes.

Critical to connection management is the node filtering step. The router applies `grpc.WithNodeFilter(balancer.NewFilter())` (line 50 in [`router/conn/conn.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/conn/conn.go)), which ensures that only nodes returned by the balancer's `Pick` logic are eligible for connection. This bridges the custom routing logic with the gRPC client's selector mechanism.

### Middleware Stack Configuration

After establishing the transport, the router wraps the connection with a middleware stack defined in [`router/conn/conn.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/conn/conn.go) (lines 54-60). This stack includes:

- **Recovery middleware**: Prevents panics from crashing the client
- **Metadata propagation**: Ensures context values traverse the wire
- **Tracing**: Distributed tracing integration for observability
- **Metrics**: Project-specific `metrics.Client()` for performance monitoring
- **Logging**: Structured logging for debugging connection issues

The resulting `grpc.ClientConnInterface` is stored within a thin `Conn` wrapper struct (defined in [`router/conn/conn.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/conn/conn.go), lines 23-26), which exposes the standard gRPC client interface while maintaining the configured routing semantics.

## Master vs. Read-Only Connection Patterns

The fabrica-kit router distinguishes between two operational modes that determine how connections interact with the distributed route table.

### Single-Writer Semantics with SetNxOrGet

Master connections implement a claim-based routing mechanism to ensure single-writer semantics for specific object IDs. When a master balancer selects a node via `weightBalancer.Pick` (in [`router/balancer/balancer.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/balancer.go), lines 71-96), it attempts to claim the `(color, oid)` pair using `SetNxOrGet`.

If the claim succeeds (no other client owns the route), the balancer proceeds with the selected address. If another client already claimed the address, the balancer falls back to the address stored in the route table. This mechanism ensures that all clients converge on the same node for a given key, preventing split-brain scenarios in distributed writes.

### TTL-Based Route Renewal

For read-only connections, the router implements automatic route expiration to handle node failures and topology changes. The `renewalRouteTable` (defined in [`router/routetable/renewal.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/routetable/renewal.go)) periodically refreshes entries by calling `ExpireIfSame` with a configurable TTL (defaulting to 24 hours).

The `RenewSelf` method handles the actual renewal logic, ensuring that stale routes do not persist indefinitely in the distributed cache. This complements the `AppTunnelChangeTimeout` constant defined in [`router/constants.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/constants.go), which governs timeout behavior during route transitions.

## Implementation Details and Key Files

Understanding the connection management architecture requires familiarity with these critical source files:

| Path | Purpose |
|------|---------|
| [`router/conn/conn.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/conn/conn.go) | Creates the gRPC client, registers balancers, wires middleware, and defines the `Conn` wrapper struct. |
| [`router/balancer/balancer.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/balancer.go) | Implements the core weighted-round-robin balancer that respects route table mappings. |
| [`router/balancer/master.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/master.go) | Helper functions for registering master balancers with write access to route tables. |
| [`router/balancer/readonly.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/readonly.go) | Helper functions for registering read-only balancers. |
| [`router/routetable/routetable.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/routetable/routetable.go) | Defines `ReadOnlyRouteTable`, `MasterRouteTable` interfaces and key helpers. |
| [`router/routetable/redis/redis.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/routetable/redis/redis.go) | Redis-backed implementation of route table storage. |
| [`router/routetable/renewal.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/routetable/renewal.go) | TTL handling and self-renewal logic for route entries. |
| [`router/constants.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/constants.go) | Timeout constants including `AppTunnelChangeTimeout`. |

## Summary

The fabrica-kit router implements a sophisticated connection management system that extends standard gRPC with distributed routing capabilities:

- **Layered Architecture**: Connections progress through balancer registration, route-table wiring, gRPC dialing, node filtering, and middleware wrapping.
- **Dual Balancer Modes**: Master balancers claim routes via `SetNxOrGet` for single-writer semantics, while read-only balancers consume existing mappings with TTL-based renewal.
- **Kratos Integration**: The router leverages Kratos discovery schemes and middleware patterns while adding custom node filtering to enforce route-table decisions.
- **Fault Tolerance**: TTL expiration, route renewal mechanisms, and fallback logic in `weightBalancer.Pick` ensure resilience against node failures and network partitions.

## Frequently Asked Questions

### How does the fabrica-kit router ensure that multiple clients route to the same node for a given object ID?

The router implements **single-writer semantics** through the master balancer's `SetNxOrGet` mechanism. When a master balancer in [`router/balancer/balancer.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/balancer.go) selects a node via `weightBalancer.Pick`, it attempts to atomically claim the `(color, oid)` pair in the route table. If the claim succeeds, the client uses the selected address; if another client already owns the route, the balancer falls back to the stored address, ensuring all clients converge on the same node.

### What is the difference between master and read-only balancers in fabrica-kit?

**Master balancers** (registered via `balancer.RegisterMasterBalancer`) have write access to the route table and can claim ownership of routes using `SetNxOrGet`. They are used when clients need to establish single-writer relationships with specific service nodes. **Read-only balancers** (registered via `balancer.RegisterReadOnlyBalancer`) only consume existing route mappings without modifying the table, making them suitable for stateless read operations that don't require node affinity.

### How does fabrica-kit handle stale route entries when nodes fail or change addresses?

The router implements **TTL-based renewal** through the `renewalRouteTable` in [`router/routetable/renewal.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/routetable/renewal.go). Route entries expire automatically after a configurable TTL (defaulting to 24 hours) unless renewed by active clients. The `ExpireIfSame` and `RenewSelf` methods ensure that stale mappings from failed nodes are automatically purged, while the `AppTunnelChangeTimeout` constant in [`router/constants.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/constants.go) governs timeout behavior during route transitions.

### Where does the actual gRPC connection creation happen in the fabrica-kit router?

The gRPC connection is established in [`router/conn/conn.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/conn/conn.go) within the `NewConn` function (lines 46-53). This function uses the Kratos gRPC client with a `discovery:///serviceName` scheme to resolve endpoints via the provided `registry.Discovery` implementation. The connection is then wrapped with node filtering via `grpc.WithNodeFilter(balancer.NewFilter())` and middleware including recovery, tracing, metrics, and logging before being returned as a `Conn` struct implementing `grpc.ClientConnInterface`.