Skip to content

Event-Driven Mindset

This document defines ConnectSoft's event-driven architecture principles. It is written for architects and engineers building systems where events drive agent activation and orchestration.

Everything important is expressed as events. Events drive agent activation, orchestration, and system behavior. This enables parallel execution, horizontal scaling, and replay/audit via event streams.

Tip

Events are not just for microservice communication—they're the primary mechanism for agent coordination, audit trails, and system observability. Every significant action should emit an event.

Why Event-Driven

Event-driven architecture provides several key benefits:

Decoupling

  • Loose coupling - Services don't need to know about each other directly
  • Independent evolution - Services can evolve independently
  • Reduced dependencies - No direct service-to-service calls for business logic

Scalability

  • Horizontal scaling - Event consumers scale independently
  • Parallel processing - Multiple consumers process events in parallel
  • Load distribution - Events distribute load across services

Auditability

  • Complete history - Event streams provide complete audit trail
  • Replay capability - Rebuild state from event log
  • Compliance - Events enable compliance and regulatory requirements

Agent Coordination

  • Reactive workflows - Agents react to events, not polling
  • Orchestration - Events coordinate agent workflows
  • Traceability - All agent actions emit events for full traceability

Event Types and Contracts

ConnectSoft uses several event types:

Domain Events

Definition: Events that represent something that happened in the domain.

Characteristics: - Emitted by aggregates within a bounded context - Represent business facts (e.g., InvoiceCreated, PaymentProcessed) - Immutable and timestamped - Used for eventual consistency within a bounded context

Example:

public class InvoiceCreatedEvent : DomainEvent
{
    public Guid InvoiceId { get; }
    public Guid TenantId { get; }
    public Money Amount { get; }
    public DateTime CreatedAt { get; }
}

Integration Events

Definition: Events published across bounded contexts for eventual consistency.

Characteristics: - Published by application layer (not domain) - Represent facts that other contexts need to know - Versioned and backward compatible - Used for cross-context communication

Example:

public class InvoicePaidIntegrationEvent : IntegrationEvent
{
    public Guid InvoiceId { get; }
    public Guid TenantId { get; }
    public Money Amount { get; }
    public DateTime PaidAt { get; }
    public string Version { get; } = "1.0";
}

Event Contracts

Versioning: - Events are versioned (e.g., InvoicePaidV1, InvoicePaidV2) - Backward compatibility maintained when possible - Deprecation process for old versions

Documentation: - Events documented in OpenAPI/Schema Registry - Event schemas versioned and stored - Consumers can discover and subscribe to events

Patterns We Use

Outbox Pattern

Purpose: Ensure events are published reliably, even if service fails.

How It Works: 1. Save event to database (same transaction as domain change) 2. Background process reads events from database 3. Publishes events to event bus 4. Marks events as published

Benefits: - Guaranteed event delivery - No lost events if service crashes - Transactional consistency

Pub/Sub Pattern

Purpose: Decouple event publishers from consumers.

How It Works: - Publishers publish events to event bus (don't know who consumes) - Consumers subscribe to event types (don't know who publishes) - Event bus routes events to subscribers

Benefits: - Loose coupling - Multiple consumers per event - Easy to add new consumers

Event Sourcing (When Applicable)

Purpose: Store all changes as events, rebuild state from events.

When to Use: - Audit-critical domains (financial, compliance) - Need complete history - Need replay capability

When NOT to Use: - Simple CRUD operations - High-volume, low-value events - Performance-critical paths

At ConnectSoft: Used selectively for audit-critical domains (Audit Platform, financial systems).

Events as Notifications

Purpose: Notify other services about changes, don't rebuild state.

When to Use: - Most common pattern - Services maintain their own state - Events notify about changes

Example: InvoiceCreated event notifies Notification service to send email, but Notification service doesn't rebuild invoice state.

Event Flows Between Services

Here's how events flow between services:

flowchart TD
    subgraph "Invoice Bounded Context"
        INV[Invoice Service]
        INV_AGG[Invoice Aggregate]
    end

    subgraph "Billing Bounded Context"
        BILL[Billing Service]
    end

    subgraph "Notification Bounded Context"
        NOTIF[Notification Service]
    end

    subgraph "Audit Bounded Context"
        AUDIT[Audit Service]
    end

    subgraph "Event Bus"
        EB[Azure Service Bus<br/>Pub/Sub]
    end

    INV_AGG -->|Emits Domain Event| INV
    INV -->|Publishes Integration Event| EB

    EB -->|Subscribes| BILL
    EB -->|Subscribes| NOTIF
    EB -->|Subscribes| AUDIT

    BILL -->|Updates Billing State| BILL
    NOTIF -->|Sends Email| NOTIF
    AUDIT -->|Logs Event| AUDIT

    style INV fill:#2563EB,color:#fff
    style EB fill:#10B981,color:#fff
    style BILL fill:#4F46E5,color:#fff
    style NOTIF fill:#EAB308,color:#fff
    style AUDIT fill:#EF4444,color:#fff
Hold "Alt" / "Option" to enable pan & zoom

Flow Example:

  1. Invoice Service creates invoice, aggregate emits InvoiceCreated domain event
  2. Invoice Service publishes InvoiceCreatedIntegrationEvent to event bus
  3. Billing Service subscribes, updates billing state
  4. Notification Service subscribes, sends confirmation email
  5. Audit Service subscribes, logs event for compliance

Key Points: - Services don't know about each other - Event bus routes events to subscribers - Each service processes events independently - Services can be added/removed without affecting others

When NOT to Use Events

Events are powerful, but not always the right choice:

Synchronous Consistency Needed

When: Need immediate consistency, can't tolerate eventual consistency.

Example: Payment processing where you need to know immediately if payment succeeded.

Alternative: Use synchronous API calls or distributed transactions (with caution).

Simple CRUD Inside Single Bounded Context

When: Simple create/read/update/delete within one bounded context.

Example: Updating user profile in Identity service.

Alternative: Use domain events within bounded context, but don't publish integration events.

High-Performance, Low-Latency Requirements

When: Need sub-millisecond response times.

Example: Real-time trading systems, gaming.

Alternative: Use direct calls or in-memory events, not distributed event bus.

Simple Request-Response Patterns

When: Simple request-response where caller needs immediate response.

Example: Getting user details by ID.

Alternative: Use synchronous API calls.

Important

Use events for: Cross-context communication, eventual consistency, audit trails, agent coordination. Don't use events for: Synchronous consistency requirements, simple CRUD in single context, high-performance/low-latency paths, simple request-response.