Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

README.md

Spring Cloud AWS SQS - Architectural Overview

What: This module integrates Amazon SQS with Spring applications, providing annotation-driven listeners (via @SqsListener and programmatic listener containers, backed by an asynchronous processing runtime.

Why: The SQS integration underwent a major redesign in Spring Cloud AWS 3.0 to address limitations in previous versions and build on AWS SDK v2’s async API, and it was announced as GA in 2023.

Who: This document is meant for maintainers, contributors, and readers who want to understand the module’s internal structure and design. It describes the module in two phases: an assembly phase at startup, and a runtime phase where messages are polled, processed, and acknowledged. It focuses on the high-level structure and provides shared terminology for discussing the module’s flows and components.

Two-Phase Architecture

The module is organized into two phases with different responsibilities:

  • Assembly phase: At startup, Spring detects @SqsListener annotations, creates listener endpoints, and wires MessageListenerContainer instances through a factory and registry. This is similar to patterns used by Spring for Apache Kafka and other Spring messaging projects.

  • Runtime phase: When containers start, they run an asynchronous pipeline that polls SQS, invokes the listener, and acknowledges messages. This pipeline builds on AWS SDK v2’s async API (SqsAsyncClient) and uses a composable component model, including adaptive backpressure controls. While reusing familiar Spring abstractions such as MessageListener, the async processing pipeline is a module-specific design introduced in Spring Cloud AWS 3.0.

This separation keeps startup wiring concerns independent from message processing concerns, and makes the runtime pipeline easier to reason about and evolve without changing the assembly flow.

Assembly Phase

The assembly phase wires listener containers at startup. The flow is:

  1. SqsListenerAnnotationBeanPostProcessor detects @SqsListener annotations during bean post-processing
  2. For each annotation, it creates an Endpoint describing the listener
  3. Endpoints are registered with the EndpointRegistrar
  4. The registrar delegates to SqsMessageListenerContainerFactory to create containers
  5. Containers are registered in the MessageListenerContainerRegistry, which manages their lifecycle
flowchart LR
	A["SqsListenerAnnotationBeanPostProcessor"] --> B["Endpoint\ncreated"]
	B --> C["EndpointRegistrar"]
	C --> D["SqsMessageListenerContainerFactory"]
	D --> E["MessageListenerContainer"]
	E --> F["MessageListenerContainerRegistry"]
	F --> G["Container lifecycle start\n(transition to runtime)"]
Loading

Runtime Phase

When the MessageListenerContainerRegistry starts its containers, each container assembles its processing pipeline and begins polling for messages. The ContainerComponentFactory creates the runtime components and wires them together.

Async execution model

SQS is inherently I/O-bound. Every poll is a network call to AWS, and every acknowledgement is a batch-delete call. A blocking model would tie up threads waiting on these responses.

The pipeline is built on SqsAsyncClient, where receiveMessage() and deleteMessageBatch() both return CompletableFuture. This keeps polling and acknowledgement non-blocking and makes concurrency primarily a matter of configured in-flight capacity rather than thread availability.

Composable Pipeline

The runtime is structured as a composable pipeline with four stages — ingress, dispatch, processing, and acknowledgement — each mapped to dedicated components within each listener container:

These components are assembled at container start and interact through interfaces, which makes it possible to swap or extend individual stages.

flowchart LR
    A["MessageSource\n(polls SQS)"] --> B["MessageSink"]
    A --> BP["BackPressureHandler"]
    B --> P
    subgraph P ["MessageProcessingPipeline"]
        direction LR
        P1["Interceptor\n(before)"] --> P2["Listener"]
        P2 --> P3["ErrorHandler"]
        P3 --> P4["Interceptor\n(after)"]
        P4 --> P5["AcknowledgementHandler"]
    end
    P --> D["AcknowledgementProcessor\n(deletes from SQS)"]
    D --> E["AcknowledgementResultCallback"]
Loading