Appendix H: Feature Flags
Flagd + OpenFeature with Camel routes — gradual rollouts, A/B testing, and runtime route control.
15 minutesChapter 26appendices
The Detour pattern (Chapter 18) used configuration properties to toggle processing steps. Feature flags extend this to dynamic, fine-grained control: roll out a new enrichment step to 10% of traffic, enable hazmat routing only for specific customer tiers, or A/B test two content-based routing strategies.
OpenFeature + flagd
OpenFeature is a vendor-neutral API for feature flag evaluation. flagd is a lightweight flag evaluation daemon that reads flag definitions from a file or ConfigMap.
Architecture
Flag definitions
{
"flags": {
"enrichment-enabled": {
"state": "ENABLED",
"variants": {
"on": true,
"off": false
},
"defaultVariant": "on",
"targeting": {}
},
"new-routing-algorithm": {
"state": "ENABLED",
"variants": {
"legacy": "content-based-router",
"new": "dynamic-router"
},
"defaultVariant": "legacy",
"targeting": {
"fractional": [
["legacy", 90],
["new", 10]
]
}
},
"hazmat-compliance-v2": {
"state": "ENABLED",
"variants": {
"v1": false,
"v2": true
},
"defaultVariant": "v1",
"targeting": {
"if": [
{ "in": ["$customer_tier", ["ENTERPRISE", "VIP"]] },
"v2",
"v1"
]
}
}
}
}
Integration with Camel
@ApplicationScoped
@Named("featureFlags")
public class FeatureFlagEvaluator {
@Inject
Client openFeatureClient;
public boolean isEnabled(String flagKey) {
return openFeatureClient.getBooleanValue(flagKey, false);
}
public String getVariant(String flagKey, Map<String, Object> context) {
MutableContext evalContext = new MutableContext();
context.forEach((k, v) -> evalContext.add(k, String.valueOf(v)));
return openFeatureClient.getStringValue(flagKey, "default", evalContext);
}
}
// Detour controlled by feature flag
from("kafka:eip.orders.placed?brokers=localhost:9092&groupId=flag-router")
.routeId("feature-flag-detour")
.unmarshal().json(Map.class)
.process(exchange -> {
boolean enrichmentEnabled = exchange.getContext()
.getRegistry().lookupByNameAndType("featureFlags", FeatureFlagEvaluator.class)
.isEnabled("enrichment-enabled");
exchange.getIn().setHeader("enrichmentEnabled", enrichmentEnabled);
})
.choice()
.when(header("enrichmentEnabled").isEqualTo(true))
.to("direct:enrich-order")
.otherwise()
.log("Enrichment disabled by feature flag")
.end()
.to("direct:process-order");
// A/B test routing algorithm
from("kafka:eip.orders.placed?brokers=localhost:9092&groupId=ab-test")
.routeId("feature-flag-ab-test")
.unmarshal().json(Map.class)
.process(exchange -> {
Map<String, Object> order = exchange.getIn().getBody(Map.class);
FeatureFlagEvaluator flags = exchange.getContext()
.getRegistry().lookupByNameAndType("featureFlags", FeatureFlagEvaluator.class);
String algorithm = flags.getVariant("new-routing-algorithm",
Map.of("customer_tier", String.valueOf(order.get("customer_tier"))));
exchange.getIn().setHeader("routingAlgorithm", algorithm);
})
.toD("direct:${header.routingAlgorithm}");
Running flagd in the Podman stack
Add flagd to compose.yaml:
flagd:
image: ghcr.io/open-feature/flagd:latest
ports:
- "8013:8013"
volumes:
- ./flags.json:/etc/flagd/flags.json
command: start --uri file:/etc/flagd/flags.json
networks:
- eip-net
Dependencies
<dependency>
<groupId>dev.openfeature</groupId>
<artifactId>sdk</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>dev.openfeature.contrib.providers</groupId>
<artifactId>flagd</artifactId>
<version>0.7.0</version>
</dependency>
Verification status: verified — conceptual reference chapter, no runnable example.