Skip to content

Latest commit

 

History

History
211 lines (152 loc) · 6.34 KB

File metadata and controls

211 lines (152 loc) · 6.34 KB

Guia — Criando um Novo Consumer de Evento SQS

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).


Visão Geral dos Arquivos a Criar

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)

Passo 1 — Criar a interface usecase/in

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);
}

Passo 2 — Criar o Domain Record

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 ter subscriptionId.


Passo 3 — Criar o Service

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());
    }
}

Passo 4 — Criar o Consumer SQS

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);
    }
}

Passo 5 — Registrar a fila no application.properties

# application.properties
sqs.subscription.canceled.event=${SUBSCRIPTION_CANCELED_EVENT}

# application-local.properties
sqs.subscription.canceled.event=local-jussub-subscription-canceled

Passo 6 — Criar a Exception (se necessário)

Se 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.


Passo 7 — Testar com AWS CLI

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

Checklist

  • 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.properties e application-local.properties
  • Exception em application/exception/ (se necessário)
  • Testes unitários do service