# Connection Picker in fabrica-kit: Bridging Kratos and gRPC Load Balancing

> Discover the fabrica-kit connection picker, a gRPC balancer integrating Kratos selector. Learn how it directs RPC calls to sub-connections for efficient load balancing.

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

---

**The connection picker in fabrica-kit is a gRPC `balancer.Picker` implementation that integrates the Kratos selector load-balancing algorithm with gRPC's client-side balancer, determining which sub-connection handles each RPC call.**

The connection picker serves as the critical integration point in the fabrica-kit router package, enabling intelligent request routing for microservices. By bridging the Kratos selector framework with gRPC's native balancer interface, it allows applications to leverage sophisticated load-balancing strategies including weighted round-robin and route table-based master/reader separation.

## What Is the Connection Picker?

In `fabrica-kit`, the connection picker is implemented in [`router/balancer/picker.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/picker.go) as a concrete implementation of the gRPC `balancer.Picker` interface. Unlike standard gRPC pickers that rely solely on gRPC's built-in load balancing, this picker delegates selection logic to the **Kratos selector** — a pluggable load-balancing abstraction that supports multiple algorithms including weighted round-robin and consistent hashing.

The picker operates within the gRPC balancer lifecycle: when a gRPC client creates a connection via `conn.NewConn` (defined in [`router/conn/conn.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/conn/conn.go)), the appropriate balancer is registered through `balancer.RegisterMasterBalancer` or `RegisterReadOnlyBalancer`. The picker is then instantiated by the picker builder when sub-connections become ready.

## How the Connection Picker Works

The connection picker functionality spans three primary components that work together to route RPCs to appropriate service instances.

### Balancer Registration

The registration process begins in [`router/balancer/register.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/register.go). When `RegisterMasterBalancer` or `RegisterReadOnlyBalancer` is called, the system creates a Kratos `selector.Builder` and registers a gRPC balancer that uses a custom picker builder:

```go
// Conceptual flow based on register.go
balancer.RegisterMasterBalancer(serviceName, logger, routeTable, discovery)
// This internally creates a selector.Builder and registers the balancer
// with a picker builder that understands Kratos nodes.

```

The registration binds the service name to a specific balancer configuration, ensuring that when gRPC resolves the service address, it uses the fabrica-kit connection picker rather than the default round-robin implementation.

### Picker Builder Construction

The `newPickerBuilder` function in [`router/balancer/pickerbuilder.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/pickerbuilder.go) implements the gRPC `base.PickerBuilder` interface. Its `Build` method receives the ready sub-connections (`info.ReadySCs`) and constructs the picker:

```go
// From pickerbuilder.go - conceptual structure
func (pb *pickerBuilder) Build(info base.PickerBuildInfo) balancer.Picker {
    nodes := make([]selector.Node, 0, len(info.ReadySCs))
    for subConn, info := range info.ReadySCs {
        // Wrap each SubConn into a grpcNode that implements selector.Node
        node := &grpcNode{
            subConn: subConn,
            address: info.Address,
            // ... other metadata
        }
        nodes = append(nodes, node)
    }
    
    // Create the picker with the selector populated with nodes
    p := &picker{
        selector: pb.builder.Build(), // Kratos selector.Builder
    }
    p.selector.Apply(nodes) // Populate the selector with wrapped nodes
    return p
}

```

Each gRPC `SubConn` is wrapped in a `grpcNode` struct that implements the Kratos `selector.Node` interface, allowing the Kratos selector to treat gRPC connections as part of its load-balancing pool.

### The Pick Method

The core selection logic resides in [`router/balancer/picker.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/picker.go) within the `Pick` method. When gRPC needs to route an RPC, it calls this method:

```go
// From picker.go - simplified structure
func (p *picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
    // Extract node filters from context (e.g., from gRPC transport)
    filters := gtr.NodeFilters(info.Ctx)
    
    // Delegate to Kratos selector
    node, done, err := p.selector.Select(info.Ctx, selector.WithNodeFilter(filters...))
    if err != nil {
        return balancer.PickResult{}, err
    }
    
    // Cast to our wrapped node type
    grpcNode := node.(*grpcNode)
    
    // Construct the result with the chosen SubConn
    result := balancer.PickResult{
        SubConn: grpcNode.subConn,
        Done: func(doneInfo balancer.DoneInfo) {
            // Forward metrics to Kratos selector
            done(doneInfo.BytesSent, doneInfo.BytesReceived, doneInfo.Trailer, doneInfo.Err)
        },
    }
    return result, nil
}

```

The picker extracts any **node filters** attached to the RPC context, invokes the Kratos selector's `Select` method (which applies weighted round-robin or other algorithms), and returns the appropriate `SubConn`. After the RPC completes, the `Done` callback forwards telemetry data (bytes sent/received, trailers, errors) back to the Kratos selector for circuit-breaking and metrics collection.

## End-to-End Usage Example

To utilize the connection picker in a service client, you instantiate a connection through the fabrica-kit router package:

```go
package main

import (
    "context"
    "log"
    
    "github.com/go-pantheon/fabrica-kit/router/balancer"
    "github.com/go-pantheon/fabrica-kit/router/conn"
    "github.com/go-kratos/kratos/v2/log"
    "github.com/go-kratos/kratos/v2/registry"
)

func main() {
    // Initialize logger, route table, and service discovery
    var (
        logger     log.Logger
        routeTable routetable.ReadOnlyRouteTable
        discovery  registry.Discovery
    )
    
    // Create a connection using the master balancer (which includes the connection picker)
    c, err := conn.NewConn(
        "order-service",           // target service name
        balancer.TypeMaster,       // uses RegisterMasterBalancer
        logger,
        routeTable,
        discovery,
    )
    if err != nil {
        log.Fatal(err)
    }
    defer c.Close()
    
    // Create gRPC client
    client := pb.NewOrderServiceClient(c)
    
    // Execute RPC - the connection picker automatically selects the optimal sub-connection
    resp, err := client.CreateOrder(context.Background(), req)
    if err != nil {
        log.Fatal(err)
    }
}

```

In this flow, `conn.NewConn` triggers `balancer.RegisterMasterBalancer`, which registers the connection picker with gRPC. When `client.CreateOrder` is invoked, gRPC internally calls the picker's `Pick` method to determine which service instance handles the request.

## Summary

- The **connection picker** is a custom gRPC `balancer.Picker` implementation located in [`router/balancer/picker.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/picker.go) that integrates Kratos selector logic with gRPC load balancing.
- It is constructed by `newPickerBuilder` in [`router/balancer/pickerbuilder.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/pickerbuilder.go), which wraps gRPC `SubConn` instances into Kratos-compatible `grpcNode` objects.
- The `Pick` method delegates selection to the Kratos selector, supporting weighted round-robin and node filtering, then returns the chosen `SubConn` with a `Done` callback for metrics collection.
- Registration occurs through `balancer.RegisterMasterBalancer` or `RegisterReadOnlyBalancer` in [`router/balancer/register.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/register.go), enabling transparent integration when using `conn.NewConn`.

## Frequently Asked Questions

### How does the connection picker differ from a standard gRPC round-robin picker?

The connection picker in fabrica-kit differs from standard gRPC pickers by delegating selection logic to the **Kratos selector** framework. While gRPC's default round-robin picker maintains its own connection pool logic, the fabrica-kit picker wraps each gRPC `SubConn` in a `grpcNode` struct and uses Kratos's `selector.Select` method. This enables advanced features like weighted round-robin algorithms, node filtering based on context metadata, and integration with route tables for master/reader separation that standard gRPC pickers do not provide natively.

### What happens if the connection picker cannot find an available node?

If the Kratos selector cannot select a suitable node—whether due to circuit breaker states, health check failures, or empty node lists—the `Pick` method in [`router/balancer/picker.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/picker.go) returns the error directly from `p.selector.Select()`. This error propagates to the gRPC client, causing the RPC to fail immediately with the selector's error message. The picker does not implement retry logic at this level; retry policies should be configured at the gRPC client level or handled by the calling application.

### Can I use the connection picker with read-only replicas?

Yes, the connection picker supports read-only replicas through the `RegisterReadOnlyBalancer` function in [`router/balancer/register.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/register.go). When creating a connection via `conn.NewConn`, you specify `balancer.TypeReadOnly` instead of `TypeMaster`. This registers a picker configured to route requests to read replica nodes rather than master nodes. The picker builder creates nodes based on the route table's read replica entries, and the selector applies the same weighted round-robin logic to distribute read traffic across available replicas while respecting any node filters provided in the RPC context.

### How does the picker collect metrics after an RPC completes?

The picker collects metrics through the `Done` callback returned in the `balancer.PickResult`. When the RPC finishes, gRPC invokes this callback with `DoneInfo` containing bytes sent, bytes received, trailers, and any error. The picker's `Done` function (defined in [`router/balancer/picker.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/picker.go)) forwards these metrics to the Kratos selector's `done` callback, which was returned during the `Select` call. This allows the selector to update circuit breaker states, record latency metrics, and adjust node weights based on actual RPC performance data.