Dynamic Router Pattern
Overview
The Dynamic Router pattern is an advanced message routing capability in enterprise integration architectures that determines message destinations at runtime based on configurable rules, current system state, and dynamic conditions. Unlike static routing patterns that follow predetermined paths, the Dynamic Router acts as an intelligent traffic controller that analyzes messages, evaluates real-time conditions, and selects the most appropriate destination from a set of possible endpoints. This pattern enables sophisticated routing decisions that adapt to changing business requirements, system loads, and operational conditions without requiring system redeployment.
Theoretical Foundation
The Dynamic Router pattern is rooted in adaptive routing algorithms and runtime decision-making systems, specifically addressing the challenge of flexible message distribution in complex, evolving enterprise environments. The pattern incorporates principles from artificial intelligence, rule-based systems, and real-time analytics to enable intelligent routing decisions that consider multiple factors including message content, system state, business rules, and performance characteristics.
Core Principles
1. Runtime Route Determination
The Dynamic Router makes routing decisions at message processing time: - Context-sensitive routing - considering current system state, load conditions, and availability - Content-aware routing - analyzing message payload, headers, and metadata for routing decisions - Rule-based routing - applying configurable business rules and logic to determine destinations - Adaptive routing - learning from historical patterns and performance data to optimize decisions
2. Multi-Factor Decision Making
The pattern evaluates multiple criteria simultaneously: - Message characteristics - type, priority, size, content, and source system - System conditions - endpoint availability, load levels, performance metrics, and capacity - Business context - time of day, user preferences, geographic location, and organizational rules - Historical patterns - past routing success rates, performance trends, and failure patterns
3. Configurable Routing Logic
The Dynamic Router supports flexible routing approaches: - Rule-based configuration - externalized rules that can be updated without deployment - Algorithmic routing - using algorithms for load balancing, geographic optimization, or performance maximization - Machine learning integration - incorporating ML models for predictive routing decisions - Hybrid approaches - combining multiple routing strategies for optimal results
4. Real-Time Adaptation
The pattern enables continuous optimization: - Performance monitoring - tracking routing success rates and performance metrics - Automatic adjustment - adapting routing decisions based on real-time feedback - Failover handling - dynamically routing around failed or degraded endpoints - Learning capabilities - improving routing decisions over time through pattern analysis
Why Dynamic Routing is Essential in Integration Architecture
1. Operational Flexibility
In dynamic business environments, the Dynamic Router provides: - Business agility - adapting to changing business requirements without system changes - Operational efficiency - optimizing resource utilization through intelligent routing - Scalability support - distributing load across available resources based on capacity - Maintenance windows - routing around systems undergoing maintenance or updates
2. Performance Optimization
For achieving optimal system performance: - Load distribution - dynamically balancing load across multiple endpoints - Latency minimization - routing to geographically or network-optimal destinations - Resource optimization - considering endpoint capacity and current utilization - Throughput maximization - selecting routes that provide the best overall throughput
3. Resilience and Reliability
In mission-critical systems: - Automatic failover - routing around failed or degraded components - Circuit breaker integration - respecting circuit breaker states in routing decisions - Health-aware routing - considering endpoint health in routing calculations - Disaster recovery - automatically routing to backup systems during outages
4. Business Intelligence Integration
For data-driven decision making: - Analytics-driven routing - using business intelligence to inform routing decisions - Customer segmentation - routing based on customer characteristics and preferences - Geographic optimization - routing based on geographic proximity and regulations - Time-based routing - adapting routing behavior based on time patterns and schedules
Benefits in Integration Contexts
1. Enhanced Adaptability
- Configuration flexibility - changing routing behavior through configuration rather than code
- Business rule alignment - ensuring routing decisions reflect current business policies
- Environmental adaptation - automatically adjusting to different deployment environments
- Rapid response - quickly adapting to changing conditions and requirements
2. Improved Performance
- Optimal resource utilization - ensuring requests are routed to best-available resources
- Reduced latency - selecting fastest paths based on real-time performance data
- Increased throughput - optimizing routing for maximum system throughput
- Load optimization - preventing overload through intelligent load distribution
3. Operational Excellence
- Reduced maintenance - minimizing system downtime through automatic rerouting
- Simplified deployment - enabling blue-green deployments and canary releases
- Monitoring integration - incorporating system monitoring into routing decisions
- Automated operations - reducing manual intervention through intelligent automation
4. Business Value
- Cost optimization - routing to most cost-effective endpoints when appropriate
- Service quality - ensuring messages are routed to provide best user experience
- Compliance support - routing based on regulatory and compliance requirements
- Innovation enablement - allowing experimentation with new endpoints and services
Integration Architecture Applications
1. Microservices Architecture
Dynamic Routing in microservices provides: - Service discovery integration - dynamically routing to available service instances - Version management - routing to appropriate service versions based on client requirements - Canary deployments - gradually routing traffic to new service versions - A/B testing support - routing subsets of traffic to different service implementations
2. Cloud and Hybrid Deployments
In cloud environments: - Multi-cloud routing - distributing requests across multiple cloud providers - Cost optimization - routing to most cost-effective cloud resources - Geographic distribution - routing to nearest available cloud regions - Capacity scaling - routing based on auto-scaling and capacity availability
3. Enterprise Application Integration
For connecting enterprise systems: - System availability routing - routing around systems in maintenance or failure states - Data residency compliance - ensuring data is routed according to geographic regulations - Priority-based routing - routing high-priority messages to premium service endpoints - Business hours routing - adapting routing behavior based on business operational hours
4. API Management and Gateways
In API gateway implementations: - Backend versioning - routing to appropriate API backend versions - Rate limiting integration - routing around rate-limited endpoints - Authentication-based routing - routing based on user authentication and authorization - Tenant isolation - routing multi-tenant requests to appropriate isolated resources
Implementation Patterns
1. Rule-Based Dynamic Router
// Dynamic router using configurable business rules
@Component
public class RuleBasedDynamicRouter {
@Autowired
private RoutingRuleEngine ruleEngine;
@Autowired
private EndpointRegistry endpointRegistry;
@Autowired
private SystemMonitoringService monitoringService;
public RoutingDecision routeMessage(MessageContext messageContext) {
// Gather current context information
RoutingContext context = buildRoutingContext(messageContext);
// Apply routing rules in priority order
List<RoutingRule> applicableRules = ruleEngine.getApplicableRules(context);
for (RoutingRule rule : applicableRules) {
RoutingDecision decision = rule.evaluate(context);
if (decision.isDefinitive()) {
// Validate the selected endpoint
if (isEndpointAvailable(decision.getTargetEndpoint(), context)) {
return decision;
}
// If endpoint not available, continue to next rule
}
}
// Fallback to default routing
return createDefaultRoutingDecision(context);
}
private RoutingContext buildRoutingContext(MessageContext messageContext) {
return RoutingContext.builder()
.message(messageContext.getMessage())
.headers(messageContext.getHeaders())
.timestamp(Instant.now())
.systemLoad(monitoringService.getCurrentSystemLoad())
.availableEndpoints(endpointRegistry.getAvailableEndpoints())
.businessContext(extractBusinessContext(messageContext))
.build();
}
private boolean isEndpointAvailable(String endpoint, RoutingContext context) {
EndpointHealth health = monitoringService.getEndpointHealth(endpoint);
return health.isAvailable() &&
health.getResponseTime() < context.getMaxAcceptableLatency() &&
health.getCurrentLoad() < health.getMaxCapacity() * 0.8; // 80% threshold
}
}
@Service
public class RoutingRuleEngine {
private final List<RoutingRule> rules = new ArrayList<>();
@PostConstruct
public void initializeRules() {
rules.add(new PriorityBasedRoutingRule());
rules.add(new MessageTypeRoutingRule());
rules.add(new LoadBalancedRoutingRule());
rules.add(new GeographicRoutingRule());
rules.add(new TimeBasedRoutingRule());
rules.add(new DefaultRoutingRule());
// Sort by priority
rules.sort(Comparator.comparing(RoutingRule::getPriority));
}
public List<RoutingRule> getApplicableRules(RoutingContext context) {
return rules.stream()
.filter(rule -> rule.isApplicable(context))
.collect(Collectors.toList());
}
}
// Example routing rule implementation
public class PriorityBasedRoutingRule implements RoutingRule {
@Override
public boolean isApplicable(RoutingContext context) {
return context.getHeaders().containsKey("priority");
}
@Override
public RoutingDecision evaluate(RoutingContext context) {
String priority = (String) context.getHeaders().get("priority");
String targetEndpoint = switch (priority.toUpperCase()) {
case "HIGH" -> selectHighPriorityEndpoint(context);
case "MEDIUM" -> selectMediumPriorityEndpoint(context);
case "LOW" -> selectLowPriorityEndpoint(context);
default -> null;
};
if (targetEndpoint != null) {
return RoutingDecision.builder()
.targetEndpoint(targetEndpoint)
.isDefinitive(true)
.reason("Priority-based routing: " + priority)
.confidence(0.9)
.build();
}
return RoutingDecision.notDefinitive();
}
private String selectHighPriorityEndpoint(RoutingContext context) {
return context.getAvailableEndpoints().stream()
.filter(endpoint -> endpoint.getTier().equals("PREMIUM"))
.filter(endpoint -> endpoint.getCurrentLoad() < endpoint.getCapacity() * 0.5)
.findFirst()
.map(EndpointInfo::getId)
.orElse(null);
}
}
2. Performance-Aware Dynamic Router
// Dynamic router that considers performance characteristics
@Component
public class PerformanceAwareDynamicRouter {
@Autowired
private PerformanceMetricsService metricsService;
@Autowired
private RoutingAlgorithm routingAlgorithm;
public RoutingDecision routeBasedOnPerformance(MessageContext messageContext,
List<String> candidateEndpoints) {
Map<String, PerformanceMetrics> endpointMetrics = new HashMap<>();
// Gather performance data for candidate endpoints
for (String endpoint : candidateEndpoints) {
PerformanceMetrics metrics = metricsService.getRecentMetrics(endpoint);
if (metrics.isHealthy()) {
endpointMetrics.put(endpoint, metrics);
}
}
if (endpointMetrics.isEmpty()) {
return RoutingDecision.failure("No healthy endpoints available");
}
// Apply performance-based routing algorithm
String selectedEndpoint = routingAlgorithm.selectOptimalEndpoint(
endpointMetrics,
messageContext.getRequirements()
);
return RoutingDecision.builder()
.targetEndpoint(selectedEndpoint)
.isDefinitive(true)
.reason("Performance-optimized routing")
.metrics(endpointMetrics.get(selectedEndpoint))
.build();
}
}
@Service
public class WeightedPerformanceRoutingAlgorithm implements RoutingAlgorithm {
public String selectOptimalEndpoint(Map<String, PerformanceMetrics> endpointMetrics,
MessageRequirements requirements) {
Map<String, Double> endpointScores = new HashMap<>();
for (Map.Entry<String, PerformanceMetrics> entry : endpointMetrics.entrySet()) {
String endpoint = entry.getKey();
PerformanceMetrics metrics = entry.getValue();
double score = calculatePerformanceScore(metrics, requirements);
endpointScores.put(endpoint, score);
}
return endpointScores.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse(null);
}
private double calculatePerformanceScore(PerformanceMetrics metrics,
MessageRequirements requirements) {
double latencyScore = calculateLatencyScore(metrics.getAverageLatency(),
requirements.getMaxLatency());
double throughputScore = calculateThroughputScore(metrics.getThroughput(),
requirements.getMinThroughput());
double availabilityScore = metrics.getAvailability();
double loadScore = calculateLoadScore(metrics.getCurrentLoad(),
metrics.getMaxCapacity());
// Weighted scoring based on requirements
return (latencyScore * requirements.getLatencyWeight()) +
(throughputScore * requirements.getThroughputWeight()) +
(availabilityScore * requirements.getAvailabilityWeight()) +
(loadScore * requirements.getLoadWeight());
}
private double calculateLatencyScore(Duration averageLatency, Duration maxLatency) {
if (averageLatency.compareTo(maxLatency) >= 0) {
return 0.0;
}
double ratio = (double) averageLatency.toMillis() / maxLatency.toMillis();
return Math.max(0.0, 1.0 - ratio);
}
private double calculateLoadScore(int currentLoad, int maxCapacity) {
double loadRatio = (double) currentLoad / maxCapacity;
if (loadRatio >= 0.9) return 0.1; // Almost at capacity
if (loadRatio >= 0.7) return 0.5; // High load
if (loadRatio >= 0.5) return 0.8; // Medium load
return 1.0; // Low load
}
}
3. Machine Learning Enhanced Router
// Dynamic router enhanced with machine learning predictions
@Component
public class MLEnhancedDynamicRouter {
@Autowired
private RoutingMLService mlService;
@Autowired
private HistoricalDataService historicalDataService;
@Autowired
private FallbackRouter fallbackRouter;
public RoutingDecision routeWithMLPrediction(MessageContext messageContext) {
try {
// Prepare features for ML model
RoutingFeatures features = extractFeatures(messageContext);
// Get ML prediction
MLPrediction prediction = mlService.predictOptimalEndpoint(features);
// Validate prediction confidence
if (prediction.getConfidence() >= 0.8) {
String predictedEndpoint = prediction.getPredictedEndpoint();
// Verify endpoint availability
if (isEndpointSuitable(predictedEndpoint, messageContext)) {
// Record successful ML routing for model feedback
recordMLRoutingDecision(messageContext, prediction, true);
return RoutingDecision.builder()
.targetEndpoint(predictedEndpoint)
.isDefinitive(true)
.reason("ML-predicted optimal endpoint")
.confidence(prediction.getConfidence())
.mlPrediction(prediction)
.build();
}
}
// Fall back to traditional routing if ML prediction is uncertain or invalid
RoutingDecision fallbackDecision = fallbackRouter.route(messageContext);
// Record ML routing failure for model improvement
recordMLRoutingDecision(messageContext, prediction, false);
return fallbackDecision;
} catch (Exception e) {
log.warn("ML routing failed, falling back to traditional routing", e);
return fallbackRouter.route(messageContext);
}
}
private RoutingFeatures extractFeatures(MessageContext messageContext) {
Map<String, Object> message = messageContext.getMessage();
Map<String, Object> headers = messageContext.getHeaders();
return RoutingFeatures.builder()
.messageType((String) message.get("type"))
.messageSize(calculateMessageSize(message))
.priority((String) headers.get("priority"))
.source((String) headers.get("source"))
.timeOfDay(Instant.now().atZone(ZoneId.systemDefault()).getHour())
.dayOfWeek(Instant.now().atZone(ZoneId.systemDefault()).getDayOfWeek().getValue())
.recentSystemLoad(getRecentSystemLoad())
.historicalPatterns(getHistoricalPatterns(messageContext))
.build();
}
private void recordMLRoutingDecision(MessageContext context, MLPrediction prediction,
boolean successful) {
MLFeedback feedback = MLFeedback.builder()
.messageContext(context)
.prediction(prediction)
.successful(successful)
.timestamp(Instant.now())
.build();
mlService.recordFeedback(feedback);
}
}
@Service
public class RoutingMLService {
private final MLModel routingModel;
public MLPrediction predictOptimalEndpoint(RoutingFeatures features) {
// Convert features to model input format
double[] inputVector = featuresToVector(features);
// Get model prediction
ModelOutput output = routingModel.predict(inputVector);
// Convert model output to routing decision
return convertToRoutingPrediction(output, features);
}
public void recordFeedback(MLFeedback feedback) {
// Store feedback for model retraining
feedbackRepository.save(feedback);
// Trigger model retraining if enough new feedback accumulated
if (shouldRetrain()) {
scheduleModelRetraining();
}
}
@Scheduled(fixedRate = 86400000) // Daily
public void retrainModel() {
log.info("Starting ML model retraining");
List<MLFeedback> recentFeedback = feedbackRepository.findRecentFeedback();
if (recentFeedback.size() >= MIN_FEEDBACK_FOR_RETRAINING) {
try {
MLModel newModel = modelTrainer.trainModel(recentFeedback);
if (newModel.getAccuracy() > routingModel.getAccuracy()) {
this.routingModel = newModel;
log.info("Updated routing model with improved accuracy: {}",
newModel.getAccuracy());
}
} catch (Exception e) {
log.error("Model retraining failed", e);
}
}
}
}
4. Context-Aware Geographic Router
// Dynamic router that considers geographic and network topology factors
@Component
public class GeographicDynamicRouter {
@Autowired
private GeographicService geoService;
@Autowired
private NetworkTopologyService topologyService;
@Autowired
private DataResidencyService residencyService;
public RoutingDecision routeByGeography(MessageContext messageContext,
String clientLocation) {
// Check data residency requirements
DataResidencyRequirement residencyReq = residencyService
.getRequirements(messageContext.getMessage());
if (residencyReq != null) {
String requiredRegion = residencyReq.getRequiredRegion();
List<String> allowedRegions = residencyReq.getAllowedRegions();
// Filter endpoints by data residency compliance
List<EndpointInfo> compliantEndpoints = filterByDataResidency(
getAvailableEndpoints(),
requiredRegion,
allowedRegions
);
if (compliantEndpoints.isEmpty()) {
return RoutingDecision.failure("No endpoints comply with data residency requirements");
}
// Select optimal endpoint within compliance constraints
String selectedEndpoint = selectOptimalGeographicEndpoint(
compliantEndpoints,
clientLocation
);
return RoutingDecision.builder()
.targetEndpoint(selectedEndpoint)
.isDefinitive(true)
.reason("Geographic routing with data residency compliance")
.build();
}
// Standard geographic optimization without residency constraints
return routeByProximity(messageContext, clientLocation);
}
private RoutingDecision routeByProximity(MessageContext messageContext,
String clientLocation) {
List<EndpointInfo> availableEndpoints = getAvailableEndpoints();
Map<String, GeographicMetrics> geoMetrics = new HashMap<>();
for (EndpointInfo endpoint : availableEndpoints) {
GeographicMetrics metrics = calculateGeographicMetrics(
clientLocation,
endpoint.getLocation()
);
geoMetrics.put(endpoint.getId(), metrics);
}
// Select endpoint with best geographic score
String optimalEndpoint = geoMetrics.entrySet().stream()
.min(Map.Entry.comparingByValue(
Comparator.comparing(GeographicMetrics::getTotalScore)))
.map(Map.Entry::getKey)
.orElse(null);
if (optimalEndpoint != null) {
return RoutingDecision.builder()
.targetEndpoint(optimalEndpoint)
.isDefinitive(true)
.reason("Geographic proximity optimization")
.geographicMetrics(geoMetrics.get(optimalEndpoint))
.build();
}
return RoutingDecision.failure("No suitable geographic endpoint found");
}
private GeographicMetrics calculateGeographicMetrics(String clientLocation,
String endpointLocation) {
// Calculate physical distance
double distance = geoService.calculateDistance(clientLocation, endpointLocation);
// Calculate network distance (latency-based)
Duration networkLatency = topologyService.estimateLatency(clientLocation, endpointLocation);
// Consider time zone differences for business hours
int timezoneOffset = geoService.getTimezoneOffset(clientLocation, endpointLocation);
// Calculate total geographic score (lower is better)
double distanceScore = distance / 1000.0; // Normalize to thousands of km
double latencyScore = networkLatency.toMillis() / 100.0; // Normalize to hundreds of ms
double timezoneScore = Math.abs(timezoneOffset) / 12.0; // Normalize to half-day differences
double totalScore = distanceScore + (latencyScore * 2.0) + timezoneScore;
return GeographicMetrics.builder()
.physicalDistance(distance)
.networkLatency(networkLatency)
.timezoneOffset(timezoneOffset)
.distanceScore(distanceScore)
.latencyScore(latencyScore)
.timezoneScore(timezoneScore)
.totalScore(totalScore)
.build();
}
}
Apache Camel Implementation
1. Basic Dynamic Router Route
@Component
public class BasicDynamicRouterRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
from("direct:dynamicRouter")
.routeId("basic-dynamic-router")
.log("Starting dynamic routing for message: ${header.messageId}")
.dynamicRouter(method(DynamicRoutingService.class, "getNextEndpoint"))
.log("Dynamic routing completed for message: ${header.messageId}");
}
}
@Service
public class DynamicRoutingService {
@Autowired
private RoutingDecisionEngine decisionEngine;
public String getNextEndpoint(
@Header Map<String, Object> headers,
@Property Map<String, Object> properties,
@Body Object body
) {
// Check if routing is complete
if (properties.containsKey("routingComplete")) {
return null; // Stop routing
}
// Create message context
MessageContext context = MessageContext.builder()
.headers(headers)
.properties(properties)
.body(body)
.build();
// Get routing decision
RoutingDecision decision = decisionEngine.determineRoute(context);
if (decision.isValid()) {
// Mark routing as complete
properties.put("routingComplete", true);
log.info("Routing message to: {} (reason: {})",
decision.getTargetEndpoint(), decision.getReason());
return decision.getTargetEndpoint();
}
return null; // No route found
}
}
2. Content and Context-Aware Dynamic Router
@Component
public class ContentAwareDynamicRouterRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
from("direct:contentAwareDynamicRouter")
.routeId("content-aware-dynamic-router")
.log("Processing message for content-aware routing")
.process(exchange -> {
// Analyze message content and set routing context
Object body = exchange.getIn().getBody();
Map<String, Object> headers = exchange.getIn().getHeaders();
RoutingContext routingContext = analyzeMessageForRouting(body, headers);
exchange.setProperty("routingContext", routingContext);
})
.dynamicRouter().method(ContentAwareRoutingService.class, "determineRoute")
.log("Content-aware routing completed");
}
private RoutingContext analyzeMessageForRouting(Object body, Map<String, Object> headers) {
RoutingContextBuilder builder = RoutingContext.builder();
// Analyze content
if (body instanceof Map) {
Map<?, ?> messageData = (Map<?, ?>) body;
builder.messageType((String) messageData.get("type"))
.priority((String) messageData.get("priority"))
.size(calculateMessageSize(body));
// Extract business context
if (messageData.containsKey("customer")) {
Map<?, ?> customer = (Map<?, ?>) messageData.get("customer");
builder.customerTier((String) customer.get("tier"))
.customerRegion((String) customer.get("region"));
}
}
// Analyze headers
builder.source((String) headers.get("source"))
.timestamp(Instant.now())
.correlationId((String) headers.get("correlationId"));
return builder.build();
}
}
@Service
public class ContentAwareRoutingService {
@Autowired
private RoutingRuleEngine ruleEngine;
@Autowired
private SystemStateService systemState;
public String determineRoute(
@Property("routingContext") RoutingContext context,
@Property Map<String, Object> properties
) {
// Check if routing is complete
if (properties.containsKey("routingComplete")) {
return null;
}
// Add current system state to context
context = context.withSystemState(systemState.getCurrentState());
// Apply routing rules
RoutingDecision decision = ruleEngine.evaluateRouting(context);
if (decision.hasRoute()) {
properties.put("routingComplete", true);
properties.put("routingDecision", decision);
log.info("Content-aware routing decision: {} -> {} (confidence: {})",
context.getMessageType(), decision.getTargetEndpoint(),
decision.getConfidence());
return decision.getTargetEndpoint();
}
return null;
}
}
3. Load-Aware Dynamic Router
@Component
public class LoadAwareDynamicRouterRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
from("direct:loadAwareDynamicRouter")
.routeId("load-aware-dynamic-router")
.log("Starting load-aware dynamic routing")
.process(exchange -> {
// Gather current load information
Map<String, LoadMetrics> loadMetrics = gatherLoadMetrics();
exchange.setProperty("loadMetrics", loadMetrics);
// Set routing attempts counter
exchange.setProperty("routingAttempts", 0);
})
.dynamicRouter().method(LoadAwareRoutingService.class, "selectEndpoint")
.log("Load-aware routing completed");
}
private Map<String, LoadMetrics> gatherLoadMetrics() {
// Implementation to gather load metrics from all available endpoints
return Map.of(
"endpoint1", new LoadMetrics(0.3, 150, 500),
"endpoint2", new LoadMetrics(0.7, 350, 500),
"endpoint3", new LoadMetrics(0.1, 50, 500)
);
}
}
@Service
public class LoadAwareRoutingService {
private static final int MAX_ROUTING_ATTEMPTS = 3;
public String selectEndpoint(
@Property("loadMetrics") Map<String, LoadMetrics> loadMetrics,
@Property("routingAttempts") Integer attempts,
@Properties Map<String, Object> properties
) {
// Check termination conditions
if (attempts >= MAX_ROUTING_ATTEMPTS || properties.containsKey("routingComplete")) {
return null;
}
// Find endpoint with best load characteristics
String selectedEndpoint = loadMetrics.entrySet().stream()
.filter(entry -> entry.getValue().isAvailable())
.filter(entry -> entry.getValue().getLoadFactor() < 0.8) // Under 80% capacity
.min(Map.Entry.comparingByValue(
Comparator.comparing(LoadMetrics::getLoadFactor)))
.map(Map.Entry::getKey)
.orElse(null);
if (selectedEndpoint != null) {
properties.put("routingComplete", true);
properties.put("selectedEndpoint", selectedEndpoint);
LoadMetrics metrics = loadMetrics.get(selectedEndpoint);
log.info("Selected endpoint {} with load factor: {} (current/max: {}/{})",
selectedEndpoint, metrics.getLoadFactor(),
metrics.getCurrentLoad(), metrics.getMaxCapacity());
return selectedEndpoint;
}
// Increment attempts for next iteration
properties.put("routingAttempts", attempts + 1);
log.warn("No suitable endpoint found in attempt {}", attempts + 1);
return "direct:loadBalancingFailed";
}
}
4. Time-Based Dynamic Router
@Component
public class TimeBasedDynamicRouterRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
from("direct:timeBasedDynamicRouter")
.routeId("time-based-dynamic-router")
.log("Starting time-based dynamic routing")
.process(exchange -> {
// Add time-based context
Instant now = Instant.now();
ZonedDateTime localTime = now.atZone(ZoneId.systemDefault());
exchange.setProperty("currentTime", now);
exchange.setProperty("hourOfDay", localTime.getHour());
exchange.setProperty("dayOfWeek", localTime.getDayOfWeek());
exchange.setProperty("isBusinessHours", isBusinessHours(localTime));
})
.dynamicRouter().method(TimeBasedRoutingService.class, "routeByTime")
.log("Time-based routing completed");
}
private boolean isBusinessHours(ZonedDateTime dateTime) {
DayOfWeek dayOfWeek = dateTime.getDayOfWeek();
int hour = dateTime.getHour();
return dayOfWeek != DayOfWeek.SATURDAY &&
dayOfWeek != DayOfWeek.SUNDAY &&
hour >= 9 && hour < 17; // 9 AM to 5 PM
}
}
@Service
public class TimeBasedRoutingService {
@Value("${routing.business-hours.endpoints}")
private List<String> businessHoursEndpoints;
@Value("${routing.after-hours.endpoints}")
private List<String> afterHoursEndpoints;
@Value("${routing.weekend.endpoints}")
private List<String> weekendEndpoints;
public String routeByTime(
@Property("isBusinessHours") Boolean isBusinessHours,
@Property("dayOfWeek") DayOfWeek dayOfWeek,
@Property("hourOfDay") Integer hourOfDay,
@Properties Map<String, Object> properties,
@Body Object messageBody
) {
if (properties.containsKey("routingComplete")) {
return null;
}
List<String> candidateEndpoints;
String routingReason;
// Determine candidate endpoints based on time
if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY) {
candidateEndpoints = weekendEndpoints;
routingReason = "Weekend routing";
} else if (isBusinessHours) {
candidateEndpoints = businessHoursEndpoints;
routingReason = "Business hours routing";
} else {
candidateEndpoints = afterHoursEndpoints;
routingReason = "After-hours routing";
}
// Select first available endpoint from candidates
String selectedEndpoint = candidateEndpoints.stream()
.filter(this::isEndpointAvailable)
.findFirst()
.orElse(null);
if (selectedEndpoint != null) {
properties.put("routingComplete", true);
properties.put("routingReason", routingReason);
log.info("Time-based routing: {} -> {} ({})",
hourOfDay, selectedEndpoint, routingReason);
return selectedEndpoint;
}
// Fallback to any available endpoint
return selectFallbackEndpoint(candidateEndpoints);
}
private boolean isEndpointAvailable(String endpoint) {
// Implementation to check endpoint availability
return true; // Simplified for example
}
private String selectFallbackEndpoint(List<String> originalCandidates) {
// Try to find any available endpoint outside the original candidates
log.warn("Original time-based candidates unavailable, selecting fallback");
return "direct:fallbackEndpoint";
}
}
Best Practices
1. Router Design and Architecture
- Design routers to be stateless to enable horizontal scaling and resilience
- Implement clear separation between routing logic and business logic
- Use dependency injection for routing components to enable testing and flexibility
- Design for extensibility by using plugin architectures for routing algorithms
- Implement proper error handling and fallback routing mechanisms
2. Configuration and Rule Management
- Externalize routing rules and configurations to enable runtime updates
- Use version control for routing configurations to track changes and enable rollbacks
- Implement rule validation to prevent deployment of invalid routing configurations
- Provide configuration templates and documentation for different routing scenarios
- Use feature flags to enable gradual rollout of new routing rules
3. Performance and Scalability
- Cache routing decisions when appropriate to reduce computational overhead
- Use efficient data structures and algorithms for routing decision calculations
- Implement routing decision timeouts to prevent blocking on slow decision engines
- Monitor routing performance and optimize slow routing algorithms
- Consider using asynchronous routing decisions for high-throughput scenarios
4. Monitoring and Observability
- Implement comprehensive logging for routing decisions with correlation IDs
- Track routing success rates, latencies, and error patterns
- Monitor endpoint health and availability for routing decisions
- Implement alerting for routing failures and performance degradation
- Provide dashboards for routing metrics and system health visualization
5. Testing and Validation
- Create comprehensive test suites for routing logic including edge cases
- Implement A/B testing capabilities for evaluating new routing strategies
- Use chaos engineering to test routing resilience under failure conditions
- Create routing simulation tools for testing complex routing scenarios
- Implement canary testing for new routing algorithms and configurations
6. Security and Compliance
- Ensure routing decisions don't expose sensitive information in logs or metrics
- Implement proper access controls for routing configuration management
- Validate routing rules to prevent security bypasses or unauthorized access
- Audit routing decisions for compliance and security review purposes
- Use secure channels for routing configuration updates and management
The Dynamic Router pattern is fundamental for building adaptive, intelligent integration solutions that can respond to changing conditions, optimize performance, and maintain high availability in complex enterprise environments while supporting evolving business requirements.
← Back to All Patterns