NatsPubsub Architecture Guide
This comprehensive guide explores the architecture of NatsPubsub, including system design, component breakdown, message flow, reliability patterns, and design decisions.
Table of Contents
- Overview
- System Architecture
- Component Architecture
- Message Flow Architecture
- Reliability Patterns
- Stream and Consumer Architecture
- Connection Management
- Concurrency Model
- Topology Management
- Design Decisions
- Scaling Architecture
- Deployment Architectures
Overview
NatsPubsub is a production-ready pub/sub library built on NATS JetStream, designed with reliability, scalability, and developer experience as core principles.
Architecture Principles
- Reliability First: Built-in patterns for message delivery guarantees
- Declarative API: Clean, intuitive interfaces for publishers and subscribers
- SOLID Principles: Modular, testable, and maintainable code
- Polyglot Design: Ruby and JavaScript implementations with full interoperability
- Cloud-Native: Designed for distributed microservices architectures
High-Level Architecture
System Architecture
Layered Architecture
NatsPubsub follows a layered architecture pattern:
Layer Responsibilities
-
Application Layer
- Business logic
- Message handlers
- Domain models
-
API Layer
- Publisher interface
- Subscriber interface
- Fluent APIs
-
Business Logic Layer
- Message validation
- Schema enforcement
- Business rules
-
Middleware Layer
- Logging
- Metrics
- Tracing
- Error handling
-
Reliability Layer
- Inbox/Outbox patterns
- DLQ handling
- Retry logic
- Circuit breakers
-
Transport Layer
- Connection management
- Subject routing
- Message serialization
-
Storage Layer
- Database persistence
- Cache management
- State storage
Component Architecture
Core Components
Publisher Component
Responsibilities:
- Message publishing
- Subject building
- Envelope creation
- Validation
- Error handling
Design Pattern: Strategy Pattern
// Publisher uses strategy pattern for different publish types
class Publisher {
private connectionManager: ConnectionManager;
private envelopeBuilder: EnvelopeBuilder;
private subjectBuilder: SubjectBuilder;
private validator: PublishValidator;
// Strategy: Publish to topic
async publishToTopic(topic: string, message: any): Promise<PublishResult>;
// Strategy: Publish to multiple topics
async publishToMultipleTopics(
params: MultiTopicParams,
): Promise<MultiTopicPublishResult>;
// Strategy: Publish with domain/resource/action
async publishDomainResourceAction(
params: DomainResourceActionParams,
): Promise<PublishResult>;
}
Key Features:
- Dependency injection for testability
- Fluent API for batch operations
- Automatic subject building
- Message envelope wrapping
- Validation before publish
Subscriber Component
Responsibilities:
- Message consumption
- Message processing
- Error handling
- Acknowledgment
- Retry logic
Design Pattern: Template Method Pattern
// Subscriber uses template method pattern
abstract class Subscriber {
// Template method
async call(
event: Record<string, unknown>,
metadata: EventMetadata,
): Promise<void> {
// 1. Pre-processing (implemented by base class)
await this.preProcess(metadata);
// 2. Handle (implemented by subclass)
await this.handle(event, metadata);
// 3. Post-processing (implemented by base class)
await this.postProcess(metadata);
}
// Hook method - must be implemented by subclass
abstract handle(
event: Record<string, unknown>,
metadata: EventMetadata,
): Promise<void>;
}
Key Features:
- Declarative subscription syntax
- Automatic consumer creation
- Concurrent message processing
- Middleware support
- Error boundaries
Consumer Component
Responsibilities:
- JetStream consumer management
- Message fetching
- Concurrency control
- Backpressure handling
- Graceful shutdown
Design Pattern: Observer Pattern
class Consumer {
private subscriptions: Map<string, ConsumerSubscription>;
private processing: Set<string>;
private concurrency: number;
// Observer pattern - notify on message
async startConsuming(subscriber: Subscriber): Promise<void> {
// Create consumer
const consumer = await this.createConsumer(subscriber);
// Observe messages
for await (const msg of consumer) {
await this.processMessage(msg, subscriber);
}
}
}
Configuration Component
Responsibilities:
- Configuration management
- Preset application
- Validation
- Environment detection
Design Pattern: Singleton Pattern
// Configuration singleton
class Config {
private static instance: Config;
private config: NatsPubsubConfig;
public static getInstance(): Config {
if (!Config.instance) {
Config.instance = new Config();
}
return Config.instance;
}
public configure(options: Partial<NatsPubsubConfig>): void;
public configureWithPreset(
preset: PresetName,
overrides?: Partial<NatsPubsubConfig>,
): void;
public get(): NatsPubsubConfig;
public validate(): void;
}
Connection Manager
Responsibilities:
- NATS connection lifecycle
- Connection pooling
- Reconnection logic
- Health checking
Design Pattern: Object Pool Pattern
class ConnectionManager {
private connection: NatsConnection | null;
private reconnecting: boolean;
private healthCheckInterval: NodeJS.Timeout;
async connect(): Promise<void>;
async disconnect(): Promise<void>;
async reconnect(): Promise<void>;
getJetStream(): JetStreamClient;
isHealthy(): boolean;
}
Topology Manager
Responsibilities:
- Stream creation
- Consumer setup
- Subject routing
- Overlap detection
Design Pattern: Builder Pattern
class TopologyManager {
private streamManager: StreamManager;
private consumerManager: ConsumerManager;
private overlapGuard: OverlapGuard;
async ensureStream(config: StreamConfig): Promise<void>;
async ensureConsumer(config: ConsumerConfig): Promise<void>;
async validateTopology(): Promise<ValidationResult>;
}
Message Flow Architecture
Publishing Flow
Subscribing Flow
Batch Publishing Flow
Reliability Patterns
Outbox Pattern Architecture
Components:
- Outbox Repository: Database access layer
- Outbox Publisher: Publishing coordinator
- Background Worker: Processes pending events
- Cleanup Service: Maintains table health
Guarantees:
- At-least-once delivery
- Transactional consistency
- Ordering within transaction
- Automatic retry on failure
Inbox Pattern Architecture
Components:
- Inbox Repository: Database access layer
- Inbox Processor: Deduplication coordinator
- Cleanup Service: Maintains table health
Guarantees:
- Exactly-once processing
- Idempotent operations
- Duplicate detection
- Processing history
DLQ Architecture
Components:
- DLQ Publisher: Sends failed messages to DLQ
- DLQ Consumer: Processes DLQ messages
- DLQ Handler: Custom failure handling
- DLQ Storage: Persistent failed message storage
Features:
- Automatic retry exhaustion detection
- Metadata preservation
- Failure reason tracking
- Manual replay capability
Stream and Consumer Architecture
Stream Hierarchy
Consumer Types
NatsPubsub Uses:
- Pull Consumers: For controlled message fetching
- Durable: For persistent subscriptions
- Batch Fetching: For high throughput
Stream Configuration
interface StreamConfig {
name: string; // Stream name
subjects: string[]; // Subject patterns
retention: "limits" | "interest" | "workqueue";
storage: "file" | "memory";
replicas: number; // Replication factor
maxMsgs: number; // Max messages
maxBytes: number; // Max storage
maxAge: number; // Message TTL (nanoseconds)
maxMsgSize: number; // Max message size
duplicateWindow: number; // Duplicate detection window
}
Example:
const streamConfig: StreamConfig = {
name: "production-events-stream",
subjects: ["production.app.>"],
retention: "interest", // Delete after all consumers ACK
storage: "file", // Persistent storage
replicas: 3, // High availability
maxMsgs: 1000000, // 1M messages max
maxBytes: 10 * 1024 * 1024 * 1024, // 10GB
maxAge: 7 * 24 * 3600 * 1e9, // 7 days
maxMsgSize: 1024 * 1024, // 1MB per message
duplicateWindow: 2 * 60 * 1e9, // 2 minutes
};