EventBridge 아키텍처: 버스, 규칙, 대상

EventBridge는 세 가지 기본 개념으로 구성됩니다.

  • 이벤트 버스: 이벤트를 수신하는 채널입니다. 각 AWS 계정에는 기본 이벤트 버스 (EC2, S3, RDS와 같은 AWS 서비스로부터 이벤트 수신), 여러 사용자 정의 버스(맞춤형 이벤트 버스) 애플리케이션 이벤트의 경우 e 파트너 이벤트 버스 SaaS 통합용(Zendesk, Datadog, Salesforce).
  • 이벤트 규칙: 규칙 이벤트 패턴 (JSON 필터)를 결정합니다. 어떤 이벤트를 어떤 대상으로 라우팅할지. 단일 규칙에는 최대 5개의 대상이 있을 수 있습니다.
  • 목표: 이벤트의 대상입니다. Lambda, SQS, SNS, Step Functions를 지원합니다. API Gateway, Kinesis, 다른 계정의 EventBridge 버스 및 20개 이상의 기타 대상.

EventBridge 이벤트의 구조

{
  "version": "0",
  "id": "12345678-1234-1234-1234-123456789012",
  "source": "com.mioapp.ordini",
  "account": "123456789012",
  "time": "2026-03-20T10:30:00Z",
  "region": "eu-west-1",
  "detail-type": "OrdineEffettuato",
  "detail": {
    "ordineId": "ord-abc123",
    "clienteId": "cli-xyz789",
    "totale": 149.99,
    "stato": "IN_ATTESA_PAGAMENTO",
    "categoria": "ELETTRONICA"
  }
}

분야 detail 애플리케이션 페이로드를 포함합니다. source e detail-type 이벤트 유형을 식별합니다.

EventBridge에 이벤트 게시

// EventBridgePublisher.java - Pubblica eventi custom su EventBridge
import software.amazon.awssdk.services.eventbridge.EventBridgeClient;
import software.amazon.awssdk.services.eventbridge.model.*;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.*;

public class EventBridgePublisher {

    private final EventBridgeClient eventBridgeClient;
    private final ObjectMapper objectMapper;
    private static final String EVENT_BUS_NAME = "mioapp-production";

    public EventBridgePublisher() {
        this.eventBridgeClient = EventBridgeClient.builder()
            .region(Region.EU_WEST_1)
            .build();
        this.objectMapper = new ObjectMapper();
    }

    public void publishOrdineEffettuato(OrdineEffettuatoEvent event) throws Exception {
        String detailJson = objectMapper.writeValueAsString(event);

        PutEventsRequestEntry entry = PutEventsRequestEntry.builder()
            .source("com.mioapp.ordini")
            .detailType("OrdineEffettuato")
            .detail(detailJson)
            .eventBusName(EVENT_BUS_NAME)
            // TraceHeader per X-Ray distributed tracing (opzionale)
            .traceHeader("Root=1-63441c4a-abcdef")
            .build();

        PutEventsResponse response = eventBridgeClient.putEvents(
            PutEventsRequest.builder()
                .entries(entry)
                .build()
        );

        if (response.failedEntryCount() > 0) {
            response.entries().forEach(e -> {
                if (e.errorCode() != null) {
                    throw new EventPublishException(
                        "Errore pubblicazione evento: " + e.errorCode() + " - " + e.errorMessage()
                    );
                }
            });
        }
    }

    // Batch publish (max 10 eventi per chiamata, 256 KB totale)
    public void publishBatch(List<DomainEvent> events) throws Exception {
        List<PutEventsRequestEntry> entries = new ArrayList<>();

        for (DomainEvent event : events) {
            entries.add(PutEventsRequestEntry.builder()
                .source("com.mioapp." + event.getAggregateType())
                .detailType(event.getClass().getSimpleName())
                .detail(objectMapper.writeValueAsString(event))
                .eventBusName(EVENT_BUS_NAME)
                .build());
        }

        PutEventsResponse response = eventBridgeClient.putEvents(
            PutEventsRequest.builder()
                .entries(entries)
                .build()
        );

        System.out.printf("Pubblicati %d/%d eventi. Falliti: %d%n",
            entries.size() - response.failedEntryCount(),
            entries.size(),
            response.failedEntryCount());
    }
}

이벤트 패턴: 콘텐츠 기반 라우팅

EventBridge의 핵심은 콘텐츠 기반 라우팅: 규칙 필터 이벤트 유형뿐만 아니라 JSON 페이로드의 내용을 기반으로 하는 이벤트입니다. 패턴은 접두사, 접미사, 특정 값, 숫자 범위 및 음수 조건에 대한 일치를 지원합니다.

# Esempi di Event Pattern per il routing

# Pattern 1: Tutti gli ordini di importo alto (sopra 500 EUR)
{
  "source": ["com.mioapp.ordini"],
  "detail-type": ["OrdineEffettuato"],
  "detail": {
    "totale": [{ "numeric": [">", 500] }]
  }
}

# Pattern 2: Ordini con prodotti della categoria ELETTRONICA o GAMING
{
  "source": ["com.mioapp.ordini"],
  "detail-type": ["OrdineEffettuato"],
  "detail": {
    "categoria": ["ELETTRONICA", "GAMING"]
  }
}

# Pattern 3: Ordini di clienti premium con pagamento completato
{
  "source": ["com.mioapp.ordini"],
  "detail-type": ["OrdineEffettuato", "PagamentoConfermato"],
  "detail": {
    "tipoCliente": ["PREMIUM", "VIP"],
    "stato": [{ "prefix": "PAGA" }]
  }
}

# Pattern 4: Qualsiasi evento di errore da tutti i servizi dell'app
{
  "source": [{ "prefix": "com.mioapp." }],
  "detail-type": [{ "suffix": "Failed" }]
}

# Pattern 5: Esclusione (not): tutti gli ordini ECCETTO i test
{
  "source": ["com.mioapp.ordini"],
  "detail-type": ["OrdineEffettuato"],
  "detail": {
    "ambiente": [{ "anything-but": ["test", "staging"] }]
  }
}

CloudFormation/Terraform을 사용하여 규칙 생성

# Terraform: event bus, rule e target Lambda

# Custom Event Bus
resource "aws_cloudwatch_event_bus" "mioapp" {
  name = "mioapp-production"
}

# Rule: ordini ad alto valore verso Lambda di VIP handling
resource "aws_cloudwatch_event_rule" "ordini_vip" {
  name           = "ordini-vip-handler"
  event_bus_name = aws_cloudwatch_event_bus.mioapp.name

  event_pattern = jsonencode({
    source      = ["com.mioapp.ordini"]
    detail-type = ["OrdineEffettuato"]
    detail = {
      totale     = [{ numeric = [">", 500] }]
      tipoCliente = ["PREMIUM", "VIP"]
    }
  })

  description = "Instrada ordini VIP alto valore alla Lambda dedicata"
}

# Target: Lambda VIP handler
resource "aws_cloudwatch_event_target" "ordini_vip_lambda" {
  rule           = aws_cloudwatch_event_rule.ordini_vip.name
  event_bus_name = aws_cloudwatch_event_bus.mioapp.name
  arn            = aws_lambda_function.vip_handler.arn

  # Retry policy per il target
  retry_policy {
    maximum_event_age_in_seconds = 3600  # 1 ora
    maximum_retry_attempts       = 3
  }

  # Dead letter queue per gli eventi che non vengono consegnati
  dead_letter_config {
    arn = aws_sqs_queue.eventbridge_dlq.arn
  }
}

# Rule: tutti gli errori verso SQS per analisi
resource "aws_cloudwatch_event_rule" "tutti_errori" {
  name           = "tutti-errori-sqs"
  event_bus_name = aws_cloudwatch_event_bus.mioapp.name

  event_pattern = jsonencode({
    source      = [{ prefix = "com.mioapp." }]
    detail-type = [{ suffix = "Failed" }]
  })
}

resource "aws_cloudwatch_event_target" "errori_sqs" {
  rule           = aws_cloudwatch_event_rule.tutti_errori.name
  event_bus_name = aws_cloudwatch_event_bus.mioapp.name
  arn            = aws_sqs_queue.errori_queue.arn
}

스키마 레지스트리 및 스키마 검색

EventBridge에는 다음이 포함됩니다. 스키마 레지스트리 내장: 자동으로 검색 가능 버스에서 일어나는 사건의 패턴(검색 스키마) 정의를 유지하고 검증 및 코드 생성을 위한 스키마.

가장 큰 장점은 코드 생성: 발견된 방식에서 시작하여, EventBridge는 이벤트 페이로드에 해당하는 Java, TypeScript 또는 Python 클래스를 자동으로 생성합니다.

# Abilitare schema discovery su un event bus (AWS CLI)
aws schemas create-discoverer \
  --source-arn arn:aws:events:eu-west-1:123456789012:event-bus/mioapp-production \
  --description "Auto-discovery per mioapp-production"

# Elencare gli schemi scoperti
aws schemas list-schemas \
  --registry-name discovered-schemas

# Scaricare il codice generato per Java
aws schemas get-code-binding-source \
  --registry-name discovered-schemas \
  --schema-name "com.mioapp.ordini@OrdineEffettuato" \
  --language "java8" \
  --schema-version "1" \
  --output text > OrdineEffettuatoEvent.java

이벤트 보관 및 재생

EventBridge의 가장 강력한 기능 중 하나는이벤트 아카이브: 구성 가능한 기간 동안 버스를 통해 이동하는 모든 이벤트를 자동으로 보관합니다. 허용하는 다시 하다 과거 이벤트(프로젝션 재구축에 유용함, 생산 문제 디버깅 또는 새로운 소비자 테스트).

# Creare un archivio per il bus degli ordini
aws events create-archive \
  --archive-name mioapp-ordini-archive \
  --event-source-arn arn:aws:events:eu-west-1:123456789012:event-bus/mioapp-production \
  --retention-days 90 \
  --event-pattern '{
    "source": ["com.mioapp.ordini"],
    "detail-type": ["OrdineEffettuato", "PagamentoConfermato"]
  }'

# Replay degli eventi archiviati (utile per rebuild di read model)
aws events start-replay \
  --replay-name rebuild-read-model-20260320 \
  --event-source-arn arn:aws:events:eu-west-1:123456789012:archive/mioapp-ordini-archive \
  --event-start-time "2026-01-01T00:00:00Z" \
  --event-end-time "2026-03-20T23:59:59Z" \
  --destination '{
    "Arn": "arn:aws:events:eu-west-1:123456789012:event-bus/mioapp-production",
    "FilterArns": [
      "arn:aws:events:eu-west-1:123456789012:rule/mioapp-production/ordini-vip-handler"
    ]
  }'

# Monitorare il replay
aws events describe-replay \
  --replay-name rebuild-read-model-20260320

EventBridge Lambda 소비자

// Handler Lambda Java per eventi EventBridge
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.ScheduledEvent;
import com.fasterxml.jackson.databind.ObjectMapper;

public class OrdiniVipHandler implements RequestHandler<ScheduledEvent, String> {

    private final ObjectMapper objectMapper = new ObjectMapper();
    private final VipNotificationService notificationService = new VipNotificationService();

    @Override
    public String handleRequest(ScheduledEvent event, Context context) {
        context.getLogger().log("Evento ricevuto: " + event.getDetailType());

        try {
            // Deserializza il detail field
            OrdineEffettuatoEvent ordine = objectMapper.convertValue(
                event.getDetail(),
                OrdineEffettuatoEvent.class
            );

            context.getLogger().log(String.format(
                "Ordine VIP: %s, cliente: %s, totale: %.2f",
                ordine.getOrdineId(),
                ordine.getClienteId(),
                ordine.getTotale()
            ));

            // Invia notifica personalizzata al cliente VIP
            notificationService.sendVipOrderConfirmation(
                ordine.getClienteId(),
                ordine.getOrdineId(),
                ordine.getTotale()
            );

            return "SUCCESS";

        } catch (Exception e) {
            context.getLogger().log("ERRORE: " + e.getMessage());
            // Rilancia per triggherare il retry di EventBridge
            throw new RuntimeException("Elaborazione fallita", e);
        }
    }
}

// TypeScript handler per Lambda Node.js
// export const handler = async (event: EventBridgeEvent<'OrdineEffettuato', OrdineDetail>) => {
//   const { ordineId, clienteId, totale } = event.detail;
//   await sendVipNotification(clienteId, ordineId, totale);
//   return { statusCode: 200 };
// };

EventBridge 모범 사례

  • 모든 환경을 위한 맞춤형 이벤트 버스: 프로덕션, 스테이징, 개발을 위해 별도의 버스를 사용합니다. 테스트 이벤트를 프로덕션 버스로 보내지 마십시오.
  • 항상 대상에 DLQ를 사용하십시오.: 각 대상에 대해 SQS 배달 못한 편지 대기열을 구성합니다. 소비자 장애 발생시 이벤트를 놓치지 않도록.
  • Lambda 소비자의 멱등성: EventBridge는 최소 한 번 전달을 보장합니다. Lambda는 동일한 이벤트의 중복 수신을 처리해야 합니다.
  • 이벤트 버전 관리: 항상 필드를 추가하세요. schemaVersion 세부적으로. EventBridge에는 버전 관리 메커니즘이 내장되어 있지 않습니다. 페이로드에서 이를 처리합니다.
  • 버스 생산별 이벤트 아카이브: 항상 보존을 사용하여 아카이브를 구성합니다. 최소 30일. 소비자에게 버그가 발생할 경우 재생을 통해 문제를 해결할 수 있습니다.

시리즈의 다음 단계

  • 기사 7 – SQS, SNS, EventBridge: 선택을 위한 결정 가이드 각 특정 사용 사례에 적합한 AWS 메시징 서비스.
  • 8조 - 배달 못한 편지 대기열 및 탄력성: 올바르게 구성 데이터 손실 없이 실패한 메시지를 처리하기 위한 EventBridge, SQS 및 Lambda용 DLQ.

다른 시리즈와의 연계

  • 사가 패턴(제5조): EventBridge는 이상적인 메시지 버스입니다. AWS의 Choreography Saga용: 각 서비스는 EventBridge 및 다른 서비스에 이벤트를 게시합니다. 서비스는 구성된 라우팅 규칙을 통해 반응합니다.
  • 아파치 카프카(시리즈 38): 온프레미스 또는 하이브리드 시스템의 경우 Kafka 및 EventBridge가 공존할 수 있습니다. 처리량이 높은 내부 메시징을 위한 Kafka, 외부 AWS 및 SaaS 서비스와의 통합을 위한 EventBridge.