enabledEnvironments = new ArrayList<>();
+
+ /**
+ * Description of what this feature does
+ */
+ private String description;
+
+ /**
+ * When this feature was last modified
+ */
+ private String lastModified;
+
+ /**
+ * Check if a specific user should have this feature enabled.
+ *
+ * Uses consistent hashing to determine if user is in rollout percentage.
+ * Same user will always get the same result for a given percentage.
+ *
+ * @param userId The user ID to check
+ * @return true if feature should be enabled for this user
+ */
+ public boolean isEnabledForUser(String userId) {
+ if (!enabled) {
+ return false;
+ }
+
+ // Explicitly enabled users always get the feature
+ if (enabledUsers.contains(userId)) {
+ return true;
+ }
+
+ // If rollout is 100%, everyone gets it
+ if (rolloutPercentage >= 100) {
+ return true;
+ }
+
+ // If rollout is 0%, nobody gets it (except explicit users)
+ if (rolloutPercentage <= 0) {
+ return false;
+ }
+
+ // Use consistent hashing to determine if user is in rollout percentage
+ int hash = Math.abs(userId.hashCode());
+ return (hash % 100) < rolloutPercentage;
+ }
+
+ /**
+ * Check if feature is enabled for a specific environment
+ *
+ * @param environment The environment to check (dev, stage, prod, etc.)
+ * @return true if feature is enabled for this environment
+ */
+ public boolean isEnabledForEnvironment(String environment) {
+ if (!enabled) {
+ return false;
+ }
+
+ // If no environments specified, enabled for all
+ if (enabledEnvironments.isEmpty()) {
+ return true;
+ }
+
+ return enabledEnvironments.contains(environment);
+ }
+
+ /**
+ * Create a disabled feature toggle
+ */
+ public static FeatureToggle disabled() {
+ FeatureToggle toggle = new FeatureToggle();
+ toggle.setEnabled(false);
+ return toggle;
+ }
+
+ /**
+ * Create a fully enabled feature toggle
+ */
+ public static FeatureToggle fullyEnabled() {
+ FeatureToggle toggle = new FeatureToggle();
+ toggle.setEnabled(true);
+ toggle.setRolloutPercentage(100);
+ return toggle;
+ }
+
+ /**
+ * Create a feature toggle with specific rollout percentage
+ */
+ public static FeatureToggle withRollout(int percentage) {
+ FeatureToggle toggle = new FeatureToggle();
+ toggle.setEnabled(true);
+ toggle.setRolloutPercentage(Math.max(0, Math.min(100, percentage)));
+ return toggle;
+ }
+}
diff --git a/order-service/src/main/java/com/selimhorri/app/config/FeatureToggleService.java b/order-service/src/main/java/com/selimhorri/app/config/FeatureToggleService.java
new file mode 100644
index 000000000..43b8bb35a
--- /dev/null
+++ b/order-service/src/main/java/com/selimhorri/app/config/FeatureToggleService.java
@@ -0,0 +1,226 @@
+package com.selimhorri.app.config;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Service for managing feature toggles (feature flags).
+ *
+ * Feature toggles allow features to be enabled/disabled dynamically without
+ * redeployment, enabling:
+ * - Gradual rollouts (canary releases)
+ * - A/B testing
+ * - Kill switches for problematic features
+ * - Environment-specific features
+ *
+ * Configuration is externalized in application.yml and can be refreshed
+ * dynamically using Spring Cloud Config refresh endpoint.
+ *
+ * Usage example:
+ *
+ *
+ * if (featureToggleService.isEnabled("new-checkout-flow")) {
+ * return newCheckoutFlow();
+ * } else {
+ * return legacyCheckoutFlow();
+ * }
+ *
+ *
+ * @author ecommerce-team
+ * @version 1.0
+ */
+@Service
+@ConfigurationProperties(prefix = "features")
+@RefreshScope
+@Data
+@Slf4j
+public class FeatureToggleService {
+
+ /**
+ * Map of feature name to feature toggle configuration
+ */
+ private Map toggles = new HashMap<>();
+
+ /**
+ * Current environment (dev, stage, prod)
+ */
+ private String environment = "dev";
+
+ @PostConstruct
+ public void init() {
+ log.info("Feature Toggle Service initialized with {} features", toggles.size());
+ toggles.forEach((name, toggle) -> {
+ log.info("Feature '{}': enabled={}, rollout={}%",
+ name, toggle.isEnabled(), toggle.getRolloutPercentage());
+ });
+ }
+
+ /**
+ * Check if a feature is enabled globally.
+ *
+ * @param featureName The name of the feature
+ * @return true if feature is enabled
+ */
+ public boolean isEnabled(String featureName) {
+ FeatureToggle toggle = toggles.get(featureName);
+
+ if (toggle == null) {
+ log.warn("Feature toggle '{}' not found. Returning false by default.", featureName);
+ return false;
+ }
+
+ boolean enabledForEnv = toggle.isEnabledForEnvironment(environment);
+ boolean globallyEnabled = toggle.isEnabled();
+
+ log.debug("Feature '{}' check: globallyEnabled={}, enabledForEnv={}",
+ featureName, globallyEnabled, enabledForEnv);
+
+ return globallyEnabled && enabledForEnv;
+ }
+
+ /**
+ * Check if a feature is enabled for a specific user.
+ *
+ * Takes into account:
+ * - Global enabled flag
+ * - Environment enablement
+ * - User-specific enablement
+ * - Rollout percentage (consistent hashing)
+ *
+ * @param featureName The name of the feature
+ * @param userId The user ID to check
+ * @return true if feature is enabled for this user
+ */
+ public boolean isEnabledForUser(String featureName, String userId) {
+ FeatureToggle toggle = toggles.get(featureName);
+
+ if (toggle == null) {
+ log.warn("Feature toggle '{}' not found for user {}. Returning false.", featureName, userId);
+ return false;
+ }
+
+ if (!toggle.isEnabledForEnvironment(environment)) {
+ log.debug("Feature '{}' not enabled for environment '{}'", featureName, environment);
+ return false;
+ }
+
+ boolean enabledForUser = toggle.isEnabledForUser(userId);
+
+ log.debug("Feature '{}' check for user {}: enabled={}", featureName, userId, enabledForUser);
+
+ return enabledForUser;
+ }
+
+ /**
+ * Get all feature toggles and their current state
+ *
+ * @return Map of all feature toggles
+ */
+ public Map getAllToggles() {
+ return new HashMap<>(toggles);
+ }
+
+ /**
+ * Update a feature toggle at runtime.
+ *
+ * Note: This only updates the in-memory state. For persistent changes,
+ * update the configuration source (application.yml or Config Server).
+ *
+ * @param featureName Name of the feature
+ * @param toggle New toggle configuration
+ */
+ public void updateToggle(String featureName, FeatureToggle toggle) {
+ log.info("Updating feature toggle '{}': enabled={}, rollout={}%",
+ featureName, toggle.isEnabled(), toggle.getRolloutPercentage());
+
+ toggle.setLastModified(LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME));
+ toggles.put(featureName, toggle);
+ }
+
+ /**
+ * Enable a feature for all users
+ *
+ * @param featureName Name of the feature
+ */
+ public void enableFeature(String featureName) {
+ FeatureToggle toggle = toggles.getOrDefault(featureName, new FeatureToggle());
+ toggle.setEnabled(true);
+ toggle.setRolloutPercentage(100);
+ updateToggle(featureName, toggle);
+ }
+
+ /**
+ * Disable a feature for all users
+ *
+ * @param featureName Name of the feature
+ */
+ public void disableFeature(String featureName) {
+ FeatureToggle toggle = toggles.getOrDefault(featureName, new FeatureToggle());
+ toggle.setEnabled(false);
+ updateToggle(featureName, toggle);
+ }
+
+ /**
+ * Set rollout percentage for gradual release
+ *
+ * @param featureName Name of the feature
+ * @param percentage Percentage of users (0-100)
+ */
+ public void setRolloutPercentage(String featureName, int percentage) {
+ if (percentage < 0 || percentage > 100) {
+ throw new IllegalArgumentException("Percentage must be between 0 and 100");
+ }
+
+ FeatureToggle toggle = toggles.getOrDefault(featureName, new FeatureToggle());
+ toggle.setEnabled(true);
+ toggle.setRolloutPercentage(percentage);
+ updateToggle(featureName, toggle);
+
+ log.info("Feature '{}' rollout set to {}%", featureName, percentage);
+ }
+
+ /**
+ * Get the current rollout percentage for a feature
+ *
+ * @param featureName Name of the feature
+ * @return Rollout percentage (0-100), or 0 if feature doesn't exist
+ */
+ public int getRolloutPercentage(String featureName) {
+ FeatureToggle toggle = toggles.get(featureName);
+ return toggle != null ? toggle.getRolloutPercentage() : 0;
+ }
+
+ /**
+ * Add a user to the explicit enable list for a feature
+ *
+ * @param featureName Name of the feature
+ * @param userId User ID to enable
+ */
+ public void enableForUser(String featureName, String userId) {
+ FeatureToggle toggle = toggles.getOrDefault(featureName, new FeatureToggle());
+ if (!toggle.getEnabledUsers().contains(userId)) {
+ toggle.getEnabledUsers().add(userId);
+ updateToggle(featureName, toggle);
+ log.info("Feature '{}' explicitly enabled for user: {}", featureName, userId);
+ }
+ }
+
+ /**
+ * Check if a feature exists in configuration
+ *
+ * @param featureName Name of the feature
+ * @return true if feature exists
+ */
+ public boolean featureExists(String featureName) {
+ return toggles.containsKey(featureName);
+ }
+}
diff --git a/order-service/src/main/java/com/selimhorri/app/config/ResilienceEventListener.java b/order-service/src/main/java/com/selimhorri/app/config/ResilienceEventListener.java
new file mode 100644
index 000000000..cdf9f0fbd
--- /dev/null
+++ b/order-service/src/main/java/com/selimhorri/app/config/ResilienceEventListener.java
@@ -0,0 +1,185 @@
+package com.selimhorri.app.config;
+
+import io.github.resilience4j.bulkhead.event.BulkheadEvent;
+import io.github.resilience4j.bulkhead.event.BulkheadOnCallFinishedEvent;
+import io.github.resilience4j.bulkhead.event.BulkheadOnCallPermittedEvent;
+import io.github.resilience4j.bulkhead.event.BulkheadOnCallRejectedEvent;
+import io.github.resilience4j.circuitbreaker.event.*;
+import io.github.resilience4j.retry.event.*;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * Event listeners for Resilience4j patterns.
+ *
+ * Provides centralized logging and monitoring for:
+ * - Circuit Breaker events
+ * - Bulkhead events
+ * - Retry events
+ *
+ * These events are useful for:
+ * - Debugging resilience issues
+ * - Monitoring system health
+ * - Alerting on pattern activations
+ * - Metrics collection
+ *
+ * @author ecommerce-team
+ * @version 1.0
+ */
+@Component
+@Slf4j
+public class ResilienceEventListener {
+
+ // ========================================
+ // Circuit Breaker Events
+ // ========================================
+
+ @EventListener
+ public void onCircuitBreakerStateTransition(CircuitBreakerOnStateTransitionEvent event) {
+ log.warn("Circuit Breaker '{}' transitioned from {} to {}",
+ event.getCircuitBreakerName(),
+ event.getStateTransition().getFromState(),
+ event.getStateTransition().getToState());
+
+ // In production, you might:
+ // - Send alert if circuit opens
+ // - Update dashboard
+ // - Trigger auto-scaling if needed
+ }
+
+ @EventListener
+ public void onCircuitBreakerError(CircuitBreakerOnErrorEvent event) {
+ log.error("Circuit Breaker '{}' recorded error: {}",
+ event.getCircuitBreakerName(),
+ event.getThrowable().getMessage());
+ }
+
+ @EventListener
+ public void onCircuitBreakerSuccess(CircuitBreakerOnSuccessEvent event) {
+ log.debug("Circuit Breaker '{}' recorded success. Duration: {}ms",
+ event.getCircuitBreakerName(),
+ event.getElapsedDuration().toMillis());
+ }
+
+ @EventListener
+ public void onCircuitBreakerCallNotPermitted(CircuitBreakerOnCallNotPermittedEvent event) {
+ log.warn("Circuit Breaker '{}' call not permitted - circuit is OPEN",
+ event.getCircuitBreakerName());
+
+ // Alert! Service is completely blocked
+ // Consider scaling, failover, or manual intervention
+ }
+
+ // ========================================
+ // Bulkhead Events
+ // ========================================
+
+ @EventListener
+ public void onBulkheadCallRejected(BulkheadOnCallRejectedEvent event) {
+ log.warn("Bulkhead '{}' rejected call - max concurrent calls reached",
+ event.getBulkheadName());
+
+ // This indicates system is at capacity
+ // Consider:
+ // - Auto-scaling
+ // - Load shedding
+ // - Alerting operations team
+ }
+
+ @EventListener
+ public void onBulkheadCallPermitted(BulkheadOnCallPermittedEvent event) {
+ log.debug("Bulkhead '{}' permitted call",
+ event.getBulkheadName());
+ }
+
+ @EventListener
+ public void onBulkheadCallFinished(BulkheadOnCallFinishedEvent event) {
+ log.debug("Bulkhead '{}' call finished",
+ event.getBulkheadName());
+ }
+
+ // ========================================
+ // Retry Events
+ // ========================================
+
+ @EventListener
+ public void onRetryAttempt(RetryOnRetryEvent event) {
+ log.warn("Retry attempt {} for '{}'. Last exception: {}",
+ event.getNumberOfRetryAttempts(),
+ event.getName(),
+ event.getLastThrowable().getMessage());
+
+ // Track retry metrics
+ // If too many retries, might indicate systematic issue
+ }
+
+ @EventListener
+ public void onRetrySuccess(RetryOnSuccessEvent event) {
+ log.info("Retry succeeded for '{}' after {} attempts",
+ event.getName(),
+ event.getNumberOfRetryAttempts());
+
+ // Good to know - service recovered
+ // But many retries might indicate unstable dependency
+ }
+
+ @EventListener
+ public void onRetryError(RetryOnErrorEvent event) {
+ log.error("Retry failed for '{}' after {} attempts. Final error: {}",
+ event.getName(),
+ event.getNumberOfRetryAttempts(),
+ event.getLastThrowable().getMessage());
+
+ // All retries exhausted - invoking fallback
+ // Alert operations team
+ // Check dependent service health
+ }
+
+ @EventListener
+ public void onRetryIgnoredError(RetryOnIgnoredErrorEvent event) {
+ log.info("Retry ignored error for '{}': {}",
+ event.getName(),
+ event.getLastThrowable().getMessage());
+
+ // Error type is configured to not trigger retry
+ // This is expected for certain error types
+ }
+
+ /**
+ * Generic handler for any Bulkhead event
+ * Use for custom metrics collection
+ */
+ @EventListener
+ public void onBulkheadEvent(BulkheadEvent event) {
+ // Custom metrics collection
+ // Example: Send to Prometheus, Datadog, etc.
+ log.trace("Bulkhead event: {} - {}",
+ event.getBulkheadName(),
+ event.getEventType());
+ }
+
+ /**
+ * Generic handler for any Circuit Breaker event
+ * Use for custom metrics collection
+ */
+ @EventListener
+ public void onCircuitBreakerEvent(CircuitBreakerEvent event) {
+ // Custom metrics collection
+ log.trace("Circuit Breaker event: {} - {}",
+ event.getCircuitBreakerName(),
+ event.getEventType());
+ }
+
+ /**
+ * Generic handler for any Retry event
+ * Use for custom metrics collection
+ */
+ @EventListener
+ public void onRetryEvent(RetryEvent event) {
+ // Custom metrics collection
+ log.trace("Retry event: {} - {}",
+ event.getName(),
+ event.getEventType());
+ }
+}
diff --git a/order-service/src/main/java/com/selimhorri/app/health/DatabaseHealthIndicator.java b/order-service/src/main/java/com/selimhorri/app/health/DatabaseHealthIndicator.java
new file mode 100644
index 000000000..261ec76e2
--- /dev/null
+++ b/order-service/src/main/java/com/selimhorri/app/health/DatabaseHealthIndicator.java
@@ -0,0 +1,53 @@
+package com.selimhorri.app.health;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.stereotype.Component;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+/**
+ * Custom health indicator for database connectivity
+ */
+@Component("database")
+@RequiredArgsConstructor
+@Slf4j
+public class DatabaseHealthIndicator implements HealthIndicator {
+
+ private final DataSource dataSource;
+
+ @Override
+ public Health health() {
+ try (Connection connection = dataSource.getConnection();
+ Statement statement = connection.createStatement();
+ ResultSet resultSet = statement.executeQuery("SELECT 1")) {
+
+ if (resultSet.next()) {
+ // Get database metadata
+ String databaseProductName = connection.getMetaData().getDatabaseProductName();
+ String databaseProductVersion = connection.getMetaData().getDatabaseProductVersion();
+
+ return Health.up()
+ .withDetail("database", databaseProductName)
+ .withDetail("version", databaseProductVersion)
+ .withDetail("status", "Connected")
+ .build();
+ } else {
+ return Health.down()
+ .withDetail("status", "Unable to validate connection")
+ .build();
+ }
+ } catch (Exception e) {
+ log.error("Database health check failed", e);
+ return Health.down()
+ .withDetail("error", e.getMessage())
+ .withException(e)
+ .build();
+ }
+ }
+}
diff --git a/order-service/src/main/java/com/selimhorri/app/resilience/OrderBulkheadService.java b/order-service/src/main/java/com/selimhorri/app/resilience/OrderBulkheadService.java
new file mode 100644
index 000000000..ff8327887
--- /dev/null
+++ b/order-service/src/main/java/com/selimhorri/app/resilience/OrderBulkheadService.java
@@ -0,0 +1,174 @@
+package com.selimhorri.app.resilience;
+
+import com.selimhorri.app.dto.OrderDto;
+import com.selimhorri.app.service.OrderService;
+import io.github.resilience4j.bulkhead.annotation.Bulkhead;
+import io.github.resilience4j.bulkhead.annotation.Bulkhead.Type;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * Service implementing the Bulkhead Pattern for order processing.
+ *
+ * The Bulkhead pattern isolates resources for different types of operations,
+ * preventing one type of workload from consuming all available resources
+ * and affecting other operations.
+ *
+ * This implementation provides:
+ * - Semaphore-based bulkh AAAºead for synchronous order processing
+ * - Thread pool-based bulkhead for asynchronous order processing
+ * - Fallback mechanisms when bulkhead is full
+ * - Metrics and monitoring through Resilience4j
+ *
+ * @author ecommerce-team
+ * @version 1.0
+ */
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class OrderBulkheadService {
+
+ private final OrderService orderService;
+
+ /**
+ * Process order with semaphore-based bulkhead protection.
+ *
+ * This limits concurrent order processing to prevent resource exhaustion.
+ * When the limit is reached, the fallback method is called.
+ *
+ * @param orderDto The order to process
+ * @return Processed order
+ */
+ @Bulkhead(name = "orderProcessing", fallbackMethod = "orderProcessingFallback", type = Type.SEMAPHORE)
+ public OrderDto processOrder(OrderDto orderDto) {
+ log.info("Processing order with bulkhead protection: {}", orderDto.getOrderId());
+
+ try {
+ // Simulate processing time
+ Thread.sleep(100);
+
+ // Actual order processing
+ OrderDto savedOrder = orderService.save(orderDto);
+
+ log.info("Successfully processed order: {}", savedOrder.getOrderId());
+ return savedOrder;
+
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ log.error("Order processing interrupted: {}", orderDto.getOrderId());
+ throw new RuntimeException("Order processing interrupted", e);
+ }
+ }
+
+ /**
+ * Fallback method when bulkhead is full.
+ *
+ * This provides graceful degradation by queueing the order
+ * for later processing instead of rejecting it outright.
+ *
+ * @param orderDto The order that couldn't be processed
+ * @param exception The exception that triggered the fallback
+ * @return Order with pending status
+ */
+ public OrderDto orderProcessingFallback(OrderDto orderDto, Exception exception) {
+ log.error("Bulkhead full for order processing. Order queued for retry: {}. Reason: {}",
+ orderDto.getOrderId(),
+ exception.getMessage());
+
+ // Set order description to indicate pending retry status
+ orderDto.setOrderDesc("PENDING_RETRY: " + orderDto.getOrderDesc());
+
+ // In a real implementation, you would:
+ // 1. Queue to message broker (RabbitMQ, Kafka)
+ // 2. Store in retry queue database
+ // 3. Schedule for later processing
+
+ return orderDto;
+ }
+
+ /**
+ * Process order asynchronously with thread pool bulkhead.
+ *
+ * This uses a dedicated thread pool for async processing,
+ * preventing blocking operations from affecting the main thread pool.
+ *
+ * @param orderDto The order to process
+ * @return Processed order
+ */
+ @Bulkhead(name = "orderAsyncProcessing", fallbackMethod = "orderAsyncProcessingFallback", type = Type.THREADPOOL)
+ public OrderDto processOrderAsync(OrderDto orderDto) {
+ log.info("Processing order asynchronously with thread pool bulkhead: {}", orderDto.getOrderId());
+
+ try {
+ // Simulate async processing
+ Thread.sleep(200);
+
+ OrderDto savedOrder = orderService.save(orderDto);
+
+ log.info("Successfully processed order async: {}", savedOrder.getOrderId());
+ return savedOrder;
+
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ log.error("Async order processing interrupted: {}", orderDto.getOrderId());
+ throw new RuntimeException("Async order processing interrupted", e);
+ }
+ }
+
+ /**
+ * Fallback for async processing when thread pool is full.
+ *
+ * @param orderDto The order that couldn't be processed
+ * @param exception The exception that triggered the fallback
+ * @return Order with queued status
+ */
+ public OrderDto orderAsyncProcessingFallback(OrderDto orderDto, Exception exception) {
+ log.error("Thread pool bulkhead full for async order processing. Order queued: {}. Reason: {}",
+ orderDto.getOrderId(),
+ exception.getMessage());
+
+ orderDto.setOrderDesc("QUEUED_FOR_ASYNC: " + orderDto.getOrderDesc());
+
+ return orderDto;
+ }
+
+ /**
+ * High priority order processing with dedicated bulkhead.
+ *
+ * This ensures that high-priority orders have dedicated resources
+ * and are not affected by regular order processing load.
+ *
+ * @param orderDto High priority order
+ * @return Processed order
+ */
+ @Bulkhead(name = "highPriorityOrders", fallbackMethod = "highPriorityFallback", type = Type.SEMAPHORE)
+ public OrderDto processHighPriorityOrder(OrderDto orderDto) {
+ log.info("Processing HIGH PRIORITY order: {}", orderDto.getOrderId());
+
+ // Fast-track processing for high priority orders
+ // In a real implementation, you would add a priority field to OrderDto
+ orderDto.setOrderDesc("HIGH_PRIORITY: " + orderDto.getOrderDesc());
+ OrderDto savedOrder = orderService.save(orderDto);
+
+ return savedOrder;
+ }
+
+ /**
+ * Fallback for high priority orders.
+ * High priority orders should rarely hit this, but when they do,
+ * we need to handle them specially.
+ */
+ public OrderDto highPriorityFallback(OrderDto orderDto, Exception exception) {
+ log.error("CRITICAL: High priority order bulkhead full: {}. Immediate escalation needed!",
+ orderDto.getOrderId());
+
+ // In production:
+ // 1. Alert operations team
+ // 2. Auto-scale if in cloud
+ // 3. Queue to highest priority queue
+
+ orderDto.setOrderDesc("ESCALATED: " + orderDto.getOrderDesc());
+ return orderDto;
+ }
+}
diff --git a/order-service/src/main/java/com/selimhorri/app/resilience/OrderRetryService.java b/order-service/src/main/java/com/selimhorri/app/resilience/OrderRetryService.java
new file mode 100644
index 000000000..84a9c80fd
--- /dev/null
+++ b/order-service/src/main/java/com/selimhorri/app/resilience/OrderRetryService.java
@@ -0,0 +1,194 @@
+package com.selimhorri.app.resilience;
+
+import com.selimhorri.app.dto.OrderDto;
+import com.selimhorri.app.service.OrderService;
+import io.github.resilience4j.retry.annotation.Retry;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * Service implementing the Retry Pattern with exponential backoff.
+ *
+ * The Retry pattern automatically retries failed operations with
+ * increasing delays between attempts (exponential backoff).
+ *
+ * This is particularly useful for:
+ * - Transient network failures
+ * - Temporary service unavailability
+ * - Database connection timeouts
+ * - External API rate limiting
+ *
+ * Benefits:
+ * - Automatic recovery from transient failures
+ * - Exponential backoff prevents overwhelming struggling services
+ * - Configurable retry logic per operation type
+ * - Fallback mechanisms for ultimate failure
+ *
+ * @author ecommerce-team
+ * @version 1.0
+ */
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class OrderRetryService {
+
+ private final OrderService orderService;
+ private final RestTemplate restTemplate;
+
+ /**
+ * Save order with retry on database failures.
+ *
+ * Retries up to 3 times with exponential backoff if database operations fail.
+ * Useful for handling transient DB connection issues.
+ *
+ * @param orderDto Order to save
+ * @return Saved order
+ */
+ @Retry(name = "orderProcessing", fallbackMethod = "saveOrderFallback")
+ public OrderDto saveOrderWithRetry(OrderDto orderDto) {
+ log.info("Attempting to save order with retry: {}", orderDto.getOrderId());
+
+ OrderDto savedOrder = orderService.save(orderDto);
+
+ log.info("Successfully saved order: {}", savedOrder.getOrderId());
+ return savedOrder;
+ }
+
+ /**
+ * Fallback method when all retry attempts fail.
+ * Stores order in a dead-letter queue for manual processing.
+ *
+ * @param orderDto The order that failed to save
+ * @param exception The exception that caused the failure
+ * @return Order with error status
+ */
+ public OrderDto saveOrderFallback(OrderDto orderDto, Exception exception) {
+ log.error("All retry attempts failed for order: {}. Error: {}",
+ orderDto.getOrderId(),
+ exception.getMessage());
+
+ // In production, you would:
+ // 1. Store in dead-letter queue (DLQ)
+ // 2. Send alert to operations team
+ // 3. Log to error tracking system (Sentry, Rollbar, etc.)
+
+ orderDto.setOrderDesc("FAILED: " + orderDto.getOrderDesc());
+
+ return orderDto;
+ }
+
+ /**
+ * Call external API with retry logic.
+ *
+ * Retries up to 5 times with exponential backoff for external API calls.
+ * More retry attempts because external APIs are more prone to transient issues.
+ *
+ * @param apiUrl API endpoint URL
+ * @param orderId Order ID for the API call
+ * @return API response
+ */
+ @Retry(name = "externalApiCall", fallbackMethod = "externalApiCallFallback")
+ public String callExternalApiWithRetry(String apiUrl, Integer orderId) {
+ log.info("Calling external API with retry: {} for order: {}", apiUrl, orderId);
+
+ try {
+ String response = restTemplate.getForObject(apiUrl + "/" + orderId, String.class);
+ log.info("External API call successful for order: {}", orderId);
+ return response;
+
+ } catch (Exception e) {
+ log.warn("External API call failed (will retry): {} - {}", apiUrl, e.getMessage());
+ throw e;
+ }
+ }
+
+ /**
+ * Fallback for external API calls.
+ * Returns cached data or default response.
+ *
+ * @param apiUrl The API URL that failed
+ * @param orderId The order ID
+ * @param exception The exception thrown
+ * @return Fallback response
+ */
+ public String externalApiCallFallback(String apiUrl, Integer orderId, Exception exception) {
+ log.error("External API call failed after all retries: {} for order: {}. Error: {}",
+ apiUrl, orderId, exception.getMessage());
+
+ // Return cached data or default response
+ return "{\"status\":\"unavailable\",\"orderId\":" + orderId
+ + ",\"message\":\"Service temporarily unavailable\"}";
+ }
+
+ /**
+ * Process payment with retry.
+ * Critical operation that needs multiple retry attempts.
+ *
+ * @param orderId Order ID for payment
+ * @param amount Amount to charge
+ * @return Payment confirmation
+ */
+ @Retry(name = "orderProcessing", fallbackMethod = "processPaymentFallback")
+ public String processPaymentWithRetry(Integer orderId, Double amount) {
+ log.info("Processing payment with retry for order: {}, amount: {}", orderId, amount);
+
+ // Simulate payment processing that might fail
+ if (Math.random() < 0.3) { // 30% chance of transient failure
+ throw new RuntimeException("Payment gateway temporarily unavailable");
+ }
+
+ log.info("Payment processed successfully for order: {}", orderId);
+ return "PAYMENT_SUCCESS";
+ }
+
+ /**
+ * Fallback for payment processing.
+ * Queues payment for later retry or manual processing.
+ */
+ public String processPaymentFallback(Integer orderId, Double amount, Exception exception) {
+ log.error("Payment processing failed after retries for order: {}. Queuing for manual review.", orderId);
+
+ // In production:
+ // 1. Queue to payment retry service
+ // 2. Alert finance team
+ // 3. Update order status to PAYMENT_PENDING
+
+ return "PAYMENT_QUEUED_FOR_RETRY";
+ }
+
+ /**
+ * Update inventory with retry.
+ * Ensures inventory updates don't fail due to transient issues.
+ *
+ * @param productId Product ID
+ * @param quantity Quantity to update
+ * @return Update status
+ */
+ @Retry(name = "orderProcessing", fallbackMethod = "updateInventoryFallback")
+ public boolean updateInventoryWithRetry(Integer productId, Integer quantity) {
+ log.info("Updating inventory with retry - Product: {}, Quantity: {}", productId, quantity);
+
+ // Simulate inventory update that might have transient failures
+ if (Math.random() < 0.2) { // 20% chance of failure
+ throw new RuntimeException("Inventory service connection timeout");
+ }
+
+ log.info("Inventory updated successfully for product: {}", productId);
+ return true;
+ }
+
+ /**
+ * Fallback for inventory update.
+ * Queues update for eventual consistency.
+ */
+ public boolean updateInventoryFallback(Integer productId, Integer quantity, Exception exception) {
+ log.error("Inventory update failed after retries for product: {}. Queuing for async update.", productId);
+
+ // Queue for async processing to maintain eventual consistency
+ // In production: send to message queue for later processing
+
+ return false;
+ }
+}
diff --git a/order-service/src/main/java/com/selimhorri/app/resource/FeatureToggleController.java b/order-service/src/main/java/com/selimhorri/app/resource/FeatureToggleController.java
new file mode 100644
index 000000000..741b85344
--- /dev/null
+++ b/order-service/src/main/java/com/selimhorri/app/resource/FeatureToggleController.java
@@ -0,0 +1,196 @@
+package com.selimhorri.app.resource;
+
+import com.selimhorri.app.config.FeatureToggle;
+import com.selimhorri.app.config.FeatureToggleService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * REST Controller for managing feature toggles.
+ *
+ * Provides endpoints to:
+ * - View all feature toggles
+ * - Check if specific feature is enabled
+ * - Enable/disable features
+ * - Update rollout percentage
+ *
+ * This allows operations teams to control feature rollout without redeployment.
+ *
+ * @author ecommerce-team
+ * @version 1.0
+ */
+@RestController
+@RequestMapping("/actuator/features")
+@RequiredArgsConstructor
+@Slf4j
+public class FeatureToggleController {
+
+ private final FeatureToggleService featureToggleService;
+
+ /**
+ * Get all configured feature toggles
+ *
+ * GET /actuator/features
+ *
+ * @return Map of all feature toggles with their configuration
+ */
+ @GetMapping
+ public ResponseEntity