Pular para o conteúdo principal

EventBus

Componente central para publicação e consumo de eventos de domínio.

Visão Geral

Aggregate Root
↓ (registerEvent)
EventBus.publish(event)

EventHandler.handle(event)

Side Effects (email, integrações, etc.)

Tipos de Eventos

TipoDescrição
DomainEventEventos de domínio
IntegrationEventEventos de integração externa
SystemEventEventos do sistema

Publicando Eventos

No Aggregate Root

@Entity
@DomainAggregateRoot
public class Pedido extends AggregateRoot<Pedido, UUID> {

public void confirmar() {
validarConfirmacao();

this.status = StatusPedido.CONFIRMADO;

// Publicar evento de domínio
registerEvent(new PedidoConfirmadoEvent(
this.getId(),
this.clienteId,
this.total,
LocalDateTime.now()
));
}
}

Publicação Manual

@Service
public class PedidoService {

private final EventBus eventBus;

public void confirmarPedido(UUID pedidoId) {
Pedido pedido = repository.findById(pedidoId)
.orElseThrow();

pedido.confirmar();

repository.save(pedido);

// Publicar eventos acumulados
pedido.getEvents().forEach(eventBus::publish);
pedido.clearEvents();
}

// Ou publicar diretamente
public void notificarCliente(Pedido pedido) {
eventBus.publish(new PedidoConfirmadoEvent(
pedido.getId(),
pedido.getClienteId(),
pedido.getTotal(),
LocalDateTime.now()
));
}
}

Criando Event Handler

package com.exemplo.application.handler;

import br.com.archbase.event.EventHandler;
import org.springframework.stereotype.Component;

@Component
public class PedidoEventHandler {

private final EmailService emailService;
private final EstoqueService estoqueService;

@EventHandler
public void on(PedidoConfirmadoEvent event) {
log.info("Pedido confirmado: {}", event.getPedidoId());

// Enviar email de confirmação
emailService.enviarConfirmacao(event.getPedidoId());

// Baixar estoque
estoqueService.baixarEstoque(event.getItens());
}

@EventHandler
public void on(PedidoCanceladoEvent event) {
log.info("Pedido cancelado: {}", event.getPedidoId());

// Devolver estoque
estoqueService.devolverEstoque(event.getItens());

// Notificar cliente
emailService.enviarNotificacaoCancelamento(event.getPedidoId());
}

@EventHandler
public void on(PedidoEntregueEvent event) {
log.info("Pedido entregue: {}", event.getPedidoId());

// Calcular métricas
metricsService.registrarEntrega(event.getPedidoId());

// Notificar sistemas externos
integracaoService.notificarEntrega(event);
}
}

Event Handler com Filtro

@Component
public class RelatorioEventHandler {

@EventHandler(condition = "#event.valorTotal > 1000")
public void onPedidoAltoValor(PedidoConfirmadoEvent event) {
relatorioService.registrarVendaAlta(event);
}

@EventHandler
@Scheduled(fixedDelay = 60000) // Processa em lote a cada minuto
public void processarEventosPendentes() {
// Processar eventos não enviados
}
}

Handler Scan

@Configuration
@HandlerScan(basePackages = {
"com.exemplo.application.handler",
"com.exemplo.infrastructure.handler"
})
public class EventConfig {
// Handlers são descobertos automaticamente
}

Eventos de Integração

public record PedidoCriadoIntegrationEvent(
UUID pedidoId,
UUID clienteId,
Money total,
LocalDateTime criadoEm
) implements IntegrationEvent {

@Override
public String getAggregateType() {
return "Pedido";
}

@Override
public String getEventType() {
return "PedidoCriado";
}
}

Publicando em Message Broker

@Component
public class EventPublisherMQTT {

private final MqttClient mqttClient;

@EventListener
public void handleIntegrationEvent(IntegrationEvent event) {
String topic = "events/" + event.getAggregateType() + "/" + event.getEventType();

try {
String payload = objectMapper.writeValueAsString(event);
MqttMessage message = new MqttMessage(payload.getBytes());
message.setQos(1);
mqttClient.publish(topic, message);
} catch (Exception e) {
log.error("Erro ao publicar evento", e);
}
}
}

Event Sourcing

@Service
public class EventSourcingService {

private final EventStore eventStore;

public void save(AggregateRoot aggregate) {
List<DomainEvent> events = aggregate.getUncommittedEvents();

// Salvar eventos no event store
eventStore.save(aggregate.getId(), events);

// Publicar eventos
events.forEach(eventBus::publish);

// Marcar eventos como commitados
aggregate.markEventsAsCommitted();
}

public <T extends AggregateRoot> T load(UUID id, Class<T> type) {
List<DomainEvent> events = eventStore.getEvents(id);

T aggregate = instantiate(type);
events.forEach(aggregate::applyEvent);

return aggregate;
}
}

Retry em Event Handlers

@Component
public class ResilientEventHandler {

private final ExternalService externalService;

@EventHandler
@RetryableTopic(attempts = "3", backoff = @Backoff(delay = 1000))
public void on(PedidoConfirmadoEvent event) {
externalService.notificar(event);
}

@Recover
public void recover(PedidoConfirmadoEvent event, Throwable ex) {
log.error("Falha ao processar evento após tentativas", ex);

// Salvar em DLQ (Dead Letter Queue)
deadLetterQueue.save(event, ex);
}
}

Saga Orchestration

@Component
public class PedidoSaga {

@EventHandler
public void on(PedidoConfirmadoEvent event) {
// Iniciar saga
SagaManager saga = new SagaManager(event.getPedidoId());

// Passo 1: Reservar estoque
saga.execute("reservar-estoque", () ->
estoqueService.reservar(event.getItens())
);

// Passo 2: Processar pagamento
saga.execute("processar-pagamento", () ->
pagamentoService.processar(event.getPedidoId(), event.getTotal())
);

// Passo 3: Notificar transportadora
saga.execute("notificar-transportadora", () ->
transportadoraService.agendarColeta(event.getPedidoId())
);
}

@EventHandler
public void on(PagamentoFalhouEvent event) {
// Compensação
sagaManager.compensate(event.getPedidoId());
estoqueService.liberar(event.getPedidoId());
}
}

Testando Event Handlers

@SpringBootTest
class PedidoEventHandlerTest {

@Autowired
private EventBus eventBus;

@MockBean
private EmailService emailService;

@Test
void deveEnviarEmailQuandoPedidoConfirmado() {
// Given
PedidoConfirmadoEvent event = new PedidoConfirmadoEvent(
pedidoId, clienteId, Money.reais(100), LocalDateTime.now()
);

// When
eventBus.publish(event);

// Then
await().atMost(Duration.ofSeconds(1))
.untilAsserted(() ->
verify(emailService).enviarConfirmacao(pedidoId)
);
}
}

Boas Práticas

PráticaDescrição
Eventos imutáveisUse record para eventos
Eventos assíncronosHandlers não devem bloquear
IdempotênciaHandlers devem ser idempotentes
VersioningVersione eventos para evolução
DLQUse Dead Letter Queue para falhas

Próximos Passos