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

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. 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 (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.

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 extracts incoming request headers and copies them into the server-side context using AppendToServerContext.

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:

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 (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:

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 (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 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:

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 (lines 17-21) restricts the candidate pool to nodes matching the request's color:

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. Set this value at startup (typically via environment variables) to determine which group the service belongs to when registering with the route table:

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, 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 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →