Skip to content

EventBridge EventBus L3 Construct with Typed Event Handlers #355

@hoegertn

Description

@hoegertn

Summary

Add an opinionated L3 construct for Amazon EventBridge that follows the same spec-driven, code-generated approach used by RestApi and GraphQlApi. Users define event schemas in a definition file, and cdk-serverless generates typed event publishers, typed handler functions, infrastructure wiring, and DLQ/retry configuration — all zero-config.

Problem

cdk-serverless excels at request/response patterns (REST, GraphQL) but offers nothing for asynchronous event-driven architectures. EventBridge is the backbone of most production serverless systems, yet wiring it up still requires significant boilerplate:

  • Creating custom event buses
  • Defining rules with event patterns
  • Configuring IAM permissions for put-events
  • Setting up DLQ and retry policies on targets
  • Manually typing event detail payloads in handlers
  • No type-safe publishing — putEvents calls are stringly-typed

Teams building microservice architectures with cdk-serverless currently lose the "define once, generate everything" experience as soon as they go async.

Proposed Solution

Event Schema Definition File

A new events.yaml (or JSON) format where users declare event types with their JSON schemas, source identifiers, and detail-type mappings. This becomes the single source of truth, analogous to the OpenAPI spec for RestApi.

# events/orders.yaml
busName: OrderEvents
events:
  OrderCreated:
    source: com.myapp.orders
    detailType: OrderCreated
    schema:
      type: object
      properties:
        orderId:
          type: string
        customerId:
          type: string
        total:
          type: number
        items:
          type: array
          items:
            type: object
            properties:
              productId:
                type: string
              quantity:
                type: integer
      required: [orderId, customerId, total, items]

  OrderStatusChanged:
    source: com.myapp.orders
    detailType: OrderStatusChanged
    schema:
      type: object
      properties:
        orderId:
          type: string
        previousStatus:
          type: string
          enum: [pending, processing, shipped, delivered]
        newStatus:
          type: string
          enum: [pending, processing, shipped, delivered]
      required: [orderId, previousStatus, newStatus]

Projen Integration

import { EventBus } from 'cdk-serverless/projen';

new EventBus(project, {
  busName: 'OrderEvents',
  definitionFile: 'events/orders.yaml',
});

Running projen generates:

  • Typed event interfaces for each event (e.g. OrderCreatedDetail, OrderStatusChangedDetail)
  • Typed handler function signatures for each event (e.g. OrderCreatedHandler)
  • A typed EventPublisher class with methods per event type
  • The L3 CDK construct

CDK Construct Usage

import { OrderEventsEventBus } from './generated/eventbus.orderevents.generated';

const bus = new OrderEventsEventBus(this, 'Bus', {
  singleTableDatastore, // optional: wire DDB Streams as event source
  additionalEnv: {
    DOMAIN_NAME: props.domainName,
  },
});

The construct:

  • Creates a custom EventBridge event bus
  • Creates Lambda functions for each event handler
  • Creates EventBridge rules matching source + detail-type from the schema
  • Configures SQS DLQ on each rule target (with alarm)
  • Sets up retry policy with sensible defaults (e.g. 3 retries, exponential backoff)
  • Grants events:PutEvents to any Lambda that uses the generated publisher
  • Integrates with the existing monitoring infrastructure

Handler DX

// Fully typed — event.detail matches the schema
export const handler: OrderCreatedHandler = async (event) => {
  const { orderId, customerId, total, items } = event.detail;
  // business logic...
};

Publishing DX

import { OrderEventsPublisher } from './generated/eventbus.orderevents-publisher.generated';

const publisher = new OrderEventsPublisher();

// Type-safe — schema enforces payload shape, auto-completion works
await publisher.emit('OrderCreated', {
  orderId: '123',
  customerId: 'cust-456',
  total: 99.99,
  items: [{ productId: 'prod-789', quantity: 2 }],
});

Integration Points

  • SingleTableDatastore: Optionally wire DynamoDB Streams → EventBridge Pipe as an event source on the bus
  • Authentication: Cognito user lifecycle events (PostConfirmation, PreTokenGeneration) can publish to the bus
  • RestApi / GraphQlApi: REST/GraphQL handlers use the generated publisher to emit events after mutations
  • S3EventProcessor (if implemented): Emit events after file processing completes
  • RealtimeApi (if implemented): EventBridge rule → Lambda → AppSync Events publish for client push

Out of Scope

  • SQS consumer patterns → separate issue
  • SNS fan-out patterns → separate issue
  • EventBridge Scheduler → future enhancement
  • EventBridge Pipes (beyond DDB Streams) → future enhancement
  • Cross-account/cross-region event bus rules → future enhancement

Labels

enhancement

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions