By Appropri8 Team

Contextual Orchestration: Designing Intent-Aware Service Architectures Beyond Meshes

service-meshorchestrationopen-telemetrycontext-propagationintent-awaresemantic-routingenvoyistiolinkerdmicroservicesdistributed-systemsgonodejs

Contextual Orchestration Architecture

Your service mesh routes traffic. It balances load. It handles retries. But it doesn’t know why a request exists. It doesn’t know if the user is premium or free-tier. It doesn’t know if this is a cost-sensitive workload or a latency-critical one.

Service meshes solve the network layer. They don’t solve the business layer. They route based on IP addresses and ports. They don’t route based on intent.

Contextual orchestration changes that. It makes routing and coordination decisions based on business context. User type, workload priority, cost constraints, SLA requirements. These become first-class concerns in your routing logic.

This article explains how to build intent-aware service architectures. We’ll cover context propagation, intent routers, policy engines, and how this fits with existing service meshes.

Introduction: Why Service Meshes Aren’t Enough

Service meshes solved a real problem. Before them, you had to implement observability and control in every service. Retry logic, circuit breakers, load balancing. Each team reinvented the wheel.

Meshes moved this to the infrastructure layer. Istio, Linkerd, Consul. They handle network concerns. Traffic routing, mTLS, metrics collection. They’re good at what they do.

But they operate at the wrong abstraction level. They see packets and connections. They don’t see business context. A request from a premium user looks the same as a request from a free user. A cost-sensitive batch job looks the same as a real-time transaction.

This matters because not all requests are equal. Premium users should get faster responses. Critical workloads should get more resources. Cost-sensitive jobs should use cheaper infrastructure.

Your service mesh routes based on destination. It doesn’t route based on intent. That’s the gap contextual orchestration fills.

The Concept of Contextual Orchestration

Contextual orchestration means making routing and coordination decisions based on semantic metadata. User intent, business policies, SLA requirements. These become part of your routing logic.

Think about it this way. Traditional service meshes answer: “Where should this request go?” Contextual orchestration answers: “Where should this request go, given what we know about it?”

That knowledge comes from context. Context is metadata that travels with requests. It describes the request’s purpose, constraints, and requirements.

Semantic Metadata Propagation

Context travels through your system. It starts at the edge. A user makes a request. The API gateway adds context: user tier, request priority, expected SLA.

That context propagates downstream. Each service reads it, uses it for decisions, and passes it along. OpenTelemetry’s context propagation is built for this.

import (
    "context"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/propagation"
)

// Create context with business metadata
ctx := context.WithValue(ctx, "user.tier", "premium")
ctx = context.WithValue(ctx, "workload.priority", "high")
ctx = context.WithValue(ctx, "sla.target", "200ms")

// Propagate context through OpenTelemetry
propagator := otel.GetTextMapPropagator()

The context becomes part of your request’s identity. It’s not just “request to service A.” It’s “premium user request to service A with 200ms SLA.”

Network-Layer vs Business-Layer Orchestration

Service meshes handle network-layer concerns. They route based on:

  • Service names
  • Ports and protocols
  • Network policies
  • Load balancing algorithms

Contextual orchestration handles business-layer concerns. It routes based on:

  • User attributes (tier, region, permissions)
  • Workload characteristics (cost-sensitive, latency-critical)
  • Business policies (compliance, data residency)
  • SLA requirements (latency, availability)

These layers work together. The service mesh handles the mechanics. The contextual orchestrator handles the logic.

Real-World Analogies

OpenTelemetry already does something similar. It propagates tracing context. A trace ID travels with requests. Each service adds spans. You can reconstruct the full request path.

Contextual orchestration extends this pattern. Instead of just tracing context, you propagate business context. Instead of just adding spans, you add routing decisions.

Another analogy: Think of a restaurant. The service mesh is the kitchen layout. It determines how food moves between stations. The contextual orchestrator is the maître d’. They decide which chef handles which table based on VIP status, dietary restrictions, and timing.

Both are necessary. The kitchen layout enables efficiency. The maître d’ enables personalization.

Architectural Blueprint

A contextual orchestration system has three main components: context propagation, intent router, and policy engine. They work together to make business-aware routing decisions.

Context Propagation Layer

Context propagation ensures metadata travels with requests. It starts at the edge. API gateways, load balancers, or ingress controllers inject context.

Context can come from:

  • Request headers (user ID, tier, region)
  • Authentication tokens (claims, permissions)
  • Request metadata (path, method, query params)
  • External systems (user databases, policy stores)

The propagation mechanism uses OpenTelemetry’s context APIs. They’re designed for this. They handle serialization, deserialization, and cross-service boundaries.

package contextprop

import (
    "context"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/propagation"
)

type ContextKey string

const (
    UserTierKey    ContextKey = "user.tier"
    PriorityKey    ContextKey = "workload.priority"
    SLATargetKey   ContextKey = "sla.target"
    CostSensitiveKey ContextKey = "cost.sensitive"
)

// Inject context from HTTP headers
func InjectContext(ctx context.Context, headers map[string]string) context.Context {
    // Extract OpenTelemetry context
    propagator := otel.GetTextMapPropagator()
    ctx = propagator.Extract(ctx, propagation.HeaderCarrier(headers))
    
    // Extract business context
    if tier := headers["X-User-Tier"]; tier != "" {
        ctx = context.WithValue(ctx, UserTierKey, tier)
    }
    
    if priority := headers["X-Workload-Priority"]; priority != "" {
        ctx = context.WithValue(ctx, PriorityKey, priority)
    }
    
    if sla := headers["X-SLA-Target"]; sla != "" {
        ctx = context.WithValue(ctx, SLATargetKey, sla)
    }
    
    return ctx
}

// Extract context for propagation
func ExtractContext(ctx context.Context) map[string]string {
    headers := make(map[string]string)
    
    // Propagate OpenTelemetry context
    propagator := otel.GetTextMapPropagator()
    propagator.Inject(ctx, propagation.HeaderCarrier(headers))
    
    // Propagate business context
    if tier, ok := ctx.Value(UserTierKey).(string); ok {
        headers["X-User-Tier"] = tier
    }
    
    if priority, ok := ctx.Value(PriorityKey).(string); ok {
        headers["X-Workload-Priority"] = priority
    }
    
    if sla, ok := ctx.Value(SLATargetKey).(string); ok {
        headers["X-SLA-Target"] = sla
    }
    
    return headers
}

Context propagates through HTTP headers, gRPC metadata, or message queues. The format doesn’t matter. What matters is that downstream services can read it.

Intent Router and Orchestration Policy Engine

The intent router reads context and makes routing decisions. It evaluates policies. It selects target services. It applies routing rules.

Policies define routing logic. They’re declarative. You define what should happen, not how. The router interprets policies and applies them.

package router

import (
    "context"
    "fmt"
)

type Policy struct {
    Name        string
    Condition   Condition
    Action      Action
    Priority    int
}

type Condition struct {
    UserTier    *string
    Priority    *string
    CostSensitive *bool
    SLATarget   *int
}

type Action struct {
    Type        string
    Target      string
    Weight      int
}

type IntentRouter struct {
    policies []Policy
    services map[string]ServiceInfo
}

type ServiceInfo struct {
    Name        string
    Region      string
    Performance string
    Cost        string
}

func NewIntentRouter() *IntentRouter {
    return &IntentRouter{
        policies: []Policy{},
        services: make(map[string]ServiceInfo),
    }
}

func (ir *IntentRouter) AddPolicy(policy Policy) {
    ir.policies = append(ir.policies, policy)
    // Sort by priority
    for i := len(ir.policies) - 1; i > 0; i-- {
        if ir.policies[i].Priority > ir.policies[i-1].Priority {
            ir.policies[i], ir.policies[i-1] = ir.policies[i-1], ir.policies[i]
        }
    }
}

func (ir *IntentRouter) Route(ctx context.Context) (string, error) {
    // Extract context
    userTier := ctx.Value("user.tier")
    priority := ctx.Value("workload.priority")
    costSensitive := ctx.Value("cost.sensitive")
    
    // Evaluate policies in priority order
    for _, policy := range ir.policies {
        if ir.matchesCondition(policy.Condition, userTier, priority, costSensitive) {
            return ir.executeAction(policy.Action)
        }
    }
    
    // Default routing
    return "default-service", nil
}

func (ir *IntentRouter) matchesCondition(cond Condition, userTier, priority, costSensitive interface{}) bool {
    if cond.UserTier != nil {
        if tier, ok := userTier.(string); !ok || tier != *cond.UserTier {
            return false
        }
    }
    
    if cond.Priority != nil {
        if pri, ok := priority.(string); !ok || pri != *cond.Priority {
            return false
        }
    }
    
    if cond.CostSensitive != nil {
        if cost, ok := costSensitive.(bool); !ok || cost != *cond.CostSensitive {
            return false
        }
    }
    
    return true
}

func (ir *IntentRouter) executeAction(action Action) (string, error) {
    switch action.Type {
    case "route":
        return action.Target, nil
    case "weighted":
        // Implement weighted routing
        return action.Target, nil
    default:
        return "", fmt.Errorf("unknown action type: %s", action.Type)
    }
}

The router evaluates policies in priority order. First match wins. This lets you define general rules and specific overrides.

Integration with Existing Service Meshes

Contextual orchestration works with service meshes. They handle different concerns. The mesh handles network mechanics. The orchestrator handles business logic.

Here’s how they integrate:

  1. Context injection at the edge: API gateway or ingress injects context into headers
  2. Policy evaluation: Intent router reads context and selects target service
  3. Mesh routing: Service mesh routes to the selected service using its normal mechanisms
  4. Context propagation: Mesh passes context headers through to downstream services

The mesh doesn’t need to understand context. It just needs to pass headers. The orchestrator handles the logic.

# Istio VirtualService with context-aware routing
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: contextual-routing
spec:
  hosts:
  - api.example.com
  http:
  - match:
    - headers:
        x-user-tier:
          exact: premium
    route:
    - destination:
        host: premium-service
        subset: v1
      weight: 100
  - match:
    - headers:
        x-user-tier:
          exact: free
    route:
    - destination:
        host: standard-service
        subset: v1
      weight: 100
  - route:
    - destination:
        host: default-service
        subset: v1
      weight: 100

This works, but it’s static. The intent router makes it dynamic. It evaluates policies and updates routing rules based on current conditions.

Designing Context Handlers

Context handlers are services that read context and make decisions. They’re the bridge between business logic and routing logic.

Defining Routing Decisions by Business Context

Routing decisions should be based on business needs. Not technical metrics. Not infrastructure constraints. Business needs.

Start by identifying what matters:

  • User experience (premium users get better performance)
  • Cost optimization (batch jobs use cheaper resources)
  • Compliance (data stays in specific regions)
  • SLA requirements (latency targets, availability guarantees)

Then map those to routing decisions:

  • Premium users → high-performance services
  • Cost-sensitive workloads → cheaper regions or instances
  • Compliance requirements → specific regions or data centers
  • SLA targets → services that meet those targets

Example: Route Premium Users to High-Performance Services

Premium users pay more. They should get better performance. Route them to dedicated services with more resources.

package examples

import (
    "context"
    "net/http"
)

func PremiumUserHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
    // Extract user tier from context
    userTier := ctx.Value("user.tier")
    
    if tier, ok := userTier.(string); ok && tier == "premium" {
        // Route to high-performance service
        target := "https://premium-api.example.com"
        
        // Forward request with context
        forwardRequest(ctx, target, w, r)
        return
    }
    
    // Default routing for free users
    target := "https://standard-api.example.com"
    forwardRequest(ctx, target, w, r)
}

func forwardRequest(ctx context.Context, target string, w http.ResponseWriter, r *http.Request) {
    // Extract context headers
    headers := contextprop.ExtractContext(ctx)
    
    // Create new request with context
    req, err := http.NewRequestWithContext(ctx, r.Method, target, r.Body)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    // Copy headers including context
    for k, v := range r.Header {
        req.Header[k] = v
    }
    
    // Add context headers
    for k, v := range headers {
        req.Header.Set(k, v)
    }
    
    // Forward request
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer resp.Body.Close()
    
    // Copy response
    for k, v := range resp.Header {
        w.Header()[k] = v
    }
    w.WriteHeader(resp.StatusCode)
    // Copy body (simplified)
}

This is simple. But it works. Premium users get routed to premium services. Free users get routed to standard services.

Example: Apply Cost-Based Routing for Specific Workloads

Some workloads are cost-sensitive. Batch jobs, data processing, analytics. They don’t need low latency. They need low cost.

Route them to cheaper infrastructure. Spot instances, cheaper regions, reserved capacity.

func CostBasedRouter(ctx context.Context) (string, error) {
    costSensitive := ctx.Value("cost.sensitive")
    priority := ctx.Value("workload.priority")
    
    // Check if workload is cost-sensitive
    if cost, ok := costSensitive.(bool); ok && cost {
        // Check if it's not high priority
        if pri, ok := priority.(string); ok && pri != "high" {
            // Route to cost-optimized service
            return "cost-optimized-service", nil
        }
    }
    
    // Default routing
    return "standard-service", nil
}

The logic is straightforward. If it’s cost-sensitive and not high priority, use cheaper infrastructure. Otherwise, use standard infrastructure.

Implementation

Let’s build a complete contextual orchestration system. We’ll use OpenTelemetry for context propagation and build a simple router in Go.

Using OpenTelemetry Context APIs for Metadata

OpenTelemetry provides context propagation. It’s designed for distributed tracing. We’ll use it for business context too.

package main

import (
    "context"
    "fmt"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/propagation"
    "go.opentelemetry.io/otel/trace"
)

// Custom propagator for business context
type BusinessContextPropagator struct{}

func (b BusinessContextPropagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) {
    // Inject OpenTelemetry context
    otel.GetTextMapPropagator().Inject(ctx, carrier)
    
    // Inject business context
    if tier := ctx.Value("user.tier"); tier != nil {
        carrier.Set("X-User-Tier", fmt.Sprintf("%v", tier))
    }
    
    if priority := ctx.Value("workload.priority"); priority != nil {
        carrier.Set("X-Workload-Priority", fmt.Sprintf("%v", priority))
    }
}

func (b BusinessContextPropagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context {
    // Extract OpenTelemetry context
    ctx = otel.GetTextMapPropagator().Extract(ctx, carrier)
    
    // Extract business context
    if tier := carrier.Get("X-User-Tier"); tier != "" {
        ctx = context.WithValue(ctx, "user.tier", tier)
    }
    
    if priority := carrier.Get("X-Workload-Priority"); priority != "" {
        ctx = context.WithValue(ctx, "workload.priority", priority)
    }
    
    return ctx
}

func (b BusinessContextPropagator) Fields() []string {
    return []string{"X-User-Tier", "X-Workload-Priority"}
}

func main() {
    // Register custom propagator
    otel.SetTextMapPropagator(BusinessContextPropagator{})
    
    // Create context with business metadata
    ctx := context.Background()
    ctx = context.WithValue(ctx, "user.tier", "premium")
    ctx = context.WithValue(ctx, "workload.priority", "high")
    
    // Propagate context
    propagator := otel.GetTextMapPropagator()
    headers := make(map[string]string)
    propagator.Inject(ctx, propagation.HeaderCarrier(headers))
    
    fmt.Println("Headers:", headers)
    // Output: Headers: map[X-User-Tier:premium X-Workload-Priority:high]
}

OpenTelemetry handles serialization. It works across HTTP, gRPC, and message queues. It’s battle-tested.

Writing a Simple Context Router Microservice

Now let’s build a router that uses this context:

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/propagation"
)

type Router struct {
    policies []RoutingPolicy
}

type RoutingPolicy struct {
    Name        string                 `json:"name"`
    Condition   map[string]interface{} `json:"condition"`
    Target      string                 `json:"target"`
    Weight      int                    `json:"weight"`
    Priority    int                    `json:"priority"`
}

type RoutingDecision struct {
    Target      string `json:"target"`
    Reason      string `json:"reason"`
    Context     map[string]interface{} `json:"context"`
}

func NewRouter() *Router {
    return &Router{
        policies: []RoutingPolicy{
            {
                Name: "premium-users",
                Condition: map[string]interface{}{
                    "user.tier": "premium",
                },
                Target: "premium-service",
                Weight: 100,
                Priority: 100,
            },
            {
                Name: "cost-sensitive",
                Condition: map[string]interface{}{
                    "cost.sensitive": true,
                    "workload.priority": map[string]interface{}{
                        "$ne": "high",
                    },
                },
                Target: "cost-optimized-service",
                Weight: 100,
                Priority: 50,
            },
        },
    }
}

func (r *Router) Route(ctx context.Context) RoutingDecision {
    // Extract context
    contextMap := make(map[string]interface{})
    if tier := ctx.Value("user.tier"); tier != nil {
        contextMap["user.tier"] = tier
    }
    if priority := ctx.Value("workload.priority"); priority != nil {
        contextMap["workload.priority"] = priority
    }
    if costSensitive := ctx.Value("cost.sensitive"); costSensitive != nil {
        contextMap["cost.sensitive"] = costSensitive
    }
    
    // Evaluate policies in priority order
    for _, policy := range r.policies {
        if r.matchesCondition(policy.Condition, contextMap) {
            return RoutingDecision{
                Target:  policy.Target,
                Reason:  fmt.Sprintf("Matched policy: %s", policy.Name),
                Context: contextMap,
            }
        }
    }
    
    // Default routing
    return RoutingDecision{
        Target:  "default-service",
        Reason:  "No matching policy, using default",
        Context: contextMap,
    }
}

func (r *Router) matchesCondition(condition map[string]interface{}, context map[string]interface{}) bool {
    for key, expectedValue := range condition {
        actualValue, exists := context[key]
        
        if !exists {
            return false
        }
        
        // Handle $ne (not equal) operator
        if expectedMap, ok := expectedValue.(map[string]interface{}); ok {
            if neValue, hasNe := expectedMap["$ne"]; hasNe {
                if actualValue == neValue {
                    return false
                }
                continue
            }
        }
        
        if actualValue != expectedValue {
            return false
        }
    }
    
    return true
}

func (r *Router) handleRoute(w http.ResponseWriter, req *http.Request) {
    // Extract context from headers
    propagator := otel.GetTextMapPropagator()
    ctx := propagator.Extract(req.Context(), propagation.HeaderCarrier(req.Header))
    
    // Add business context from headers
    if tier := req.Header.Get("X-User-Tier"); tier != "" {
        ctx = context.WithValue(ctx, "user.tier", tier)
    }
    if priority := req.Header.Get("X-Workload-Priority"); priority != "" {
        ctx = context.WithValue(ctx, "workload.priority", priority)
    }
    if costSensitive := req.Header.Get("X-Cost-Sensitive"); costSensitive == "true" {
        ctx = context.WithValue(ctx, "cost.sensitive", true)
    }
    
    // Make routing decision
    decision := r.Route(ctx)
    
    // Return decision as JSON
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(decision)
}

func main() {
    router := NewRouter()
    
    http.HandleFunc("/route", router.handleRoute)
    
    log.Println("Context router listening on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

This router:

  • Extracts context from headers
  • Evaluates policies in priority order
  • Returns routing decisions
  • Works with OpenTelemetry propagation

Node.js Implementation

Here’s the same router in Node.js:

const http = require('http');
const { propagation, context } = require('@opentelemetry/api');

class ContextRouter {
    constructor() {
        this.policies = [
            {
                name: 'premium-users',
                condition: { 'user.tier': 'premium' },
                target: 'premium-service',
                weight: 100,
                priority: 100
            },
            {
                name: 'cost-sensitive',
                condition: { 
                    'cost.sensitive': true,
                    'workload.priority': { $ne: 'high' }
                },
                target: 'cost-optimized-service',
                weight: 100,
                priority: 50
            }
        ];
    }
    
    route(ctx) {
        // Extract context
        const contextMap = {};
        if (ctx['user.tier']) {
            contextMap['user.tier'] = ctx['user.tier'];
        }
        if (ctx['workload.priority']) {
            contextMap['workload.priority'] = ctx['workload.priority'];
        }
        if (ctx['cost.sensitive']) {
            contextMap['cost.sensitive'] = ctx['cost.sensitive'];
        }
        
        // Evaluate policies
        for (const policy of this.policies.sort((a, b) => b.priority - a.priority)) {
            if (this.matchesCondition(policy.condition, contextMap)) {
                return {
                    target: policy.target,
                    reason: `Matched policy: ${policy.name}`,
                    context: contextMap
                };
            }
        }
        
        // Default
        return {
            target: 'default-service',
            reason: 'No matching policy, using default',
            context: contextMap
        };
    }
    
    matchesCondition(condition, context) {
        for (const [key, expectedValue] of Object.entries(condition)) {
            const actualValue = context[key];
            
            if (actualValue === undefined) {
                return false;
            }
            
            // Handle $ne operator
            if (typeof expectedValue === 'object' && expectedValue.$ne !== undefined) {
                if (actualValue === expectedValue.$ne) {
                    return false;
                }
                continue;
            }
            
            if (actualValue !== expectedValue) {
                return false;
            }
        }
        
        return true;
    }
}

const router = new ContextRouter();

const server = http.createServer((req, res) => {
    if (req.url === '/route' && req.method === 'GET') {
        // Extract context from headers
        const ctx = {};
        if (req.headers['x-user-tier']) {
            ctx['user.tier'] = req.headers['x-user-tier'];
        }
        if (req.headers['x-workload-priority']) {
            ctx['workload.priority'] = req.headers['x-workload-priority'];
        }
        if (req.headers['x-cost-sensitive'] === 'true') {
            ctx['cost.sensitive'] = true;
        }
        
        // Make routing decision
        const decision = router.route(ctx);
        
        // Return JSON
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(decision));
    } else {
        res.writeHead(404);
        res.end();
    }
});

server.listen(8080, () => {
    console.log('Context router listening on :8080');
});

Both implementations work the same way. They extract context, evaluate policies, and return routing decisions.

Integrating with Envoy Filters for Contextual Policies

Envoy filters can read context and apply routing policies. This integrates contextual orchestration with service meshes.

apiVersion: networking.istio.io/v1beta1
kind: EnvoyFilter
metadata:
  name: contextual-routing
spec:
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        filterChain:
          filter:
            name: "envoy.filters.network.http_connection_manager"
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.filters.http.lua
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
          inline_code: |
            function envoy_on_request(request_handle)
              local user_tier = request_handle:headers():get("x-user-tier")
              
              if user_tier == "premium" then
                request_handle:headers():add("x-route-target", "premium-service")
              elseif user_tier == "free" then
                request_handle:headers():add("x-route-target", "standard-service")
              else
                request_handle:headers():add("x-route-target", "default-service")
              end
            end
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: contextual-routing
spec:
  hosts:
  - api.example.com
  http:
  - match:
    - headers:
        x-route-target:
          exact: premium-service
    route:
    - destination:
        host: premium-service
  - match:
    - headers:
        x-route-target:
          exact: standard-service
    route:
    - destination:
        host: standard-service
  - route:
    - destination:
        host: default-service

The Envoy filter reads context headers and sets routing targets. The VirtualService routes based on those targets. This gives you dynamic routing based on context.

Best Practices

Contextual orchestration is powerful. But it’s easy to misuse. Here’s how to do it right.

Avoiding Context Leakage and Propagation Bloat

Context should be minimal. Don’t propagate everything. Only what’s needed for routing decisions.

Too much context causes problems:

  • Headers get too large (HTTP header limits)
  • Performance degrades (more data to serialize)
  • Security risks (sensitive data in headers)

Keep context small:

  • Only include routing-relevant metadata
  • Don’t include full user objects
  • Don’t include sensitive data
  • Use references instead of full data
// Bad: Too much context
ctx = context.WithValue(ctx, "user", fullUserObject)  // 100KB object

// Good: Minimal context
ctx = context.WithValue(ctx, "user.tier", "premium")   // 7 bytes

Also, clean up context. Don’t let it accumulate. Remove values that are no longer needed.

Observability Implications

Contextual routing decisions should be observable. Log what decisions were made and why.

Add observability at key points:

  • Context injection (what context was added)
  • Policy evaluation (which policies matched)
  • Routing decisions (where requests were routed)
  • Outcome tracking (did the decision help?)
func (r *Router) Route(ctx context.Context) RoutingDecision {
    decision := r.doRoute(ctx)
    
    // Log decision for observability
    log.WithFields(log.Fields{
        "target": decision.Target,
        "reason": decision.Reason,
        "context": decision.Context,
    }).Info("Routing decision made")
    
    return decision
}

Use OpenTelemetry spans to track routing decisions. They show up in traces. You can see which policies matched and why.

Testing Contextual Routing Logic

Test routing logic thoroughly. It’s business-critical. Wrong routing decisions break user experience.

Test cases:

  • Premium users route to premium services
  • Cost-sensitive workloads route to cost-optimized services
  • Default routing works when no policies match
  • Policy priority works correctly
  • Context extraction works from headers
func TestPremiumUserRouting(t *testing.T) {
    router := NewRouter()
    
    ctx := context.WithValue(context.Background(), "user.tier", "premium")
    decision := router.Route(ctx)
    
    if decision.Target != "premium-service" {
        t.Errorf("Expected premium-service, got %s", decision.Target)
    }
}

func TestCostSensitiveRouting(t *testing.T) {
    router := NewRouter()
    
    ctx := context.WithValue(context.Background(), "cost.sensitive", true)
    ctx = context.WithValue(ctx, "workload.priority", "low")
    
    decision := router.Route(ctx)
    
    if decision.Target != "cost-optimized-service" {
        t.Errorf("Expected cost-optimized-service, got %s", decision.Target)
    }
}

Test in isolation. Mock context. Verify decisions. Then test integration with real services.

Code Samples

Here are complete, working examples you can use.

OpenTelemetry Context Propagation Example

package main

import (
    "context"
    "fmt"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/propagation"
    "go.opentelemetry.io/otel/trace"
)

func main() {
    // Create context with business metadata
    ctx := context.Background()
    ctx = context.WithValue(ctx, "user.tier", "premium")
    ctx = context.WithValue(ctx, "workload.priority", "high")
    ctx = context.WithValue(ctx, "sla.target", "200ms")
    
    // Propagate context
    propagator := otel.GetTextMapPropagator()
    headers := make(map[string]string)
    
    // Inject OpenTelemetry context
    propagator.Inject(ctx, propagation.HeaderCarrier(headers))
    
    // Inject business context manually
    if tier := ctx.Value("user.tier"); tier != nil {
        headers["X-User-Tier"] = fmt.Sprintf("%v", tier)
    }
    if priority := ctx.Value("workload.priority"); priority != nil {
        headers["X-Workload-Priority"] = fmt.Sprintf("%v", priority)
    }
    if sla := ctx.Value("sla.target"); sla != nil {
        headers["X-SLA-Target"] = fmt.Sprintf("%v", sla)
    }
    
    fmt.Println("Propagated headers:")
    for k, v := range headers {
        fmt.Printf("  %s: %s\n", k, v)
    }
    
    // Extract context on receiving side
    extractedCtx := propagator.Extract(ctx, propagation.HeaderCarrier(headers))
    
    // Extract business context
    if tier := headers["X-User-Tier"]; tier != "" {
        extractedCtx = context.WithValue(extractedCtx, "user.tier", tier)
    }
    if priority := headers["X-Workload-Priority"]; priority != "" {
        extractedCtx = context.WithValue(extractedCtx, "workload.priority", priority)
    }
    if sla := headers["X-SLA-Target"]; sla != "" {
        extractedCtx = context.WithValue(extractedCtx, "sla.target", sla)
    }
    
    fmt.Println("\nExtracted context:")
    fmt.Printf("  user.tier: %v\n", extractedCtx.Value("user.tier"))
    fmt.Printf("  workload.priority: %v\n", extractedCtx.Value("workload.priority"))
    fmt.Printf("  sla.target: %v\n", extractedCtx.Value("sla.target"))
}

This shows how to propagate context through OpenTelemetry. It works across HTTP, gRPC, and message queues.

Go Policy Router Example

package main

import (
    "context"
    "encoding/json"
    "log"
    "net/http"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/propagation"
)

type Router struct {
    policies []Policy
}

type Policy struct {
    Name      string
    Condition map[string]interface{}
    Target    string
    Priority  int
}

type Decision struct {
    Target  string
    Reason  string
    Context map[string]interface{}
}

func NewRouter() *Router {
    return &Router{
        policies: []Policy{
            {
                Name: "premium-users",
                Condition: map[string]interface{}{
                    "user.tier": "premium",
                },
                Target:   "premium-service",
                Priority: 100,
            },
            {
                Name: "cost-sensitive",
                Condition: map[string]interface{}{
                    "cost.sensitive": true,
                },
                Target:   "cost-optimized-service",
                Priority: 50,
            },
        },
    }
}

func (r *Router) Route(ctx context.Context) Decision {
    contextMap := make(map[string]interface{})
    
    // Extract context
    if tier := ctx.Value("user.tier"); tier != nil {
        contextMap["user.tier"] = tier
    }
    if costSensitive := ctx.Value("cost.sensitive"); costSensitive != nil {
        contextMap["cost.sensitive"] = costSensitive
    }
    
    // Evaluate policies
    for _, policy := range r.policies {
        if r.matches(policy.Condition, contextMap) {
            return Decision{
                Target:  policy.Target,
                Reason:  "Matched: " + policy.Name,
                Context: contextMap,
            }
        }
    }
    
    return Decision{
        Target:  "default-service",
        Reason:  "No match",
        Context: contextMap,
    }
}

func (r *Router) matches(condition, context map[string]interface{}) bool {
    for k, v := range condition {
        if context[k] != v {
            return false
        }
    }
    return true
}

func (r *Router) handleRoute(w http.ResponseWriter, req *http.Request) {
    // Extract context from headers
    propagator := otel.GetTextMapPropagator()
    ctx := propagator.Extract(req.Context(), propagation.HeaderCarrier(req.Header))
    
    // Add business context
    if tier := req.Header.Get("X-User-Tier"); tier != "" {
        ctx = context.WithValue(ctx, "user.tier", tier)
    }
    if costSensitive := req.Header.Get("X-Cost-Sensitive"); costSensitive == "true" {
        ctx = context.WithValue(ctx, "cost.sensitive", true)
    }
    
    // Route
    decision := r.Route(ctx)
    
    // Return JSON
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(decision)
}

func main() {
    router := NewRouter()
    http.HandleFunc("/route", router.handleRoute)
    log.Println("Router listening on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

This is a complete router. It extracts context, evaluates policies, and returns decisions.

Node.js Context Router Example

const http = require('http');
const { propagation, context: otelContext } = require('@opentelemetry/api');

class ContextRouter {
    constructor() {
        this.policies = [
            {
                name: 'premium-users',
                condition: { 'user.tier': 'premium' },
                target: 'premium-service',
                priority: 100
            },
            {
                name: 'cost-sensitive',
                condition: { 'cost.sensitive': true },
                target: 'cost-optimized-service',
                priority: 50
            }
        ].sort((a, b) => b.priority - a.priority);
    }
    
    route(ctx) {
        const contextMap = {};
        
        // Extract context
        if (ctx['user.tier']) {
            contextMap['user.tier'] = ctx['user.tier'];
        }
        if (ctx['cost.sensitive']) {
            contextMap['cost.sensitive'] = ctx['cost.sensitive'];
        }
        
        // Evaluate policies
        for (const policy of this.policies) {
            if (this.matches(policy.condition, contextMap)) {
                return {
                    target: policy.target,
                    reason: `Matched: ${policy.name}`,
                    context: contextMap
                };
            }
        }
        
        return {
            target: 'default-service',
            reason: 'No match',
            context: contextMap
        };
    }
    
    matches(condition, context) {
        for (const [key, value] of Object.entries(condition)) {
            if (context[key] !== value) {
                return false;
            }
        }
        return true;
    }
}

const router = new ContextRouter();

const server = http.createServer((req, res) => {
    if (req.url === '/route' && req.method === 'GET') {
        // Extract context
        const ctx = {};
        if (req.headers['x-user-tier']) {
            ctx['user.tier'] = req.headers['x-user-tier'];
        }
        if (req.headers['x-cost-sensitive'] === 'true') {
            ctx['cost.sensitive'] = true;
        }
        
        // Route
        const decision = router.route(ctx);
        
        // Return JSON
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(decision));
    } else {
        res.writeHead(404);
        res.end();
    }
});

server.listen(8080, () => {
    console.log('Context router listening on :8080');
});

Same functionality, different language. Both work.

Conclusion

Service meshes solved network-level orchestration. They handle routing, load balancing, and observability at the infrastructure layer. That’s good. But it’s not enough.

Contextual orchestration adds business awareness. It routes based on user intent, workload characteristics, and business policies. It makes infrastructure decisions based on business needs.

The key principles:

Propagate context, not just data. Context describes intent. It travels with requests. It informs routing decisions.

Separate concerns. Service meshes handle network mechanics. Contextual orchestrators handle business logic. They work together.

Make policies declarative. Define what should happen, not how. Let the router interpret policies.

Keep context minimal. Only propagate what’s needed. Don’t bloat headers. Don’t leak sensitive data.

Observe decisions. Log what decisions were made and why. Use traces to understand routing behavior.

Test thoroughly. Routing logic is critical. Test all policy combinations. Verify context extraction.

The transition from infrastructure-driven to intent-driven orchestration is happening. Service meshes were step one. Contextual orchestration is step two.

AI-augmented control planes will come next. They’ll learn from patterns. They’ll optimize routing automatically. They’ll adapt to changing conditions.

But the foundation is context. Without it, AI can’t make good decisions. Start building context-aware systems now. The patterns are proven. The tools exist. The question is whether you’ll add context from the start, or retrofit it later.

Earlier is easier.

Discussion

Join the conversation and share your thoughts

Discussion

0 / 5000