Back to The Vault

Architecting Event-Driven Microservices: The AuraFlow Saga Pattern

Designing resilient event-driven systems using the Saga pattern with AWS SQS to handle 40,000 events/min with guaranteed delivery.

Cite this page
Kapoor, Saksham. "Architecting Event-Driven Microservices: The AuraFlow Saga Pattern." The Vault (blog). August 10, 2025. https://saksham-kapoor.vercel.app/vault/architecting-event-driven-microservices
August 10, 2025Updated September 15, 20255 min read

System Overview

AuraFlow is a cloud-native event-driven microservices architecture built on AWS, processing 40,000 events/min with guaranteed delivery and eventual consistency. This post details the architectural decisions, trade-offs, and implementation patterns that enable high-throughput, fault-tolerant event processing.

System Architecture

This microservices architecture leverages AWS cloud services for scalability and reliability. The React frontend communicates with Spring Boot microservices through an API Gateway. Asynchronous processing is handled via SQS queues, ensuring fault tolerance and horizontal scalability.

Architecture Principles

Constraint: Event throughput
Impact: Drives partitioning and scaling strategy
  • Target: 40,000 events/min sustained
  • Peak: 100,000 events/min (2.5x burst capacity)
  • Must maintain sub-100ms p95 latency per event
Trade-off: Consistency vs Availability
Decision: Choose eventual consistency with Saga pattern for better availability
  • Strong consistency: Simpler logic, but higher latency and lower availability
  • Eventual consistency: Better performance and fault tolerance, requires distributed transaction management

The Saga Pattern

The Saga pattern manages distributed transactions across multiple microservices without distributed locks. Each saga consists of a sequence of local transactions, with compensating actions for rollback.

Trade-off: Saga Pattern vs Two-Phase Commit (2PC)
Decision: Saga pattern chosen for availability and performance at scale
  • Two-Phase Commit: Strong consistency guarantees, but requires distributed locks, introduces coordinator single point of failure, and doesn't scale beyond ~10 services
  • Saga Pattern: Eventual consistency with compensating transactions, no distributed locks, scales horizontally, but requires careful design of rollback logic
  • Decision: For 40k events/min with 5 microservices, Saga pattern provides the reliability and performance needed without the complexity overhead of 2PC

Saga Orchestration

@Service
public class AuraFlowSagaOrchestrator {
    
    @Autowired
    private SqsEventPublisher eventPublisher;
    
    public void executeSaga(SagaContext context) {
        List<SagaStep> steps = context.getSteps();
        
        for (int i = 0; i < steps.size(); i++) {
            SagaStep step = steps.get(i);
            
            try {
                // Execute step
                SagaResult result = executeStep(step, context);
                
                // Publish success event
                eventPublisher.publish(
                    new SagaStepCompletedEvent(step.getId(), result)
                );
                
            } catch (Exception e) {
                // Compensate previous steps
                compensateSteps(steps, i - 1);
                throw new SagaExecutionException("Saga failed at step: " + step.getId(), e);
            }
        }
    }
    
    private void compensateSteps(List<SagaStep> steps, int lastCompletedIndex) {
        for (int i = lastCompletedIndex; i >= 0; i--) {
            SagaStep step = steps.get(i);
            step.getCompensatingAction().execute();
        }
    }
}

AWS SQS Integration

Queue Architecture

  • Primary Queue: Standard SQS queue for high-throughput ingestion
  • Dead Letter Queue (DLQ): Failed events after 3 retries
  • FIFO Queues: For order-sensitive event streams (e.g., user state changes)

Event Publishing

@Component
public class SqsEventPublisher {
    
    @Autowired
    private AmazonSQS sqsClient;
    
    private static final String PRIMARY_QUEUE_URL = 
        "https://sqs.us-east-1.amazonaws.com/123456789/auraflow-events";
    
    public void publish(Event event) {
        SendMessageRequest request = new SendMessageRequest()
            .withQueueUrl(PRIMARY_QUEUE_URL)
            .withMessageBody(serializeEvent(event))
            .withMessageAttributes(buildAttributes(event));
        
        // Retry with exponential backoff
        int maxRetries = 3;
        for (int attempt = 0; attempt < maxRetries; attempt++) {
            try {
                sqsClient.sendMessage(request);
                return;
            } catch (Exception e) {
                if (attempt == maxRetries - 1) {
                    // Send to DLQ
                    sendToDeadLetterQueue(event, e);
                    throw new EventPublishException("Failed to publish event", e);
                }
                Thread.sleep((long) Math.pow(2, attempt) * 1000);
            }
        }
    }
}

Microservices Architecture

Service Decomposition

  1. Event Ingestion Service: Receives events from external sources, validates, and publishes to SQS
  2. Processing Service: Consumes events, applies business logic, publishes downstream events
  3. State Management Service: Maintains distributed state with eventual consistency
  4. Notification Service: Sends notifications based on event patterns

Spring Boot Implementation

@SpringBootApplication
@EnableSqs
public class AuraFlowApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuraFlowApplication.class, args);
    }
}

@Service
public class EventProcessingService {
    
    @SqsListener(value = "${sqs.queue.name}", deletionPolicy = ON_SUCCESS)
    public void processEvent(String message) {
        Event event = deserializeEvent(message);
        
        // Process event
        EventResult result = processBusinessLogic(event);
        
        // Publish downstream events
        if (result.requiresDownstreamEvents()) {
            eventPublisher.publish(result.getDownstreamEvents());
        }
    }
    
    private EventResult processBusinessLogic(Event event) {
        // Apply business rules
        // Update local state
        // Generate downstream events
        return new EventResult(/* ... */);
    }
}

Scaling Strategy

Horizontal Scaling

  • Auto Scaling Groups: Scale based on SQS queue depth
  • Target Metric: Maintain queue depth under 1000 messages
  • Scaling Policy: Add 2 instances when queue depth exceeds 1000, remove 1 when under 100

Partitioning

  • Event Partitioning: Partition events by tenantId or eventType for parallel processing
  • Database Sharding: Shard by tenantId to distribute load

Monitoring & Observability

Key Metrics

  • Event Throughput: Events processed per second
  • Queue Depth: Messages waiting in SQS
  • Processing Latency: p50, p95, p99 latencies
  • Error Rate: Failed events / total events
  • Saga Completion Rate: Successful sagas / total sagas

CloudWatch Integration

@Component
public class MetricsCollector {
    
    @Autowired
    private CloudWatchClient cloudWatch;
    
    public void recordEventProcessed(String eventType, long duration) {
        cloudWatch.putMetricData(PutMetricDataRequest.builder()
            .namespace("AuraFlow")
            .metricData(MetricDatum.builder()
                .metricName("EventProcessed")
                .value(1.0)
                .dimensions(
                    Dimension.builder().name("EventType").value(eventType).build()
                )
                .build())
            .build());
    }
}

Lessons Learned

  1. Idempotency is Critical: Design all event handlers to be idempotent to handle duplicate deliveries
  2. Compensating Actions: Always design compensating actions for saga steps—they will be needed
  3. Queue Visibility Timeout: Set appropriately to prevent duplicate processing while allowing retries
  4. Dead Letter Queues: Essential for debugging and manual recovery of failed events

Performance Results

  • Throughput: Sustained 40,000 events/min with 99.9% uptime
  • Latency: p95 under 100ms, p99 under 500ms
  • Reliability: 99.95% event delivery guarantee
  • Scalability: Linear scaling from 1 to 50+ instances

Related Posts

Press +K to search