Skip to main content

oxirs_gql/federation/
enhanced_manager.rs

1//! Enhanced Federation Manager with Dynamic Service Discovery
2//!
3//! This module provides an advanced federation manager that combines service discovery,
4//! intelligent query routing, load balancing, and fault tolerance.
5
6use anyhow::{anyhow, Context, Result};
7use async_trait::async_trait;
8use serde::{Deserialize, Serialize};
9use std::collections::hash_map::DefaultHasher;
10use std::collections::{BTreeMap, HashMap};
11use std::hash::{Hash, Hasher};
12use std::sync::Arc;
13use std::time::{Duration, Instant};
14use tokio::sync::{Mutex, RwLock};
15use tracing::{debug, info, warn};
16
17use super::config::{FederationConfig, RemoteEndpoint, RetryStrategy};
18use super::query_planner::{QueryPlan, QueryPlanner, QueryStep};
19use super::real_time_sync::{RealTimeSchemaSynchronizer, SyncConfig};
20use super::schema_stitcher::SchemaStitcher;
21use super::service_discovery::{
22    DiscoveryEvent, HealthStatus, ServiceDiscovery, ServiceDiscoveryConfig,
23    ServiceDiscoveryEventHandler, ServiceInfo,
24};
25use crate::ast::{Document, OperationType, Value};
26use crate::types::Schema;
27
28/// Enhanced federation manager configuration
29#[derive(Debug, Clone, Default)]
30pub struct EnhancedFederationConfig {
31    pub service_discovery: ServiceDiscoveryConfig,
32    pub load_balancing: LoadBalancingConfig,
33    pub circuit_breaker: CircuitBreakerConfig,
34    pub retry_policy: RetryPolicyConfig,
35    pub query_routing: QueryRoutingConfig,
36    pub caching: FederationCacheConfig,
37    pub real_time_sync: SyncConfig,
38}
39
40/// Load balancing configuration
41#[derive(Debug, Clone)]
42pub struct LoadBalancingConfig {
43    pub algorithm: LoadBalancingAlgorithm,
44    pub health_check_weight: f64,
45    pub response_time_weight: f64,
46    pub load_weight: f64,
47    pub max_requests_per_service: usize,
48}
49
50impl Default for LoadBalancingConfig {
51    fn default() -> Self {
52        Self {
53            algorithm: LoadBalancingAlgorithm::WeightedRoundRobin,
54            health_check_weight: 0.4,
55            response_time_weight: 0.3,
56            load_weight: 0.3,
57            max_requests_per_service: 1000,
58        }
59    }
60}
61
62/// Load balancing algorithms
63#[derive(Debug, Clone)]
64pub enum LoadBalancingAlgorithm {
65    RoundRobin,
66    WeightedRoundRobin,
67    LeastConnections,
68    LeastResponseTime,
69    Adaptive,
70    ConsistentHashing,
71}
72
73/// Circuit breaker configuration
74#[derive(Debug, Clone)]
75pub struct CircuitBreakerConfig {
76    pub failure_threshold: usize,
77    pub success_threshold: usize,
78    pub timeout: Duration,
79    pub retry_after: Duration,
80}
81
82impl Default for CircuitBreakerConfig {
83    fn default() -> Self {
84        Self {
85            failure_threshold: 5,
86            success_threshold: 3,
87            timeout: Duration::from_secs(30),
88            retry_after: Duration::from_secs(60),
89        }
90    }
91}
92
93/// Retry policy configuration
94#[derive(Debug, Clone)]
95pub struct RetryPolicyConfig {
96    pub max_retries: usize,
97    pub base_delay: Duration,
98    pub max_delay: Duration,
99    pub backoff_multiplier: f64,
100    pub jitter: bool,
101}
102
103impl Default for RetryPolicyConfig {
104    fn default() -> Self {
105        Self {
106            max_retries: 3,
107            base_delay: Duration::from_millis(100),
108            max_delay: Duration::from_secs(5),
109            backoff_multiplier: 2.0,
110            jitter: true,
111        }
112    }
113}
114
115/// Query routing configuration
116#[derive(Debug, Clone)]
117pub struct QueryRoutingConfig {
118    pub enable_query_analysis: bool,
119    pub enable_field_based_routing: bool,
120    pub enable_cost_based_routing: bool,
121    pub parallel_execution: bool,
122    pub max_parallel_requests: usize,
123}
124
125impl Default for QueryRoutingConfig {
126    fn default() -> Self {
127        Self {
128            enable_query_analysis: true,
129            enable_field_based_routing: true,
130            enable_cost_based_routing: true,
131            parallel_execution: true,
132            max_parallel_requests: 10,
133        }
134    }
135}
136
137/// Federation cache configuration
138#[derive(Debug, Clone)]
139pub struct FederationCacheConfig {
140    pub enable_schema_caching: bool,
141    pub enable_query_caching: bool,
142    pub schema_cache_ttl: Duration,
143    pub query_cache_ttl: Duration,
144    pub max_cache_size: usize,
145}
146
147impl Default for FederationCacheConfig {
148    fn default() -> Self {
149        Self {
150            enable_schema_caching: true,
151            enable_query_caching: true,
152            schema_cache_ttl: Duration::from_secs(3600),
153            query_cache_ttl: Duration::from_secs(300),
154            max_cache_size: 1000,
155        }
156    }
157}
158
159/// Circuit breaker state
160#[derive(Debug, Clone, PartialEq)]
161pub enum CircuitBreakerState {
162    Closed,
163    Open,
164    HalfOpen,
165}
166
167/// Service circuit breaker
168#[derive(Debug)]
169pub struct ServiceCircuitBreaker {
170    state: CircuitBreakerState,
171    failure_count: usize,
172    success_count: usize,
173    last_failure_time: Option<Instant>,
174    config: CircuitBreakerConfig,
175}
176
177impl ServiceCircuitBreaker {
178    pub fn new(config: CircuitBreakerConfig) -> Self {
179        Self {
180            state: CircuitBreakerState::Closed,
181            failure_count: 0,
182            success_count: 0,
183            last_failure_time: None,
184            config,
185        }
186    }
187
188    pub fn can_execute(&mut self) -> bool {
189        match self.state {
190            CircuitBreakerState::Closed => true,
191            CircuitBreakerState::Open => {
192                if let Some(last_failure) = self.last_failure_time {
193                    if last_failure.elapsed() >= self.config.retry_after {
194                        self.state = CircuitBreakerState::HalfOpen;
195                        self.success_count = 0;
196                        true
197                    } else {
198                        false
199                    }
200                } else {
201                    false
202                }
203            }
204            CircuitBreakerState::HalfOpen => true,
205        }
206    }
207
208    pub fn record_success(&mut self) {
209        match self.state {
210            CircuitBreakerState::Closed => {
211                self.failure_count = 0;
212            }
213            CircuitBreakerState::HalfOpen => {
214                self.success_count += 1;
215                if self.success_count >= self.config.success_threshold {
216                    self.state = CircuitBreakerState::Closed;
217                    self.failure_count = 0;
218                }
219            }
220            CircuitBreakerState::Open => {}
221        }
222    }
223
224    pub fn record_failure(&mut self) {
225        self.failure_count += 1;
226        self.last_failure_time = Some(Instant::now());
227
228        match self.state {
229            CircuitBreakerState::Closed => {
230                if self.failure_count >= self.config.failure_threshold {
231                    self.state = CircuitBreakerState::Open;
232                }
233            }
234            CircuitBreakerState::HalfOpen => {
235                self.state = CircuitBreakerState::Open;
236            }
237            CircuitBreakerState::Open => {}
238        }
239    }
240
241    pub fn state(&self) -> CircuitBreakerState {
242        self.state.clone()
243    }
244}
245
246/// Federation execution context
247#[derive(Debug, Clone)]
248pub struct FederationContext {
249    pub request_id: String,
250    pub user_id: Option<String>,
251    pub headers: HashMap<String, String>,
252    pub variables: HashMap<String, Value>,
253    pub operation_name: Option<String>,
254}
255
256/// Query performance analytics tracker
257#[derive(Debug, Clone)]
258pub struct QueryAnalytics {
259    pub query_patterns: HashMap<String, QueryPatternStats>,
260    pub service_performance: HashMap<String, ServicePerformanceStats>,
261    pub recent_queries: Vec<QueryExecutionStats>,
262    pub max_history: usize,
263}
264
265#[derive(Debug, Clone)]
266pub struct QueryPatternStats {
267    pub pattern_hash: String,
268    pub execution_count: u64,
269    pub average_duration: Duration,
270    pub success_rate: f64,
271    pub preferred_services: Vec<String>,
272    pub complexity_score: f64,
273}
274
275#[derive(Debug, Clone)]
276pub struct ServicePerformanceStats {
277    pub service_id: String,
278    pub total_requests: u64,
279    pub successful_requests: u64,
280    pub average_response_time: Duration,
281    pub error_rate: f64,
282    pub query_type_performance: HashMap<String, Duration>,
283    pub last_updated: Instant,
284}
285
286#[derive(Debug, Clone)]
287pub struct QueryExecutionStats {
288    pub query_hash: String,
289    pub service_id: String,
290    pub duration: Duration,
291    pub success: bool,
292    pub timestamp: Instant,
293    pub complexity: f64,
294    pub cache_hit: bool,
295}
296
297impl Default for QueryAnalytics {
298    fn default() -> Self {
299        Self::new()
300    }
301}
302
303impl QueryAnalytics {
304    pub fn new() -> Self {
305        Self {
306            query_patterns: HashMap::new(),
307            service_performance: HashMap::new(),
308            recent_queries: Vec::new(),
309            max_history: 10000,
310        }
311    }
312
313    /// Record a query execution
314    pub fn record_execution(&mut self, stats: QueryExecutionStats) {
315        // Update query pattern statistics
316        let pattern_stats = self
317            .query_patterns
318            .entry(stats.query_hash.clone())
319            .or_insert_with(|| QueryPatternStats {
320                pattern_hash: stats.query_hash.clone(),
321                execution_count: 0,
322                average_duration: Duration::from_millis(0),
323                success_rate: 1.0,
324                preferred_services: Vec::new(),
325                complexity_score: stats.complexity,
326            });
327
328        pattern_stats.execution_count += 1;
329        pattern_stats.average_duration = Duration::from_nanos(
330            ((pattern_stats.average_duration.as_nanos() as u64
331                * (pattern_stats.execution_count - 1))
332                + stats.duration.as_nanos() as u64)
333                / pattern_stats.execution_count,
334        );
335
336        if stats.success {
337            pattern_stats.success_rate =
338                (pattern_stats.success_rate * (pattern_stats.execution_count - 1) as f64 + 1.0)
339                    / pattern_stats.execution_count as f64;
340        } else {
341            pattern_stats.success_rate = (pattern_stats.success_rate
342                * (pattern_stats.execution_count - 1) as f64)
343                / pattern_stats.execution_count as f64;
344        }
345
346        // Update service performance statistics
347        let service_stats = self
348            .service_performance
349            .entry(stats.service_id.clone())
350            .or_insert_with(|| ServicePerformanceStats {
351                service_id: stats.service_id.clone(),
352                total_requests: 0,
353                successful_requests: 0,
354                average_response_time: Duration::from_millis(0),
355                error_rate: 0.0,
356                query_type_performance: HashMap::new(),
357                last_updated: Instant::now(),
358            });
359
360        service_stats.total_requests += 1;
361        if stats.success {
362            service_stats.successful_requests += 1;
363        }
364
365        service_stats.average_response_time = Duration::from_nanos(
366            ((service_stats.average_response_time.as_nanos() as u64
367                * (service_stats.total_requests - 1))
368                + stats.duration.as_nanos() as u64)
369                / service_stats.total_requests,
370        );
371
372        service_stats.error_rate =
373            1.0 - (service_stats.successful_requests as f64 / service_stats.total_requests as f64);
374        service_stats.last_updated = Instant::now();
375
376        // Add to recent queries history
377        self.recent_queries.push(stats);
378        if self.recent_queries.len() > self.max_history {
379            self.recent_queries
380                .drain(0..self.recent_queries.len() - self.max_history);
381        }
382    }
383
384    /// Get recommended service for a query pattern
385    pub fn get_recommended_service(&self, query_hash: &str) -> Option<String> {
386        if let Some(_pattern_stats) = self.query_patterns.get(query_hash) {
387            // Find the service with best performance for this pattern
388            let mut best_service = None;
389            let mut best_score = f64::NEG_INFINITY;
390
391            for (service_id, service_stats) in &self.service_performance {
392                // Calculate a composite score based on success rate, response time, and error rate
393                let success_weight = 0.4;
394                let response_time_weight = 0.4;
395                let error_rate_weight = 0.2;
396
397                let success_score = service_stats.successful_requests as f64
398                    / service_stats.total_requests.max(1) as f64;
399                let response_time_score =
400                    1.0 / (service_stats.average_response_time.as_millis().max(1) as f64);
401                let error_score = 1.0 - service_stats.error_rate;
402
403                let composite_score = (success_score * success_weight)
404                    + (response_time_score * response_time_weight)
405                    + (error_score * error_rate_weight);
406
407                if composite_score > best_score {
408                    best_score = composite_score;
409                    best_service = Some(service_id.clone());
410                }
411            }
412
413            best_service
414        } else {
415            None
416        }
417    }
418
419    /// Get performance insights
420    pub fn get_performance_insights(&self) -> HashMap<String, serde_json::Value> {
421        let mut insights = HashMap::new();
422
423        insights.insert(
424            "total_query_patterns".to_string(),
425            serde_json::Value::Number(self.query_patterns.len().into()),
426        );
427
428        insights.insert(
429            "total_services".to_string(),
430            serde_json::Value::Number(self.service_performance.len().into()),
431        );
432
433        insights.insert(
434            "recent_queries_count".to_string(),
435            serde_json::Value::Number(self.recent_queries.len().into()),
436        );
437
438        // Calculate overall system performance
439        let total_requests: u64 = self
440            .service_performance
441            .values()
442            .map(|s| s.total_requests)
443            .sum();
444
445        let total_successful: u64 = self
446            .service_performance
447            .values()
448            .map(|s| s.successful_requests)
449            .sum();
450
451        if total_requests > 0 {
452            insights.insert(
453                "overall_success_rate".to_string(),
454                serde_json::Value::Number(
455                    serde_json::Number::from_f64(total_successful as f64 / total_requests as f64)
456                        .unwrap_or_else(|| serde_json::Number::from(0)),
457                ),
458            );
459        }
460
461        insights
462    }
463}
464
465/// Query execution result
466#[derive(Debug, Clone, Serialize, Deserialize)]
467pub struct FederationResult {
468    pub data: Option<serde_json::Value>,
469    pub errors: Vec<FederationError>,
470    pub extensions: HashMap<String, serde_json::Value>,
471}
472
473/// Federation error
474#[derive(Debug, Clone, Serialize, Deserialize)]
475pub struct FederationError {
476    pub message: String,
477    pub path: Option<Vec<String>>,
478    pub locations: Option<Vec<ErrorLocation>>,
479    pub extensions: HashMap<String, serde_json::Value>,
480}
481
482#[derive(Debug, Clone, Serialize, Deserialize)]
483pub struct ErrorLocation {
484    pub line: usize,
485    pub column: usize,
486}
487
488/// Enhanced federation manager
489pub struct EnhancedFederationManager {
490    config: EnhancedFederationConfig,
491    service_discovery: Arc<ServiceDiscovery>,
492    schema_stitcher: Arc<SchemaStitcher>,
493    query_planner: Arc<QueryPlanner>,
494    circuit_breakers: Arc<RwLock<HashMap<String, ServiceCircuitBreaker>>>,
495    load_balancer: Arc<Mutex<LoadBalancer>>,
496    merged_schema: Arc<RwLock<Option<Schema>>>,
497    schema_synchronizer: Arc<RealTimeSchemaSynchronizer>,
498    query_analytics: Arc<Mutex<QueryAnalytics>>,
499    http_client: reqwest::Client,
500}
501
502/// Load balancer implementation
503#[derive(Debug)]
504pub struct LoadBalancer {
505    algorithm: LoadBalancingAlgorithm,
506    round_robin_counter: usize,
507    request_counts: HashMap<String, usize>,
508    config: LoadBalancingConfig,
509    hash_ring: BTreeMap<u64, String>,
510    virtual_nodes_per_service: usize,
511}
512
513impl LoadBalancer {
514    pub fn new(config: LoadBalancingConfig) -> Self {
515        Self {
516            algorithm: config.algorithm.clone(),
517            round_robin_counter: 0,
518            request_counts: HashMap::new(),
519            config,
520            hash_ring: BTreeMap::new(),
521            virtual_nodes_per_service: 150, // Standard for consistent hashing
522        }
523    }
524
525    /// Update the hash ring with available services
526    pub fn update_hash_ring(&mut self, services: &[ServiceInfo]) {
527        self.hash_ring.clear();
528
529        for service in services {
530            // Create virtual nodes for better distribution
531            for i in 0..self.virtual_nodes_per_service {
532                let virtual_key = format!("{}:{}", service.id, i);
533                let hash = self.hash_string(&virtual_key);
534                self.hash_ring.insert(hash, service.id.clone());
535            }
536        }
537    }
538
539    /// Hash a string to u64
540    fn hash_string(&self, s: &str) -> u64 {
541        let mut hasher = DefaultHasher::new();
542        s.hash(&mut hasher);
543        hasher.finish()
544    }
545
546    /// Select the best service for a request
547    pub fn select_service(
548        &mut self,
549        services: &[ServiceInfo],
550        _query_hash: Option<u64>,
551    ) -> Option<String> {
552        if services.is_empty() {
553            return None;
554        }
555
556        let healthy_services: Vec<_> = services
557            .iter()
558            .filter(|s| s.health_status == HealthStatus::Healthy)
559            .collect();
560
561        if healthy_services.is_empty() {
562            return None;
563        }
564
565        match self.algorithm {
566            LoadBalancingAlgorithm::RoundRobin => {
567                let service = &healthy_services[self.round_robin_counter % healthy_services.len()];
568                self.round_robin_counter += 1;
569                Some(service.id.clone())
570            }
571            LoadBalancingAlgorithm::WeightedRoundRobin => {
572                self.select_weighted_round_robin(&healthy_services)
573            }
574            LoadBalancingAlgorithm::LeastConnections => {
575                self.select_least_connections(&healthy_services)
576            }
577            LoadBalancingAlgorithm::LeastResponseTime => {
578                self.select_least_response_time(&healthy_services)
579            }
580            LoadBalancingAlgorithm::Adaptive => self.select_adaptive(&healthy_services),
581            LoadBalancingAlgorithm::ConsistentHashing => {
582                // Update hash ring with current healthy services
583                let healthy_service_infos: Vec<_> =
584                    healthy_services.iter().map(|&s| s.clone()).collect();
585                self.update_hash_ring(&healthy_service_infos);
586                // Use the query_hash for consistent routing
587                self.select_consistent_hash(&healthy_services, _query_hash.unwrap_or(0))
588            }
589        }
590    }
591
592    fn select_weighted_round_robin(&mut self, services: &[&ServiceInfo]) -> Option<String> {
593        // Calculate weights based on health, response time, and load
594        let mut best_service = None;
595        let mut best_score = f64::INFINITY;
596
597        for service in services {
598            let health_score = match service.health_status {
599                HealthStatus::Healthy => 1.0,
600                HealthStatus::Degraded => 0.5,
601                _ => 0.0,
602            };
603
604            let response_time_score = service
605                .response_time
606                .map(|rt| rt.as_millis() as f64)
607                .unwrap_or(1000.0);
608
609            let load_score = service.load_factor;
610            let request_count = self.request_counts.get(&service.id).copied().unwrap_or(0) as f64;
611
612            let total_score = (health_score * self.config.health_check_weight)
613                + (response_time_score * self.config.response_time_weight)
614                + (load_score * self.config.load_weight)
615                + (request_count * 0.1);
616
617            if total_score < best_score {
618                best_score = total_score;
619                best_service = Some(service.id.clone());
620            }
621        }
622
623        if let Some(ref service_id) = best_service {
624            *self.request_counts.entry(service_id.clone()).or_insert(0) += 1;
625        }
626
627        best_service
628    }
629
630    fn select_least_connections(&self, services: &[&ServiceInfo]) -> Option<String> {
631        services
632            .iter()
633            .min_by_key(|service| self.request_counts.get(&service.id).copied().unwrap_or(0))
634            .map(|service| service.id.clone())
635    }
636
637    fn select_least_response_time(&self, services: &[&ServiceInfo]) -> Option<String> {
638        services
639            .iter()
640            .min_by_key(|service| service.response_time.unwrap_or(Duration::from_secs(10)))
641            .map(|service| service.id.clone())
642    }
643
644    fn select_adaptive(&mut self, services: &[&ServiceInfo]) -> Option<String> {
645        // Adaptive algorithm combines multiple factors
646        self.select_weighted_round_robin(services)
647    }
648
649    fn select_consistent_hash(&self, _services: &[&ServiceInfo], hash: u64) -> Option<String> {
650        if self.hash_ring.is_empty() {
651            return None;
652        }
653
654        // Find the first node in the ring that is >= hash
655        if let Some((_, service_id)) = self.hash_ring.range(hash..).next() {
656            Some(service_id.clone())
657        } else {
658            // Wrap around to the first node in the ring
659            self.hash_ring
660                .first_key_value()
661                .map(|(_, service_id)| service_id.clone())
662        }
663    }
664
665    pub fn record_completion(&mut self, service_id: &str) {
666        if let Some(count) = self.request_counts.get_mut(service_id) {
667            if *count > 0 {
668                *count -= 1;
669            }
670        }
671    }
672}
673
674impl EnhancedFederationManager {
675    /// Create a new enhanced federation manager
676    pub async fn new(config: EnhancedFederationConfig, local_schema: Arc<Schema>) -> Result<Self> {
677        let service_discovery = Arc::new(ServiceDiscovery::new(config.service_discovery.clone()));
678        let schema_stitcher = Arc::new(SchemaStitcher::new(local_schema));
679        let federation_config = FederationConfig::default();
680        let query_planner = Arc::new(QueryPlanner::new(
681            schema_stitcher.clone(),
682            federation_config,
683        ));
684        let load_balancer = Arc::new(Mutex::new(LoadBalancer::new(config.load_balancing.clone())));
685
686        let http_client = reqwest::Client::builder()
687            .timeout(Duration::from_secs(30))
688            .build()
689            .context("Failed to create HTTP client")?;
690
691        let schema_synchronizer = Arc::new(RealTimeSchemaSynchronizer::new(
692            config.real_time_sync.clone(),
693            service_discovery.clone(),
694            schema_stitcher.clone(),
695        ));
696
697        let manager = Self {
698            config,
699            service_discovery,
700            schema_stitcher,
701            query_planner,
702            circuit_breakers: Arc::new(RwLock::new(HashMap::new())),
703            load_balancer,
704            merged_schema: Arc::new(RwLock::new(None)),
705            schema_synchronizer,
706            query_analytics: Arc::new(Mutex::new(QueryAnalytics::new())),
707            http_client,
708        };
709
710        // Set up event handling
711        manager.setup_event_handling().await?;
712
713        Ok(manager)
714    }
715
716    /// Start the federation manager
717    pub async fn start(&self) -> Result<()> {
718        info!("Starting enhanced federation manager");
719
720        // Start service discovery
721        self.service_discovery.start().await?;
722
723        // Start real-time schema synchronization
724        self.schema_synchronizer.start().await?;
725
726        // Build initial merged schema
727        self.rebuild_schema().await?;
728
729        Ok(())
730    }
731
732    /// Execute a federated GraphQL query
733    pub async fn execute_query(
734        &self,
735        document: &Document,
736        variables: HashMap<String, Value>,
737        context: FederationContext,
738    ) -> Result<FederationResult> {
739        let start_time = Instant::now();
740
741        debug!("Executing federated query: {}", context.request_id);
742
743        // Generate query hash for analytics
744        let query_hash = self.generate_query_hash(document, &variables);
745
746        // Check analytics for recommended service
747        let recommended_service = {
748            let analytics = self.query_analytics.lock().await;
749            analytics.get_recommended_service(&query_hash)
750        };
751
752        // Get current schema
753        let schema = {
754            let schema_guard = self.merged_schema.read().await;
755            schema_guard
756                .clone()
757                .ok_or_else(|| anyhow!("No schema available"))?
758        };
759
760        // Plan the query
761        let query_plan = self
762            .query_planner
763            .plan_query(document, &schema)
764            .await
765            .context("Failed to plan federated query")?;
766
767        // Execute the plan with analytics-aware service selection
768        let result = self
769            .execute_query_plan_with_analytics(
770                &query_plan,
771                &context,
772                &query_hash,
773                recommended_service,
774            )
775            .await;
776
777        let execution_time = start_time.elapsed();
778        let success = result.is_ok();
779
780        // Record execution statistics
781        if let Ok(ref _plan_result) = result {
782            // Record analytics for each service used
783            for step in &query_plan.steps {
784                let service_id = &step.endpoint_id;
785                let stats = QueryExecutionStats {
786                    query_hash: query_hash.clone(),
787                    service_id: service_id.clone(),
788                    duration: execution_time,
789                    success,
790                    timestamp: start_time,
791                    complexity: self.calculate_query_complexity(document),
792                    cache_hit: false, // This would be determined by cache layer
793                };
794
795                let mut analytics = self.query_analytics.lock().await;
796                analytics.record_execution(stats);
797            }
798        }
799
800        debug!(
801            "Query executed in {:?}: {} (success: {})",
802            execution_time, context.request_id, success
803        );
804
805        result
806    }
807
808    /// Generate a hash for the query for analytics tracking
809    fn generate_query_hash(
810        &self,
811        document: &Document,
812        variables: &HashMap<String, Value>,
813    ) -> String {
814        let mut hasher = DefaultHasher::new();
815
816        // Create a simplified representation of the query for hashing
817        let query_str = format!("{document:?}");
818        query_str.hash(&mut hasher);
819
820        // Include variables in the hash if they significantly affect query characteristics
821        let variables_str = format!("{variables:?}");
822        variables_str.hash(&mut hasher);
823
824        format!("{:x}", hasher.finish())
825    }
826
827    /// Calculate query complexity score
828    fn calculate_query_complexity(&self, document: &Document) -> f64 {
829        let mut complexity = 0.0;
830
831        // Basic complexity calculation based on operation type and depth
832        for definition in &document.definitions {
833            match definition {
834                crate::ast::Definition::Operation(op) => {
835                    complexity += match op.operation_type {
836                        OperationType::Query => 1.0,
837                        OperationType::Mutation => 2.0,
838                        OperationType::Subscription => 1.5,
839                    };
840
841                    // Add complexity based on selection depth
842                    complexity += self.calculate_selection_complexity(&op.selection_set, 0) as f64;
843                }
844                crate::ast::Definition::Fragment(_) => {
845                    complexity += 0.5; // Fragments add some complexity
846                }
847                crate::ast::Definition::Schema(_) => {
848                    complexity += 0.1; // Schema definitions add minimal complexity
849                }
850                crate::ast::Definition::Type(_) => {
851                    complexity += 0.1; // Type definitions add minimal complexity
852                }
853                crate::ast::Definition::Directive(_) => {
854                    complexity += 0.1; // Directive definitions add minimal complexity
855                }
856                crate::ast::Definition::SchemaExtension(_) => {
857                    complexity += 0.1; // Schema extensions add minimal complexity
858                }
859                crate::ast::Definition::TypeExtension(_) => {
860                    complexity += 0.1; // Type extensions add minimal complexity
861                }
862            }
863        }
864
865        complexity
866    }
867
868    /// Calculate complexity of a selection set recursively
869    #[allow(clippy::only_used_in_recursion)]
870    fn calculate_selection_complexity(
871        &self,
872        selection_set: &crate::ast::SelectionSet,
873        depth: usize,
874    ) -> usize {
875        let mut complexity = depth * 2; // Depth multiplier
876
877        for selection in &selection_set.selections {
878            match selection {
879                crate::ast::Selection::Field(field) => {
880                    complexity += 1;
881                    if let Some(ref selection_set) = field.selection_set {
882                        complexity += self.calculate_selection_complexity(selection_set, depth + 1);
883                    }
884                    // Add complexity for arguments
885                    complexity += field.arguments.len();
886                }
887                crate::ast::Selection::InlineFragment(fragment) => {
888                    complexity +=
889                        self.calculate_selection_complexity(&fragment.selection_set, depth + 1);
890                }
891                crate::ast::Selection::FragmentSpread(_) => {
892                    complexity += 2; // Fragment spreads add complexity
893                }
894            }
895        }
896
897        complexity
898    }
899
900    /// Execute query plan with analytics-aware service selection
901    async fn execute_query_plan_with_analytics(
902        &self,
903        query_plan: &QueryPlan,
904        context: &FederationContext,
905        _query_hash: &str,
906        recommended_service: Option<String>,
907    ) -> Result<FederationResult> {
908        // If we have a recommended service from analytics, try to use it
909        if let Some(service_id) = recommended_service {
910            debug!("Using analytics-recommended service: {}", service_id);
911        }
912
913        // For now, delegate to the original query plan execution
914        // In a full implementation, this would integrate the recommended service
915        // into the service selection logic
916        self.execute_query_plan(query_plan, context).await
917    }
918
919    /// Get performance analytics insights
920    pub async fn get_analytics_insights(&self) -> HashMap<String, serde_json::Value> {
921        let analytics = self.query_analytics.lock().await;
922        analytics.get_performance_insights()
923    }
924
925    /// Get the current merged schema
926    pub async fn get_schema(&self) -> Option<Schema> {
927        let schema_guard = self.merged_schema.read().await;
928        schema_guard.clone()
929    }
930
931    /// Get all discovered services
932    pub async fn get_services(&self) -> Vec<ServiceInfo> {
933        self.service_discovery.get_services().await
934    }
935
936    /// Get healthy services only
937    pub async fn get_healthy_services(&self) -> Vec<ServiceInfo> {
938        self.service_discovery.get_healthy_services().await
939    }
940
941    /// Get schema synchronization status
942    pub async fn get_sync_status(&self) -> super::real_time_sync::SyncStatus {
943        self.schema_synchronizer.get_sync_status().await
944    }
945
946    /// Get active schema conflicts
947    pub async fn get_schema_conflicts(&self) -> Vec<super::real_time_sync::SchemaConflict> {
948        self.schema_synchronizer.get_active_conflicts().await
949    }
950
951    /// Force a manual schema synchronization
952    pub async fn force_schema_sync(&self) -> Result<()> {
953        self.schema_synchronizer.perform_full_sync().await
954    }
955
956    /// Subscribe to schema changes
957    pub async fn subscribe_to_schema_changes(
958        &self,
959    ) -> tokio::sync::mpsc::UnboundedReceiver<super::real_time_sync::SchemaChangeEvent> {
960        self.schema_synchronizer.subscribe_to_changes().await
961    }
962
963    /// Execute a query plan
964    async fn execute_query_plan(
965        &self,
966        plan: &QueryPlan,
967        context: &FederationContext,
968    ) -> Result<FederationResult> {
969        let mut results = HashMap::new();
970        let mut errors = Vec::new();
971
972        // Execute each step of the plan
973        for step in &plan.steps {
974            match self.execute_query_step(step, context, &results).await {
975                Ok(step_result) => {
976                    if let Some(data) = step_result.data {
977                        results.insert(step.endpoint_id.clone(), data);
978                    }
979                    errors.extend(step_result.errors);
980                }
981                Err(e) => {
982                    errors.push(FederationError {
983                        message: format!(
984                            "Failed to execute step for service {}: {}",
985                            step.endpoint_id, e
986                        ),
987                        path: None,
988                        locations: None,
989                        extensions: HashMap::new(),
990                    });
991                }
992            }
993        }
994
995        // Merge results
996        let merged_data = self.merge_results(&results, plan).await?;
997
998        Ok(FederationResult {
999            data: Some(merged_data),
1000            errors,
1001            extensions: HashMap::new(),
1002        })
1003    }
1004
1005    /// Execute a single query step
1006    async fn execute_query_step(
1007        &self,
1008        step: &QueryStep,
1009        context: &FederationContext,
1010        _previous_results: &HashMap<String, serde_json::Value>,
1011    ) -> Result<FederationResult> {
1012        // Get service info
1013        let service = self
1014            .service_discovery
1015            .get_service(&step.endpoint_id)
1016            .await
1017            .ok_or_else(|| anyhow!("Service not found: {}", step.endpoint_id))?;
1018
1019        // Check circuit breaker
1020        {
1021            let mut breakers = self.circuit_breakers.write().await;
1022            let breaker = breakers
1023                .entry(service.id.clone())
1024                .or_insert_with(|| ServiceCircuitBreaker::new(self.config.circuit_breaker.clone()));
1025
1026            if !breaker.can_execute() {
1027                return Err(anyhow!("Circuit breaker open for service: {}", service.id));
1028            }
1029        }
1030
1031        // Execute query with retry
1032        let result = self
1033            .execute_with_retry(&service, &step.query_fragment, &context.variables)
1034            .await;
1035
1036        // Update circuit breaker
1037        {
1038            let mut breakers = self.circuit_breakers.write().await;
1039            if let Some(breaker) = breakers.get_mut(&service.id) {
1040                match result {
1041                    Ok(_) => breaker.record_success(),
1042                    Err(_) => breaker.record_failure(),
1043                }
1044            }
1045        }
1046
1047        // Record completion in load balancer
1048        self.load_balancer
1049            .lock()
1050            .await
1051            .record_completion(&service.id);
1052
1053        result
1054    }
1055
1056    /// Execute query with retry logic
1057    async fn execute_with_retry(
1058        &self,
1059        service: &ServiceInfo,
1060        query: &str,
1061        variables: &HashMap<String, Value>,
1062    ) -> Result<FederationResult> {
1063        let mut last_error = None;
1064
1065        for attempt in 0..=self.config.retry_policy.max_retries {
1066            if attempt > 0 {
1067                let delay = self.calculate_retry_delay(attempt);
1068                tokio::time::sleep(delay).await;
1069            }
1070
1071            match self.execute_single_request(service, query, variables).await {
1072                Ok(result) => return Ok(result),
1073                Err(e) => {
1074                    last_error = Some(e);
1075                    warn!(
1076                        "Query attempt {} failed for service {}: {}",
1077                        attempt + 1,
1078                        service.id,
1079                        last_error
1080                            .as_ref()
1081                            .expect("last_error should be set after failed attempt")
1082                    );
1083                }
1084            }
1085        }
1086
1087        Err(last_error.unwrap_or_else(|| anyhow!("All retry attempts failed")))
1088    }
1089
1090    /// Calculate retry delay with exponential backoff
1091    fn calculate_retry_delay(&self, attempt: usize) -> Duration {
1092        let base_delay = self.config.retry_policy.base_delay;
1093        let multiplier = self.config.retry_policy.backoff_multiplier;
1094        let max_delay = self.config.retry_policy.max_delay;
1095
1096        let delay = base_delay.as_millis() as f64 * multiplier.powi(attempt as i32);
1097        let delay = Duration::from_millis(delay.min(max_delay.as_millis() as f64) as u64);
1098
1099        if self.config.retry_policy.jitter {
1100            let jitter = fastrand::f64() * 0.1; // ±10% jitter
1101            let jitter_factor = 1.0 + (jitter - 0.05);
1102            Duration::from_millis((delay.as_millis() as f64 * jitter_factor) as u64)
1103        } else {
1104            delay
1105        }
1106    }
1107
1108    /// Execute a single HTTP request to a service
1109    async fn execute_single_request(
1110        &self,
1111        service: &ServiceInfo,
1112        query: &str,
1113        variables: &HashMap<String, Value>,
1114    ) -> Result<FederationResult> {
1115        let request_body = serde_json::json!({
1116            "query": query,
1117            "variables": variables
1118        });
1119
1120        let response = self
1121            .http_client
1122            .post(&service.url)
1123            .json(&request_body)
1124            .send()
1125            .await
1126            .context("Failed to send request")?;
1127
1128        if !response.status().is_success() {
1129            return Err(anyhow!(
1130                "HTTP request failed with status: {}",
1131                response.status()
1132            ));
1133        }
1134
1135        let response_json: serde_json::Value = response
1136            .json()
1137            .await
1138            .context("Failed to parse response JSON")?;
1139
1140        // Parse GraphQL response
1141        let data = response_json.get("data").cloned();
1142        let errors = response_json
1143            .get("errors")
1144            .and_then(|e| e.as_array())
1145            .map(|errors| {
1146                errors
1147                    .iter()
1148                    .filter_map(|error| {
1149                        Some(FederationError {
1150                            message: error.get("message")?.as_str()?.to_string(),
1151                            path: error.get("path").and_then(|p| {
1152                                p.as_array().map(|arr| {
1153                                    arr.iter()
1154                                        .filter_map(|v| v.as_str().map(|s| s.to_string()))
1155                                        .collect()
1156                                })
1157                            }),
1158                            locations: None, // Could be parsed from error object
1159                            extensions: HashMap::new(),
1160                        })
1161                    })
1162                    .collect()
1163            })
1164            .unwrap_or_default();
1165
1166        Ok(FederationResult {
1167            data,
1168            errors,
1169            extensions: HashMap::new(),
1170        })
1171    }
1172
1173    /// Merge results from multiple subgraph services into a single response.
1174    ///
1175    /// A flat top-level key union (the previous behaviour) silently dropped an
1176    /// entire subgraph's contribution whenever two subgraphs shared a top-level
1177    /// field name (e.g. both wrap their payload under `data`, or both expose an
1178    /// extended entity), because one `insert` clobbered the other in
1179    /// unordered `HashMap` iteration order.
1180    ///
1181    /// Instead we deterministically (sorted by endpoint id) *deep*-merge each
1182    /// subgraph's object payload: disjoint fields are unioned, and objects that
1183    /// appear at the same path in multiple subgraphs (the Apollo Federation
1184    /// entity-extension case) have their fields merged recursively rather than
1185    /// one replacing the other.
1186    async fn merge_results(
1187        &self,
1188        results: &HashMap<String, serde_json::Value>,
1189        _plan: &QueryPlan,
1190    ) -> Result<serde_json::Value> {
1191        if results.len() == 1 {
1192            return results
1193                .values()
1194                .next()
1195                .cloned()
1196                .ok_or_else(|| anyhow!("federated result map was unexpectedly empty"));
1197        }
1198
1199        // Deterministic merge order so the output is reproducible.
1200        let mut endpoint_ids: Vec<&String> = results.keys().collect();
1201        endpoint_ids.sort();
1202
1203        let mut merged = serde_json::Value::Object(serde_json::Map::new());
1204        for id in endpoint_ids {
1205            if let Some(value) = results.get(id) {
1206                Self::deep_merge_json(&mut merged, value);
1207            }
1208        }
1209        Ok(merged)
1210    }
1211
1212    /// Recursively merge `source` into `target`.
1213    ///
1214    /// - Two objects: merge key-by-key, recursing into shared keys.
1215    /// - Two arrays: concatenate.
1216    /// - Otherwise (`target` empty/null, or a leaf conflict): `source` fills an
1217    ///   absent/null slot but never overwrites an existing non-null leaf, so no
1218    ///   subgraph's data is silently discarded.
1219    fn deep_merge_json(target: &mut serde_json::Value, source: &serde_json::Value) {
1220        use serde_json::Value;
1221        match (target, source) {
1222            (Value::Object(t), Value::Object(s)) => {
1223                for (k, v) in s {
1224                    match t.get_mut(k) {
1225                        Some(existing) => Self::deep_merge_json(existing, v),
1226                        None => {
1227                            t.insert(k.clone(), v.clone());
1228                        }
1229                    }
1230                }
1231            }
1232            (Value::Array(t), Value::Array(s)) => {
1233                t.extend(s.iter().cloned());
1234            }
1235            (t @ Value::Null, s) => {
1236                *t = s.clone();
1237            }
1238            // Leaf-vs-leaf (or type mismatch): keep the first non-null value we
1239            // saw to stay deterministic; do not clobber established data.
1240            _ => {}
1241        }
1242    }
1243
1244    /// Rebuild the merged schema from all discovered services
1245    async fn rebuild_schema(&self) -> Result<()> {
1246        rebuild_federated_schema(
1247            &self.service_discovery,
1248            &self.schema_stitcher,
1249            &self.merged_schema,
1250        )
1251        .await
1252    }
1253
1254    /// Set up event handling for service discovery.
1255    ///
1256    /// Registers a [`SchemaRebuildHandler`] with the service-discovery engine so
1257    /// that a subgraph registering, deregistering, or changing health triggers a
1258    /// real rebuild of the merged federated schema. The handler holds `Arc`
1259    /// clones of exactly the shared state `rebuild_schema` needs
1260    /// (`service_discovery`, `schema_stitcher`, `merged_schema`) — no raw
1261    /// pointer to `self`, so there is no lifetime hazard and no `unsafe`.
1262    async fn setup_event_handling(&self) -> Result<()> {
1263        let handler = SchemaRebuildHandler {
1264            service_discovery: Arc::clone(&self.service_discovery),
1265            schema_stitcher: Arc::clone(&self.schema_stitcher),
1266            merged_schema: Arc::clone(&self.merged_schema),
1267        };
1268        self.service_discovery
1269            .add_event_handler(Box::new(handler))
1270            .await;
1271        info!("Service discovery event handling configured (dynamic re-federation active)");
1272        Ok(())
1273    }
1274}
1275
1276/// Rebuild the merged federated schema from all currently-healthy services.
1277///
1278/// Shared by [`EnhancedFederationManager::rebuild_schema`] and the
1279/// discovery-event [`SchemaRebuildHandler`], so both paths produce identical
1280/// results.
1281async fn rebuild_federated_schema(
1282    service_discovery: &Arc<ServiceDiscovery>,
1283    schema_stitcher: &Arc<SchemaStitcher>,
1284    merged_schema: &Arc<RwLock<Option<Schema>>>,
1285) -> Result<()> {
1286    info!("Rebuilding federated schema");
1287
1288    let services = service_discovery.get_healthy_services().await;
1289    let endpoints: Vec<RemoteEndpoint> = services
1290        .into_iter()
1291        .map(|service| RemoteEndpoint {
1292            id: service.id,
1293            url: service.url,
1294            namespace: Some(service.name),
1295            auth_header: None,
1296            timeout_secs: 30,
1297            max_retries: 3,
1298            retry_strategy: RetryStrategy::ExponentialBackoff {
1299                initial_delay_ms: 100,
1300                max_delay_ms: 5000,
1301                multiplier: 2.0,
1302            },
1303            health_check_url: None,
1304            priority: 0,
1305            schema_version: service.federation_version,
1306            min_compatible_version: None,
1307        })
1308        .collect();
1309
1310    let schema = schema_stitcher.merge_schemas(&endpoints).await?;
1311
1312    {
1313        let mut schema_guard = merged_schema.write().await;
1314        *schema_guard = Some(schema);
1315    }
1316
1317    info!("Schema rebuilt successfully");
1318    Ok(())
1319}
1320
1321/// Handler that rebuilds the merged federated schema in response to
1322/// service-discovery topology / health changes.
1323struct SchemaRebuildHandler {
1324    service_discovery: Arc<ServiceDiscovery>,
1325    schema_stitcher: Arc<SchemaStitcher>,
1326    merged_schema: Arc<RwLock<Option<Schema>>>,
1327}
1328
1329#[async_trait]
1330impl ServiceDiscoveryEventHandler for SchemaRebuildHandler {
1331    async fn handle_event(&self, event: DiscoveryEvent) -> Result<()> {
1332        match event {
1333            DiscoveryEvent::ServiceRegistered(_)
1334            | DiscoveryEvent::ServiceDeregistered(_)
1335            | DiscoveryEvent::HealthChanged { .. } => {
1336                info!("Service discovery event received; rebuilding federated schema");
1337                if let Err(e) = rebuild_federated_schema(
1338                    &self.service_discovery,
1339                    &self.schema_stitcher,
1340                    &self.merged_schema,
1341                )
1342                .await
1343                {
1344                    warn!("Failed to rebuild federated schema after discovery event: {e}");
1345                    return Err(e);
1346                }
1347                Ok(())
1348            }
1349            _ => Ok(()),
1350        }
1351    }
1352}
1353
1354#[cfg(test)]
1355mod tests {
1356    use super::*;
1357
1358    #[tokio::test]
1359    async fn test_load_balancer_round_robin() {
1360        let config = LoadBalancingConfig {
1361            algorithm: LoadBalancingAlgorithm::RoundRobin,
1362            ..Default::default()
1363        };
1364        let mut balancer = LoadBalancer::new(config);
1365
1366        let services = vec![
1367            ServiceInfo {
1368                id: "service-1".to_string(),
1369                name: "Service 1".to_string(),
1370                url: "http://localhost:4001".to_string(),
1371                version: None,
1372                capabilities: Default::default(),
1373                health_status: HealthStatus::Healthy,
1374                metadata: HashMap::new(),
1375                last_seen: chrono::Utc::now(),
1376                response_time: None,
1377                load_factor: 0.5,
1378                federation_version: None,
1379            },
1380            ServiceInfo {
1381                id: "service-2".to_string(),
1382                name: "Service 2".to_string(),
1383                url: "http://localhost:4002".to_string(),
1384                version: None,
1385                capabilities: Default::default(),
1386                health_status: HealthStatus::Healthy,
1387                metadata: HashMap::new(),
1388                last_seen: chrono::Utc::now(),
1389                response_time: None,
1390                load_factor: 0.5,
1391                federation_version: None,
1392            },
1393        ];
1394
1395        let selected1 = balancer.select_service(&services, None);
1396        let selected2 = balancer.select_service(&services, None);
1397
1398        assert_eq!(selected1, Some("service-1".to_string()));
1399        assert_eq!(selected2, Some("service-2".to_string()));
1400    }
1401
1402    #[test]
1403    fn test_circuit_breaker() {
1404        let config = CircuitBreakerConfig {
1405            failure_threshold: 2,
1406            success_threshold: 1,
1407            timeout: Duration::from_secs(1),
1408            retry_after: Duration::from_secs(1),
1409        };
1410        let mut breaker = ServiceCircuitBreaker::new(config);
1411
1412        assert!(breaker.can_execute());
1413        assert_eq!(breaker.state(), CircuitBreakerState::Closed);
1414
1415        // Record failures to open circuit
1416        breaker.record_failure();
1417        assert!(breaker.can_execute());
1418        breaker.record_failure();
1419        assert!(!breaker.can_execute());
1420        assert_eq!(breaker.state(), CircuitBreakerState::Open);
1421    }
1422
1423    #[test]
1424    fn regression_deep_merge_preserves_both_subgraphs_under_shared_key() {
1425        // Two subgraphs both wrap payload under `data` with disjoint fields.
1426        let mut merged = serde_json::json!({});
1427        let a = serde_json::json!({"data": {"product": {"id": "1"}}});
1428        let b = serde_json::json!({"data": {"review": {"stars": 5}}});
1429        EnhancedFederationManager::deep_merge_json(&mut merged, &a);
1430        EnhancedFederationManager::deep_merge_json(&mut merged, &b);
1431
1432        // Neither subgraph's contribution may be lost.
1433        assert_eq!(merged["data"]["product"]["id"], serde_json::json!("1"));
1434        assert_eq!(merged["data"]["review"]["stars"], serde_json::json!(5));
1435    }
1436
1437    #[test]
1438    fn regression_deep_merge_entity_extension_merges_fields() {
1439        // Apollo Federation entity extension: same entity, different fields.
1440        let mut merged = serde_json::json!({});
1441        let base = serde_json::json!({"product": {"id": "1", "name": "Widget"}});
1442        let ext = serde_json::json!({"product": {"id": "1", "price": 9.99}});
1443        EnhancedFederationManager::deep_merge_json(&mut merged, &base);
1444        EnhancedFederationManager::deep_merge_json(&mut merged, &ext);
1445
1446        assert_eq!(merged["product"]["name"], serde_json::json!("Widget"));
1447        assert_eq!(merged["product"]["price"], serde_json::json!(9.99));
1448        assert_eq!(merged["product"]["id"], serde_json::json!("1"));
1449    }
1450
1451    #[test]
1452    fn regression_deep_merge_concatenates_arrays() {
1453        let mut merged = serde_json::json!({"items": [1, 2]});
1454        let more = serde_json::json!({"items": [3, 4]});
1455        EnhancedFederationManager::deep_merge_json(&mut merged, &more);
1456        assert_eq!(merged["items"], serde_json::json!([1, 2, 3, 4]));
1457    }
1458}