Event-Driven System
Table of Contents
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Component Details
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
- Appendix
Introduction
This technical document focuses on Sparrow event-driven system, systematically explains the collaboration of three subsystems: event bus, event store and event projection; deeply analyzes NATS JetStream integration, event serialization and routing strategy, event replay mechanism; and provides best practices for event-driven architecture, including event design principles, version management and consistency guarantee. At the same time, the document demonstrates implementation points of Saga coordinator, event processing pipeline and event replay, and provides performance optimization, error handling and monitoring debugging recommendations.
Project Structure
Sparrow event-driven system is mainly distributed in the following modules:
- Messaging and Event Bus: messaging (publish/subscribe, JetStream read/write, event routing)
- Projection System: projection (indexer, event reader, full projection)
- Use Case and Orchestration: usecase (Saga transaction orchestration), adapter/saga (adapter wrapper)
- Bootstrap and Integration: bootstrap (application lifecycle, NATS connection, publish/subscribe entry)
- Domain Model: entity (domain event interface)
- Configuration: config (NATS connection and stream configuration)
Core Components
Event Bus and Routing
- JetStreamPublisher: Responsible for serializing domain events to generic BaseEvent and publishing to NATS JetStream, subject format is "aggregateType.aggregateID.eventType", automatically ensures stream exists and configures subject set.
- JetStreamSubscriber[T]: Generic subscriber, subscribes by event type, supports explicit acknowledgment, graceful shutdown, context control and panic recovery.
- StreamHub: Subscription aggregator, uniformly manages subscribers and implements start/shutdown lifecycle.
Event Store and Reading
- JetStreamEventReader: JetStream-based event reader, supports full replay by aggregate ID, replay by version, replay by physical offset, internally uses ephemeral consumer to avoid persistence overhead.
Projection System
- JetStreamIndexer: Scans and extracts aggregate root ID list of specified aggregate type from event stream, used for full projection.
- FullProjection: Combines EventReader, AggregateIndexer and Projector, executes projection by event type and aggregate ID.
- projection Interface Family: Defines responsibility boundaries of Projector, EventReader, AggregateIndexer.
Saga Orchestration
- SagaService: Executes transaction steps in order, reverse compensation on failure, state persistence.
- Coordinator: Adapter wrapper, provides simplified API and default memory storage.
Bootstrap and Integration
- App: Centrally manages NATS connection, publisher/subscriber creation, event reader, task scheduling and graceful shutdown process.
Domain Model and Configuration
- DomainEvent Interface: Unified event metadata (ID, type, aggregate ID/type, version, timestamp).
- NATsConfig: NATS/JetStream connection and event stream retention policy configuration.
Architecture Overview
Sparrow adopts event-driven architecture, uses NATS JetStream as event bus and event store, combines projection system to achieve read model rebuild and query optimization. Application accesses NATS uniformly through App, publisher is responsible for event publishing, subscriber is responsible for event consumption, projector is responsible for rebuilding aggregate state from event stream.
Component Details
Event Bus and Routing (NATS JetStream)
Subject Design and Routing
- Subject Format: "aggregateType.aggregateID.eventType", convenient for filtering and routing by aggregate type and event type.
- Publisher automatically generates subject prefix "aggregateType.>" for each aggregate root type, ensures all aggregate root events in same service fall into same stream.
Publishing Process
- Serializes domain event to JSON, encapsulates as BaseEvent, Payload stores in Base64, then publishes to JetStream.
- Publisher ensures stream exists, configures retention policy and size limit, supports debug mode to switch storage medium.
Subscription Process
- Subscriber creates persistent consumer for each event type, explicit acknowledgment, supports immediate replay and graceful shutdown.
- Consumer goroutine internally recovers panic, avoids single point exception causing overall crash.
Lifecycle and Graceful Shutdown
- Subscriber controls consumption through context, Stop supports timeout and resource cleanup; StreamHub aggregates multiple subscribers and uniformly manages.
Event Store and Reading
Design Points
- Uses ephemeral consumer to avoid control plane pressure from persistent consumer, automatically cleans up after consumption completes.
- Supports full replay by aggregate ID, replay by version, replay by physical offset, meets different scenario requirements.
- Events are sorted by version ascending then applied to aggregate root, ensures consistency of state rebuild.
Error Handling
- Logs and skips when deserialization failure or event application failure, avoids interrupting overall process.
Projection System
Aggregate Root Index (JetStreamIndexer)
- Traverses event stream through ephemeral consumer, filters by aggregate type, collects unique aggregate root IDs, supports timeout and error handling.
Event Reading (EventReader)
- Provided by JetStreamEventReader, supports getting events and replay by aggregate ID.
Full Projection (FullProjection)
- Combines EventReader, AggregateIndexer and Projector, executes projection by event type and aggregate ID list, outputs view entity collection.
Saga Orchestration
SagaService
- Executes steps in order, reverse compensation on failure, state persistence, supports handler and compensator registration.
Coordinator
- Adapter wrapper, provides simplified API and default memory storage, convenient for quick integration.
Dependency Analysis
Application Integration
App Responsibilities
- Loads configuration and logs, creates Gin engine, manages subprocess and retry mechanism.
- Provides StreamPub, StreamReader, NewHub and other convenient methods, unifies NATS/JetStream usage.
- Uniformly calls Close during graceful shutdown phase, ensures subscribers and retry goroutines are properly released.
Component Coupling
- App as facade, depends on NATsConfig, Logger, Container, provides publisher/subscriber/reader creation capabilities upward.
- JetStreamPublisher/JetStreamSubscriber depends on nats.Conn and jetstream.JetStream, subject and stream configuration injected by App.
- Projection layer depends on EventReader and AggregateIndexer, both can be implemented by JetStream.
- SagaService depends on Repository abstraction, adapter Coordinator provides default implementation.
External Dependencies
- NATS/JetStream: Event bus and event store.
- Gin: HTTP service hosting.
- Zap: Log recording.
Performance Considerations
Stream and Subject Design
- Publisher generates "aggregateType.>" subject for each aggregate root type, reduces subject fragmentation, improves JetStream distribution efficiency.
- Ephemeral consumer avoids control plane pressure from persistent consumer, reduces operational cost.
Serialization and Payload
- Event payload stores in BaseEvent.Payload in Base64, avoids polymorphism issues of strong type events in stream, while maintaining extensibility.
Replay and Batch Processing
- Reader and indexer use batch Fetch and explicit acknowledgment, reduces network round trips and control overhead.
Retry and Backoff
- App has built-in exponential backoff retry mechanism, avoids transient failure amplification, improves system resilience.
Configuration Optimization
- Controls event stream maximum retention time through NATsConfig, balances storage and query cost.
Troubleshooting Guide
Publishing Failure
- Check JetStream client initialization and stream creation logs, confirm subject set and stream configuration.
- Pay attention to serialization errors and publishing error logs, locate event format or connection issues.
Subscription Failure
- View consumer creation and stream acquisition errors, confirm service name and stream name consistency.
- Observe message deserialization failure and handler error logs, confirm event type mapping and handler implementation.
Replay Exception
- Check ephemeral consumer creation and message acquisition errors, confirm aggregate ID and event version filter conditions.
- If sorting or event application fails, check logs and verify event version and aggregate root LoadFromEvents implementation.
Projection Exception
- Confirm if indexer can correctly scan event stream, check filter subject and aggregate type matching.
- During full projection, pay attention to event reading and projector execution error chain.
Saga Failure
- Check step handler and compensator registration, confirm transaction state persistence and rollback path.
Conclusion
Sparrow event-driven system achieves high throughput, low latency event bus and event store through NATS JetStream, combines projection system to achieve efficient rebuild of read model. Publish/subscribe, event replay, full projection and Saga orchestration form a complete event-driven closed loop. Through clear interface abstraction and pluggable implementation, the system has good extensibility and maintainability. Recommend further improving observability and capacity planning through configuration and monitoring in production environment.
Appendix
Event Design Principles
- Event semantics clear, immutable, modeled by aggregate root dimension.
- Event version increments with event evolution, supports forward/backward compatibility strategy.
Event Version Management
- Uses DomainEvent.GetVersion/SetVersion to manage event version, sorts and applies by version during projection and replay.
Consistency Guarantee
- Subscription side uses explicit acknowledgment and persistent consumer, combines graceful shutdown and context cancellation, ensures processing completes before shutdown.
Best Practice Checklist
- Validate event metadata and payload format before publishing; implement idempotency and compensation on subscription side; filter by aggregate root ID and event type dual dimensions on projection side; Saga steps minimized, compensatable.