AWS na Prática · 05 — SNS & SQS

Comunicação assíncrona resiliente entre os microserviços do projeto.

16 min de leitura

Comunicação assíncrona resiliente entre os microserviços do projeto.

Sobre este módulo

Aqui o sistema deixa de ser três serviços isolados e vira um sistema distribuído de verdade. Você vai implementar o fluxo Orders → Inventory via eventos, com filas, dead letter queues e idempotência — os blocos fundamentais.

Nível: Intermediário · Duração estimada: 3-4 horas


Sumário

  1. Por que mensageria?
  2. SNS: pub/sub na AWS
  3. SQS: filas gerenciadas
  4. O padrão SNS + SQS (fan-out)
  5. Standard vs FIFO
  6. Dead Letter Queues
  7. Idempotência na prática
  8. Implementando o fluxo Orders → Inventory
  9. Templates CloudFormation
  10. Monitoramento e debugging
  11. Exercícios de fixação
  12. Próximos passos

01. Por que mensageria?

Até aqui, nossos serviços não conversam entre si. Vamos resolver isso. Mas antes de implementar, é importante entender o porquê de usar mensageria em vez de simples chamadas HTTP entre serviços.

O problema do acoplamento síncrono

Imagine que Orders chama Inventory diretamente via HTTP toda vez que um pedido é criado. O que acontece se Inventory estiver fora do ar? O pedido falha — mesmo que a falta seja num serviço relativamente secundário (decrementar estoque pode ser feito depois).

Esse é o acoplamento temporal: ambos os serviços precisam estar ativos no mesmo momento. Um problema em um derruba o outro.

O modelo de mensageria

Em vez de chamar diretamente, Orders publica um evento em algum lugar (SNS topic). O sistema de mensageria armazena o evento e entrega para quem estiver escutando (SQS queue do Inventory).

Se Inventory estiver fora? Sem problema — a mensagem fica na fila. Quando Inventory voltar, processa as pendentes. Orders nem sabe que houve indisponibilidade.

Os ganhos concretos

  • Resiliência — falhas em um serviço não derrubam outros.
  • Picos absorvidos — a fila atua como buffer entre produtor e consumidor.
  • Escala independente — produtor e consumidor escalam separadamente.
  • Acoplamento mínimo — produtor não precisa conhecer consumidores.
  • Adicionar consumidores é trivial — basta inscrever uma nova fila no topic.

Os custos

  • Eventual consistency — há atraso entre evento publicado e processado.
  • Debugging mais complexo — precisa rastrear mensagens entre sistemas.
  • Ordem não garantida (em Standard) — eventos podem chegar fora de ordem.
  • Duplicação possível — mesma mensagem pode ser entregue mais de uma vez.

02. SNS: pub/sub na AWS

SNS (Simple Notification Service) é o serviço de pub/sub da AWS. Produtores publicam mensagens em um topic; subscribers recebem cópias.

Conceitos centrais

  • Topic — ponto de comunicação. Tem ARN, nome, e zero ou mais subscribers.
  • Publisher — qualquer entidade (serviço, Lambda, CLI) que publica em um topic.
  • Subscriber — endpoint que recebe as mensagens publicadas. Pode ser SQS, Lambda, e-mail, HTTP, SMS.
  • Subscription — a ligação entre topic e subscriber. Pode ter filtros.

Tipos de subscriber

TipoCaso de uso
SQSMensagens persistentes, processamento async controlado
LambdaDisparar função sem fila intermediária
E-mailAlertas para humanos
SMSNotificações urgentes
HTTP/HTTPSWebhooks para sistemas externos
Mobile pushNotifications iOS/Android

Características importantes

  • Fan-out: uma mensagem publicada no topic vai para todos os subscribers.
  • Push-based: SNS empurra para os subscribers, não há polling.
  • Best effort em Standard: ordem não garantida, duplicação possível.
  • FIFO disponível: ordem e exatamente-uma-vez (com filtros mais limitados).

Filtros de assinatura

Você pode configurar um filter policy em uma subscription para que ela só receba mensagens que atendam certos critérios. Por exemplo, uma fila pode querer só eventos de pedidos com valor maior que R$ 1000:

{
  "totalAmount": [{"numeric": [">", 1000]}],
  "country": ["BR", "US"]
}

O publisher passa atributos ao publicar; SNS aplica os filtros antes de entregar. Reduz tráfego, simplifica consumidores, evita filas com mensagens irrelevantes.

03. SQS: filas gerenciadas

SQS (Simple Queue Service) é a fila gerenciada da AWS. Mensagens são guardadas até serem processadas — é o mecanismo de buffer e desacoplamento.

Como funciona, em alto nível

  1. Produtor envia mensagem para a fila (SendMessage).
  2. SQS armazena a mensagem com redundância em múltiplas AZs.
  3. Consumidor faz polling: ReceiveMessage retorna até 10 mensagens.
  4. Mensagem fica invisível por um período (visibility timeout) para outros consumidores.
  5. Consumidor processa e chama DeleteMessage para confirmar o sucesso.
  6. Se não deletar dentro do timeout, mensagem volta para a fila e pode ser reprocessada.

Conceitos importantes

  • Visibility timeout — tempo que uma mensagem fica invisível depois de recebida. Default: 30s. Configurável até 12h.
  • Message retention — tempo que uma mensagem fica na fila se não for processada. Default: 4 dias. Máx: 14 dias.
  • Long polling — consumidor espera até 20s por novas mensagens. Reduz custo e latência.
  • Batch operations — send/receive/delete de até 10 mensagens em uma chamada. Mais eficiente.

Limites importantes

LimiteValor
Tamanho máximo da mensagem256 KB
Mensagens em flight (Standard)120.000
Mensagens em flight (FIFO)20.000
Throughput StandardQuase ilimitado
Throughput FIFO300/s ou 3000/s com batching
Tempo máximo de retenção14 dias

Para mensagens maiores que 256 KB, use o padrão claim check: salve o payload no S3 e envie o ARN/URL na fila. O consumidor busca no S3.

04. O padrão SNS + SQS (fan-out)

Combinar SNS com SQS é o padrão mais comum em arquitetura event-driven na AWS. Vale entender por que essa combinação é melhor que cada um sozinho.

Por que não SNS direto para Lambda/serviço?

Você até pode. Mas há dois problemas:

  • Sem buffer — se o consumidor está fora, a mensagem se perde após algumas tentativas.
  • Sem retry policy customizada — SNS faz retries, mas com regras fixas.
  • Sem replay — não dá para reprocessar mensagens antigas se algo der errado.

Por que não SQS direto?

SQS sozinho funciona se você tem um único consumidor. Mas se múltiplos serviços precisam reagir ao mesmo evento, você teria que escrever em múltiplas filas — o produtor passa a conhecer todos os consumidores. Acoplamento de novo.

A solução: SNS + SQS combinados

Produtor publica em um SNS topic. Cada consumidor cria sua própria SQS queue e a inscreve no topic. SNS faz fan-out, SQS armazena com garantias.

Renderizando diagrama…

Figura 1 — Fan-out: produtor publica uma vez, múltiplos consumidores recebem.

Vantagens do padrão

  • Desacoplamento total — produtor não sabe quem consome.
  • Cada consumidor tem sua fila — pode falhar sem afetar outros.
  • Adicionar novo consumidor é trivial — apenas crie nova fila e inscreva.
  • Buffer por consumidor — cada um processa no seu ritmo.
  • Replay possível — mensagens antigas podem ser reprocessadas (até a retenção).

05. Standard vs FIFO

SNS e SQS oferecem dois tipos: Standard e FIFO. Saber qual usar afeta performance, custo e correção do sistema.

CaracterísticaStandardFIFO
OrdemBest effortGarantida
EntregaAt least onceExactly once
ThroughputQuase ilimitado300/s (3000 com batch)
CustoMais baratoMais caro
Filtros SNSCompletosLimitados

Quando usar Standard

  • Eventos independentes — ordem não importa.
  • Alto volume — milhares de mensagens por segundo.
  • Idempotência implementada do lado do consumidor — duplicação não é problema.
  • Maioria absoluta dos casos — você só vai precisar de FIFO em situações específicas.

Quando usar FIFO

  • Ordem é crítica — evento B precisa ser processado depois do A.
  • Sem idempotência e duplicação é inaceitável.
  • Volume baixo a moderado.
  • Casos comuns: workflow sequenciais, processamento financeiro, sequências de comando.

MessageGroupId em FIFO

Em FIFO, você precisa especificar um MessageGroupId em cada mensagem. A ordem é garantida dentro de um mesmo grupo, mas grupos diferentes podem ser processados em paralelo. Use customerId ou orderId como group ID — eventos do mesmo cliente/pedido em ordem, mas paralelismo entre clientes.

06. Dead Letter Queues

Mensagens que falham repetidamente precisam ir para algum lugar — não podem ficar travando a fila principal nem sumir. DLQ é a solução.

O problema do poison message

Imagine uma mensagem malformada que faz seu consumidor crashar. Sem DLQ:

  • Consumidor pega a mensagem, processa, falha.
  • Visibility timeout vence; mensagem volta para a fila.
  • Outro consumidor (ou o mesmo) pega de novo, falha de novo.
  • Loop infinito — fila trava processando lixo.

Como DLQ resolve

Você configura um limite de tentativas (maxReceiveCount). Quando uma mensagem é recebida mais vezes que isso sem ser deletada, ela é movida automaticamente para uma fila secundária — a Dead Letter Queue.

A fila principal volta ao normal. Você inspeciona a DLQ depois, identifica o problema, conserta o consumidor (ou os dados), e pode reprocessar.

Configuração

# Atributo da fila principal:
RedrivePolicy:
  deadLetterTargetArn: "arn:aws:sqs:...:inventory-dlq"
  maxReceiveCount: 5

maxReceiveCount = 5 significa: depois de 5 tentativas frustradas, a mensagem vai para a DLQ. Esse número é uma escolha sua — 3 é mais agressivo, 10 é mais tolerante.

Boas práticas com DLQ

  • Sempre crie DLQ para filas de produção. Sem ela, você tem um buraco operacional.
  • Configure alarme no CloudWatch para a métrica ApproximateNumberOfMessagesVisible da DLQ.
  • Retenção mais longa na DLQ — 14 dias (máximo), para você ter tempo de investigar.
  • Processo de re-drive — defina quando e como mensagens da DLQ são reprocessadas.

07. Idempotência na prática

Como SQS Standard entrega at least once, sua mensagem pode ser processada mais de uma vez. Se isso causa duplicação de dados ou efeitos colaterais errados, você tem um problema sério. Idempotência resolve.

Definição rigorosa

Uma operação é idempotente se executá-la N vezes tem o mesmo efeito que executá-la uma vez. Em mensageria, isso significa: receber a mesma mensagem 1x ou 10x deve gerar o mesmo estado final.

Estratégias de implementação

1. Operações naturalmente idempotentes

Algumas operações já são idempotentes: SetUserEmail("foo@bar.com") pode ser chamado N vezes — o estado final é o mesmo. Sempre que possível, modele para isso.

2. ID de evento + tabela de processados

Cada evento tem um eventId único (UUID). Antes de processar, o consumidor verifica em uma tabela se aquele eventId já foi processado. Se sim, descarta.

// Pseudocódigo (apenas exemplo conceitual):
async function processOrderCreated(event) {
  const exists = await db.checkProcessed(event.eventId);
  if (exists) return;  // já processado, descarta
 
  await db.transactWrite([
    decrementInventory(event.productId, event.quantity),
    markProcessed(event.eventId)
  ]);
}

3. Conditional writes

DynamoDB suporta ConditionExpression: a operação só executa se a condição for verdadeira. Use para evitar dupla escrita.

08. Implementando o fluxo Orders → Inventory

Vamos colocar tudo em prática. Quando um pedido é criado, Orders publica order.created em um topic SNS. Inventory escuta via uma fila SQS e decrementa o estoque.

Recursos a criar

  • SNS Topic: order-events
  • SQS Queue: inventory-orders-queue
  • SQS Queue (DLQ): inventory-orders-dlq
  • Subscription: order-events → inventory-orders-queue
  • Policies IAM: Orders pode publicar; Inventory pode consumir.

Schema do evento

{
  "eventId": "550e8400-e29b-41d4-a716-446655440000",
  "eventType": "order.created",
  "timestamp": "2026-01-15T10:30:00Z",
  "version": "1.0",
  "data": {
    "orderId": "ord-123",
    "customerId": "cust-456",
    "items": [
      {"productId": "prod-789", "quantity": 2, "price": 29.90}
    ],
    "totalAmount": 59.80
  }
}

Lado do produtor (Orders)

No Orders Service, depois de salvar o pedido no DynamoDB, publique no SNS:

// Apenas estrutura conceitual, não código pronto
import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';
 
const sns = new SNSClient({ region: process.env.AWS_REGION });
 
await sns.send(new PublishCommand({
  TopicArn: process.env.SNS_ORDER_EVENTS_TOPIC,
  Message: JSON.stringify(eventPayload),
  MessageAttributes: {
    eventType: {
      DataType: 'String',
      StringValue: 'order.created'
    }
  }
}));

Lado do consumidor (Inventory)

No Inventory Service, faça polling na fila e processe:

// Apenas estrutura conceitual
import { SQSClient, ReceiveMessageCommand, 
         DeleteMessageCommand } from '@aws-sdk/client-sqs';
 
const sqs = new SQSClient({ region: process.env.AWS_REGION });
 
// Loop de processamento (simplificado)
while (true) {
  const result = await sqs.send(new ReceiveMessageCommand({
    QueueUrl: process.env.SQS_INVENTORY_QUEUE_URL,
    MaxNumberOfMessages: 10,
    WaitTimeSeconds: 20  // long polling
  }));
 
  for (const message of result.Messages ?? []) {
    await processMessageIdempotent(message);
    await sqs.send(new DeleteMessageCommand({
      QueueUrl: process.env.SQS_INVENTORY_QUEUE_URL,
      ReceiptHandle: message.ReceiptHandle
    }));
  }
}

Importante: a mensagem que chega via SNS+SQS vem com um wrapper. O JSON real do evento está em JSON.parse(message.Body).Message.

09. Templates CloudFormation

Toda essa infraestrutura mensageria precisa estar em CloudFormation, consistente com o que aprendemos no Módulo 04.

Template: messaging.yaml (parte 1 — SNS e DLQ)

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Mensageria entre Orders e Inventory'
 
Resources:
  OrderEventsTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: order-events
      DisplayName: 'Eventos do domínio Orders'
 
  InventoryOrdersDLQ:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: inventory-orders-dlq
      MessageRetentionPeriod: 1209600  # 14 dias

Template: messaging.yaml (parte 2 — fila principal)

  InventoryOrdersQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: inventory-orders-queue
      VisibilityTimeout: 60
      MessageRetentionPeriod: 345600  # 4 dias
      RedrivePolicy:
        deadLetterTargetArn: !GetAtt InventoryOrdersDLQ.Arn
        maxReceiveCount: 5
 
  InventoryOrdersSubscription:
    Type: AWS::SNS::Subscription
    Properties:
      TopicArn: !Ref OrderEventsTopic
      Protocol: sqs
      Endpoint: !GetAtt InventoryOrdersQueue.Arn
      RawMessageDelivery: true
      FilterPolicy:
        eventType: ['order.created', 'order.cancelled']

Permissão: SNS pode escrever na SQS

  QueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Properties:
      Queues:
        - !Ref InventoryOrdersQueue
      PolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: sns.amazonaws.com
            Action: sqs:SendMessage
            Resource: !GetAtt InventoryOrdersQueue.Arn
            Condition:
              ArnEquals:
                aws:SourceArn: !Ref OrderEventsTopic

Outputs

Outputs:
  TopicArn:
    Value: !Ref OrderEventsTopic
    Export:
      Name: !Sub '${AWS::StackName}-TopicArn'
 
  QueueUrl:
    Value: !Ref InventoryOrdersQueue
    Export:
      Name: !Sub '${AWS::StackName}-QueueUrl'

10. Monitoramento e debugging

Em mensageria, monitoramento é mais importante do que em código síncrono — problemas se manifestam silenciosamente, como filas crescendo.

Métricas críticas

MétricaO que indica
ApproximateNumberOfMessagesVisibleMensagens disponíveis para receber
ApproximateNumberOfMessagesNotVisibleEm processamento
NumberOfMessagesSentThroughput de produção
NumberOfMessagesReceivedThroughput de consumo
ApproximateAgeOfOldestMessageMensagem mais antiga (lag)

Alarmes essenciais

  • DLQ não vazia — se há mensagens na DLQ, há bug em algum lugar.
  • Idade da mensagem mais antiga > threshold — consumidor lento ou parado.
  • Fila crescendo — produção mais rápida que consumo.
  • Erros de receive/delete — problemas de permissão ou config.

Inspecionando mensagens

# Pega 1 mensagem sem deletar (peek)
aws sqs receive-message \
  --profile aws-curso \
  --queue-url <queue-url> \
  --max-number-of-messages 1 \
  --visibility-timeout 0
 
# Conta mensagens visíveis
aws sqs get-queue-attributes \
  --profile aws-curso \
  --queue-url <queue-url> \
  --attribute-names ApproximateNumberOfMessagesVisible

Reprocessando da DLQ

Há um recurso chamado Dead-letter queue redrive: você seleciona a DLQ no console e clica em Start DLQ redrive — as mensagens voltam para a fila original automaticamente. Use depois de corrigir o consumidor!

11. Exercícios de fixação

Exercício
01

Suba o messaging stack

Escreva o template messaging.yaml da seção 9 e suba via CloudFormation. Confirme: o topic foi criado, as duas filas (principal + DLQ), a subscription, e a queue policy. Anote os ARNs/URLs nos Outputs.

Exercício
02

Modifique o Orders Service

Adicione no Orders Service: depois de salvar o pedido no DynamoDB, publique o evento order.created no topic. Use a env var SNS_ORDER_EVENTS_TOPIC. Faça deploy. Crie um pedido e verifique (via console SNS > topic > metrics) que a mensagem foi publicada.

Exercício
03

Implemente o consumidor

Modifique o Inventory Service para fazer polling na fila inventory-orders-queue. Para cada evento, decremente o estoque dos produtos. Implemente idempotência usando uma tabela ProcessedEvents no DynamoDB. Valide: criar pedido em Orders deve resultar em estoque decrementado em Inventory.

Exercício
04

Provoque o uso da DLQ

Mande uma mensagem malformada para a fila (body inválido). Configure seu consumidor para crashar com mensagens malformadas. Observe: depois de 5 tentativas, a mensagem aparece na DLQ. Verifique o conteúdo, conserte o consumidor para descartar mensagens malformadas em vez de crashar.

Exercício
05

Reflexão sobre eventual consistency

Em texto livre (10-15 linhas): durante o processamento async do evento, o estoque está temporariamente desatualizado. Em que cenários isso seria inaceitável? Como você ajustaria a arquitetura nesses casos?

12. Próximos passos

Seu sistema agora é distribuído de verdade. Tem produtores, consumidores, filas, eventos. Próximo nível: deixar a tarefa de consumidores para Lambda — sem ter que manter EC2 fazendo polling para sempre.

O que você aprendeu

  • Por que mensageria é a base de sistemas distribuídos.
  • SNS para pub/sub e SQS para filas com garantias.
  • O padrão fan-out (SNS + SQS).
  • Standard vs FIFO e quando escolher cada um.
  • Dead Letter Queues e tratamento de poison messages.
  • Idempotência: estratégias e implementação.
  • Implementação completa do fluxo Orders → Inventory.
  • Templates CloudFormation para mensageria.
  • Monitoramento e debugging em filas.

O que vem no Módulo 06

Lambda e processamento event-driven. Vamos substituir o consumidor do Inventory (que hoje roda como serviço Beanstalk fazendo polling) por uma função Lambda que é invocada automaticamente quando há mensagens. Também vamos adicionar funções Lambda para envio de e-mails, geração de relatórios e processamento de imagens.

Mensagens não se perdem se você não permitir. Boa jornada — Módulo 06 a seguir.

Posts relacionados