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
Flow Example:
- Invoice Service creates invoice, aggregate emits
InvoiceCreateddomain event - Invoice Service publishes
InvoiceCreatedIntegrationEventto event bus - Billing Service subscribes, updates billing state
- Notification Service subscribes, sends confirmation email
- 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.
Related Documents¶
- Clean Architecture & DDD - How domain events fit into DDD
- Cloud-Native Mindset - How events enable cloud-native scaling
- Observability-Driven Design - How events enable observability
- Microservice Template - How templates implement events