Este guia mostra o passo a passo para criar um novo consumer que processa mensagens do SQS.
Exemplo prático: processar o evento de cancelamento de assinatura (subscription-canceled).
usecase/in/subscription/
└── CanceledSubscriptionUseCase.java ← 1. Porta de entrada
application/model/domain/subscription/
└── CancelSubscriptionDomain.java ← 2. Domain record
application/service/subscription/
└── CanceledSubscriptionService.java ← 3. Service (regra de negócio)
adapter/in/consumer/subscription/
└── CanceledSubscriptionConsumer.java ← 4. Consumer SQS
application/exception/
└── SubscriptionNotFoundException.java ← 5. Exception (se necessário)
Define o contrato do que a aplicação vai fazer com o evento.
// usecase/in/subscription/CanceledSubscriptionUseCase.java
package br.com.jusfy.usecase.in.subscription;
public interface CanceledSubscriptionUseCase {
void processEvent(String message);
}Representa os dados que chegam no payload do evento SQS. Os campos devem ter exatamente os mesmos nomes que chegam no JSON.
// application/model/domain/subscription/CancelSubscriptionDomain.java
package br.com.jusfy.application.model.domain.subscription;
import java.util.UUID;
public record CancelSubscriptionDomain(
UUID subscriptionId,
String reason,
String origin
) {}Atenção: o Gson faz a desserialização pelo nome exato do campo. Se o evento envia
subscriptionId, o record deve tersubscriptionId.
Implementa a interface criada no Passo 1. Contém a regra de negócio.
// application/service/subscription/CanceledSubscriptionService.java
package br.com.jusfy.application.service.subscription;
import br.com.jusfy.application.exception.SubscriptionNotFoundException;
import br.com.jusfy.application.model.domain.subscription.CancelSubscriptionDomain;
import br.com.jusfy.application.model.entity.Subscription;
import br.com.jusfy.application.model.enumeration.SubscriptionStatusEnum;
import br.com.jusfy.application.service.utils.EventEnvelopeUtils;
import br.com.jusfy.usecase.in.subscription.CanceledSubscriptionUseCase;
import br.com.jusfy.usecase.out.subscription.SubscriptionRepositoryUseCase;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
@RequiredArgsConstructor
@Slf4j
public class CanceledSubscriptionService implements CanceledSubscriptionUseCase {
private final SubscriptionRepositoryUseCase subscriptionRepositoryUseCase;
private final EventEnvelopeUtils eventEnvelopeUtils;
@Override
public void processEvent(String message) {
log.info("START PROCESSING SUBSCRIPTION CANCELED EVENT");
CancelSubscriptionDomain domain = eventEnvelopeUtils
.extractAndDeserialize(message, CancelSubscriptionDomain.class);
Subscription subscription = subscriptionRepositoryUseCase
.findById(domain.subscriptionId())
.orElseThrow(() -> new SubscriptionNotFoundException(
"Subscription not found: " + domain.subscriptionId()
));
subscription.setStatus(SubscriptionStatusEnum.CANCELED);
subscription.setCancelledAt(LocalDateTime.now());
subscription.setCancellationReason(domain.reason());
subscription.setCancellationOrigin(domain.origin());
subscription.setUpdatedAt(LocalDateTime.now());
subscriptionRepositoryUseCase.save(subscription);
log.info("SUBSCRIPTION CANCELED SUCCESSFULLY: {}", domain.subscriptionId());
}
}Recebe a mensagem e delega para o use case. Sem lógica aqui.
// adapter/in/consumer/subscription/CanceledSubscriptionConsumer.java
package br.com.jusfy.adapter.in.consumer.subscription;
import br.com.jusfy.usecase.in.subscription.CanceledSubscriptionUseCase;
import io.awspring.cloud.sqs.annotation.SqsListener;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
@Slf4j
public class CanceledSubscriptionConsumer {
private final CanceledSubscriptionUseCase canceledSubscriptionUseCase;
@SqsListener("${sqs.subscription.canceled.event}")
public void consume(String message) {
canceledSubscriptionUseCase.processEvent(message);
}
}# application.properties
sqs.subscription.canceled.event=${SUBSCRIPTION_CANCELED_EVENT}
# application-local.properties
sqs.subscription.canceled.event=local-jussub-subscription-canceledSe o evento pode falhar por uma regra de negócio, crie uma exceção específica
em application/exception/ extendendo BusinessException:
// application/exception/SubscriptionNotFoundException.java
package br.com.jusfy.application.exception;
public class SubscriptionNotFoundException extends BusinessException {
public SubscriptionNotFoundException(String message) {
super(message);
}
}E mapeie no ApiExceptionHandler caso precise retornar um HTTP específico para APIs REST.
Veja mais em ARCHITECTURE.md.
aws sqs send-message \
--queue-url https://sqs.us-east-2.amazonaws.com/944279429428/dev-jussub-subscription-canceled \
--message-body '{
"subscriptionId": "550e8400-e29b-41d4-a716-446655440000",
"reason": "Cliente solicitou cancelamento",
"origin": "CUSTOMER"
}' \
--region us-east-2- Interface em
usecase/in/{dominio}/ - Domain record em
application/model/domain/{dominio}/ - Service em
application/service/{dominio}/implementando a interface - Consumer em
adapter/in/consumer/{dominio}/com@SqsListener - Property da fila em
application.propertieseapplication-local.properties - Exception em
application/exception/(se necessário) - Testes unitários do service