Contextual Orchestration: Designing Intent-Aware Service Architectures Beyond Meshes
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:
- Context injection at the edge: API gateway or ingress injects context into headers
- Policy evaluation: Intent router reads context and selects target service
- Mesh routing: Service mesh routes to the selected service using its normal mechanisms
- 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
Loading comments...