# How to Use Context Extensions for OID and Color Propagation in Microservices

> Leverage go-pantheon/fabrica-kit's context extensions to automatically propagate OID and color via gRPC/HTTP metadata for seamless microservice routing without boilerplate.

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

---

**Fabrica-Kit provides custom context extensions that automatically propagate object IDs (OID) and service colors through gRPC/HTTP metadata, enabling precise request routing across microservices without boilerplate code.**

The `go-pantheon/fabrica-kit` repository extends the standard Go `context.Context` with metadata keys for **object-level routing (OID)** and **service-color routing**. These extensions allow distributed systems to route requests to specific nodes that own particular objects or belong to specific deployment groups (blue/green/local).

## Understanding Context Metadata Keys

The routing metadata keys are defined in [`xcontext/context.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/xcontext/context.go). These constants identify the header names used for propagation:

- **`CtxOID`** maps to the header `x-md-global-oid` and carries the object ID for routing requests to the node that owns a specific object.
- **`CtxColor`** maps to the header `x-md-global-color` and identifies the logical service group (e.g., `local`, `blue`, `green`) for color-based routing.

According to the fabrica-kit source code in [`xcontext/context.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/xcontext/context.go) (lines 18-23), these keys are injected as gRPC/HTTP metadata and extracted at each hop in the microservice chain.

## Injecting OID and Color on the Client Side

When calling another service, inject routing metadata into the outgoing context using `AppendToClientContext`. This helper automatically encodes the values as string-based metadata headers.

```go
import (
    "context"
    "strconv"

    "github.com/go-pantheon/fabrica-kit/xcontext"
)

func withRouting(ctx context.Context, oid int64, color string) context.Context {
    return xcontext.AppendToClientContext(
        ctx,
        string(xcontext.CtxOID), strconv.FormatInt(oid, 10),
        string(xcontext.CtxColor), color,
    )
}

```

Pass the returned context to your Kratos or standard gRPC client. The OID is converted to a string because metadata values must be string-based, while the color remains a string identifier.

## Server-Side Context Propagation

### Extracting Headers with Dev Middleware

On the server side, the dev middleware in [`router/balancer/middleware/dev/middleware.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/middleware/dev/middleware.go) extracts incoming request headers and copies them into the server-side context using `AppendToServerContext`.

```go
func TransformContext(ctx context.Context) context.Context {
    if info, ok := transport.FromServerContext(ctx); ok {
        pairs := make([]string, 0, len(xcontext.Keys))
        for _, k := range xcontext.Keys {
            pairs = append(pairs, k, info.RequestHeader().Get(k))
        }
        ctx = xcontext.AppendToServerContext(ctx, pairs...)
    }
    return ctx
}

```

The middleware is automatically wired into the Kratos server stack via `dev.Server(logger)`. Once installed, all routing metadata becomes accessible through the server context without manual header parsing.

### Reading Values in Handlers

Inside service handlers, retrieve the routing information using type-safe helpers:

```go
func MyHandler(ctx context.Context, req *pb.MyRequest) (*pb.MyResponse, error) {
    // Returns int64 and error if missing/malformed
    oid, err := xcontext.OID(ctx)
    if err != nil {
        return nil, err
    }

    // Returns empty string if not present
    color := xcontext.Color(ctx)
    
    // Business logic using oid and color...
}

```

The `OID(ctx)` implementation parses the string metadata back to `int64`, while `Color(ctx)` returns the raw string value. Both functions are implemented in [`xcontext/context.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/xcontext/context.go) (lines 22-30 and 86-92).

## Downstream Propagation Between Services

When your service acts as a client to other downstream microservices, forward the routing context using the **outgoing** context helpers. These read directly from the gRPC outgoing metadata that will be sent downstream:

```go
oid, _ := xcontext.OIDFromOutgoingContext(ctx)
color := xcontext.ColorFromOutgoingContext(ctx)

nextCtx := xcontext.AppendToClientContext(
    context.Background(),
    string(xcontext.CtxOID), strconv.FormatInt(oid, 10),
    string(xcontext.CtxColor), color,
)

```

As implemented in [`xcontext/context.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/xcontext/context.go) (lines 24-31 for OID and lines 214-221 for Color), these functions extract values from the outbound metadata without requiring you to manually track the original request headers.

## Load Balancing and Node Filtering

### Route Table Lookup

The weighted balancer in [`router/balancer/balancer.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/balancer.go) uses OID and color to select the correct node from the route table. In the `Pick` method (lines 42-53), the balancer extracts routing metadata from the outgoing context:

```go
oid, _ := xcontext.OIDFromOutgoingContext(ctx)
color := xcontext.ColorFromOutgoingContext(ctx)
addr, err := p.routeTable.Get(ctx, color, oid)

```

If the route table contains a mapping for the OID within the specified color group, the request routes to that specific node. If no mapping exists, the balancer falls back to weighted round-robin across available nodes.

### Color-Based Node Filtering

Before the balancer selects a node, the filter in [`router/balancer/filter.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/filter.go) (lines 17-21) restricts the candidate pool to nodes matching the request's color:

```go
if n.Metadata()[profile.ColorKey] == xcontext.ColorFromOutgoingContext(ctx) {
    newNodes = append(newNodes, n)
}

```

This ensures that requests marked with `color: blue` only reach nodes registered in the blue group, enabling safe blue-green deployments and canary releases.

## Service Color Configuration

Each service defines its own color in the global profile at [`profile/color.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/profile/color.go). Set this value at startup (typically via environment variables) to determine which group the service belongs to when registering with the route table:

```go
const ColorLocal = "local"

func IsLocal() bool {
    return strings.ToLower(_color) == ColorLocal
}

```

The `ColorLocal` constant and `IsLocal()` helper allow services to identify their deployment context, which the balancer uses during node selection.

## Summary

- **Inject routing metadata** using `xcontext.AppendToClientContext` with `CtxOID` and `CtxColor` keys before making outbound calls.
- **Automatic extraction** occurs via the dev middleware in [`router/balancer/middleware/dev/middleware.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/middleware/dev/middleware.go), which populates the server context from incoming headers.
- **Read values** in handlers with `xcontext.OID(ctx)` for object IDs and `xcontext.Color(ctx)` for service colors.
- **Forward unchanged** to downstream services using `OIDFromOutgoingContext` and `ColorFromOutgoingContext` to maintain routing continuity.
- **Route precisely** using the built-in balancer and filter, which consult the route table and color metadata to select the correct node group.

## Frequently Asked Questions

### What is OID propagation in microservices?

OID propagation refers to passing an Object ID through the request chain to ensure all related operations for a specific entity (like a user or order) route to the same service instance. In fabrica-kit, the `x-md-global-oid` header carries this identifier through gRPC/HTTP metadata, allowing the balancer to look up the owning node in the route table.

### How does service color routing enable blue-green deployments?

Service color routing assigns logical labels (like `blue` or `green`) to deployment groups. The node filter in [`router/balancer/filter.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/filter.go) ensures requests tagged with a specific color only reach nodes with matching metadata. This allows traffic to shift between environments without changing request logic, supporting zero-downtime deployments and canary testing.

### Can I use context extensions without the dev middleware?

While possible, the dev middleware in [`router/balancer/middleware/dev/middleware.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/middleware/dev/middleware.go) handles the repetitive work of moving transport headers into the Go context. Without it, you would need to manually extract headers from `transport.FromServerContext(ctx)` and call `AppendToServerContext` in every entry point, duplicating the logic already provided by `TransformContext`.

### What happens if the OID is missing from the context?

If `xcontext.OID(ctx)` cannot find the OID key or fails to parse the value as an int64, it returns an error. The balancer handles missing OIDs gracefully by falling back to weighted round-robin selection across nodes matching the requested color, ensuring the request still reaches a valid service instance even without object-specific routing.