Event-driven architectures have become fundamental to building scalable, resilient microservices systems. NATS JetStream emerges as a powerful solution that combines the simplicity of NATS Core with the persistence and reliability required for mission-critical applications. This comprehensive guide explores how to architect, implement, and optimize event-driven systems using NATS JetStream, from basic streaming patterns to advanced clustering configurations.

Event-Driven Architecture with NATS JetStream

NATS JetStream Architecture

NATS JetStream represents a significant evolution from traditional message queues, offering a cloud-native approach to event streaming with built-in persistence, replay capabilities, and exactly-once delivery guarantees.

Core Components and Data Flow

Understanding JetStream’s architecture is crucial for designing effective event-driven systems:

┌─────────────────────────────────────────────────────────────┐
│                        NATS Server                          │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────┐    ┌─────────────────┐                │
│  │   NATS Core     │    │   JetStream     │                │
│  │   (Messaging)   │    │   (Streaming)   │                │
│  └─────────────────┘    └─────────────────┘                │
│           │                       │                         │
│           └───────────────┬───────┘                         │
├───────────────────────────┼─────────────────────────────────┤
│                          │                                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │                  Stream Storage                      │  │
│  ├──────────────┬─────────────┬─────────────┬───────────┤  │
│  │   Stream 1   │   Stream 2  │   Stream 3  │  Stream N │  │
│  │              │             │             │           │  │
│  │  Messages    │  Messages   │  Messages   │ Messages  │  │
│  │  Consumers   │  Consumers  │  Consumers  │ Consumers │  │
│  │  Policies    │  Policies   │  Policies   │ Policies  │  │
│  └──────────────┴─────────────┴─────────────┴───────────┘  │
└─────────────────────────────────────────────────────────────┘
                               │
                  ┌────────────┴────────────┐
                  │                         │
           ┌──────▼──────┐           ┌──────▼──────┐
           │  Publisher  │           │  Consumer   │
           │ Application │           │ Application │
           └─────────────┘           └─────────────┘

Stream and Consumer Configuration

JetStream’s flexibility comes from its configurable streams and consumers:

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "time"

    "github.com/nats-io/nats.go"
)

type JetStreamManager struct {
    nc *nats.Conn
    js nats.JetStreamContext
}

func NewJetStreamManager(url string) (*JetStreamManager, error) {
    // Connect to NATS with enhanced options
    nc, err := nats.Connect(url,
        nats.ReconnectWait(2*time.Second),
        nats.MaxReconnects(5),
        nats.ReconnectBufSize(5*1024*1024), // 5MB buffer
        nats.DrainTimeout(10*time.Second),
        nats.ErrorHandler(func(nc *nats.Conn, s *nats.Subscription, err error) {
            log.Printf("NATS error: %v on connection %v, subscription %v", err, nc, s)
        }),
        nats.DisconnectErrHandler(func(nc *nats.Conn, err error) {
            log.Printf("NATS disconnected: %v", err)
        }),
        nats.ReconnectHandler(func(nc *nats.Conn) {
            log.Printf("NATS reconnected to %v", nc.ConnectedUrl())
        }),
    )
    if err != nil {
        return nil, fmt.Errorf("failed to connect to NATS: %w", err)
    }

    // Create JetStream context
    js, err := nc.JetStream()
    if err != nil {
        return nil, fmt.Errorf("failed to create JetStream context: %w", err)
    }

    return &JetStreamManager{
        nc: nc,
        js: js,
    }, nil
}

func (jsm *JetStreamManager) CreateOptimizedStream(name string, subjects []string) error {
    streamConfig := &nats.StreamConfig{
        Name:              name,
        Subjects:          subjects,
        Retention:         nats.InterestPolicy,     // Delete after consumers ack
        MaxConsumers:      -1,                      // Unlimited consumers
        MaxMsgs:           1000000,                 // 1M messages max
        MaxBytes:          100 * 1024 * 1024,      // 100MB max
        MaxAge:            24 * time.Hour,          // 24 hour retention
        MaxMsgSize:        1024 * 1024,             // 1MB max message size
        Storage:           nats.FileStorage,         // Persistent storage
        Replicas:          3,                       // HA with 3 replicas
        NoAck:             false,                   // Require acknowledgments
        Discard:           nats.DiscardOld,         // Remove old when limits hit
        DuplicateWindow:   2 * time.Minute,        // Deduplication window
        
        // Compression settings for efficiency
        Compression: nats.S2Compression,
        
        // Subject transform for complex routing
        SubjectTransform: &nats.SubjectTransformConfig{
            Source:      "events.>",
            Destination: "processed.{{wildcard(1)}}",
        },
        
        // Advanced placement for geo-distribution
        Placement: &nats.Placement{
            Cluster: "production-cluster",
            Tags:    []string{"ssd", "high-memory"},
        },
    }

    _, err := jsm.js.AddStream(streamConfig)
    if err != nil {
        return fmt.Errorf("failed to create stream %s: %w", name, err)
    }

    log.Printf("Created optimized stream: %s", name)
    return nil
}

func (jsm *JetStreamManager) CreateConsumer(streamName, consumerName string, 
    config ConsumerConfig) (*nats.ConsumerInfo, error) {
    
    consumerConfig := &nats.ConsumerConfig{
        Durable:           consumerName,
        DeliverPolicy:     config.DeliverPolicy,
        OptStartSeq:       config.StartSequence,
        OptStartTime:      config.StartTime,
        AckPolicy:         config.AckPolicy,
        AckWait:           config.AckWait,
        MaxDeliver:        config.MaxRetries,
        BackOff:           config.BackoffDurations,
        ReplayPolicy:      config.ReplayPolicy,
        FilterSubject:     config.FilterSubject,
        SampleFreq:        config.SampleFrequency,
        MaxRequestBatch:   config.BatchSize,
        MaxRequestExpires: config.RequestExpiry,
        
        // Flow control and rate limiting
        RateLimit: config.RateLimit, // Messages per second
        
        // Memory optimization
        MemoryStorage: config.MemoryStorage,
        
        // Heartbeat for consumer health monitoring
        Heartbeat: 30 * time.Second,
        
        // Consumer metadata for debugging
        Description: fmt.Sprintf("Consumer for %s processing %s events", 
            consumerName, config.FilterSubject),
    }

    info, err := jsm.js.AddConsumer(streamName, consumerConfig)
    if err != nil {
        return nil, fmt.Errorf("failed to create consumer %s: %w", consumerName, err)
    }

    return info, nil
}

type ConsumerConfig struct {
    DeliverPolicy     nats.DeliverPolicy
    StartSequence     uint64
    StartTime         *time.Time
    AckPolicy         nats.AckPolicy
    AckWait           time.Duration
    MaxRetries        int
    BackoffDurations  []time.Duration
    ReplayPolicy      nats.ReplayPolicy
    FilterSubject     string
    SampleFrequency   string
    BatchSize         int
    RequestExpiry     time.Duration
    RateLimit         uint64
    MemoryStorage     bool
}

// Event structures for type safety
type EventEnvelope struct {
    ID        string                 `json:"id"`
    Type      string                 `json:"type"`
    Source    string                 `json:"source"`
    Subject   string                 `json:"subject"`
    Time      time.Time              `json:"time"`
    Data      map[string]interface{} `json:"data"`
    Metadata  EventMetadata          `json:"metadata"`
}

type EventMetadata struct {
    Version     string            `json:"version"`
    ContentType string            `json:"content_type"`
    Headers     map[string]string `json:"headers"`
    TraceID     string            `json:"trace_id"`
    CorrelationID string          `json:"correlation_id"`
}

func (jsm *JetStreamManager) PublishEvent(subject string, event EventEnvelope) error {
    // Ensure event metadata
    if event.ID == "" {
        event.ID = generateEventID()
    }
    if event.Time.IsZero() {
        event.Time = time.Now()
    }
    
    // Serialize event
    data, err := json.Marshal(event)
    if err != nil {
        return fmt.Errorf("failed to marshal event: %w", err)
    }
    
    // Publish with deduplication
    ack, err := jsm.js.Publish(subject, data, nats.MsgId(event.ID))
    if err != nil {
        return fmt.Errorf("failed to publish event: %w", err)
    }
    
    log.Printf("Published event %s to %s (seq: %d)", event.ID, subject, ack.Sequence)
    return nil
}

func generateEventID() string {
    return fmt.Sprintf("evt_%d_%s", time.Now().UnixNano(), 
        randomString(8))
}

func randomString(length int) string {
    const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
    b := make([]byte, length)
    for i := range b {
        b[i] = charset[rand.Intn(len(charset))]
    }
    return string(b)
}

Stream Processing Patterns

JetStream supports various stream processing patterns essential for event-driven architectures.

Event Sourcing Implementation

Implementing event sourcing with JetStream for complete audit trails:

package eventsource

import (
    "context"
    "encoding/json"
    "fmt"
    "reflect"
    "time"

    "github.com/nats-io/nats.go"
)

type EventStore struct {
    js nats.JetStreamContext
    streamName string
}

type Event interface {
    GetAggregateID() string
    GetEventType() string
    GetVersion() int
    GetTimestamp() time.Time
}

type BaseEvent struct {
    AggregateID string    `json:"aggregate_id"`
    EventType   string    `json:"event_type"`
    Version     int       `json:"version"`
    Timestamp   time.Time `json:"timestamp"`
}

func (e BaseEvent) GetAggregateID() string { return e.AggregateID }
func (e BaseEvent) GetEventType() string   { return e.EventType }
func (e BaseEvent) GetVersion() int        { return e.Version }
func (e BaseEvent) GetTimestamp() time.Time { return e.Timestamp }

// Domain events for an e-commerce system
type OrderCreated struct {
    BaseEvent
    CustomerID string  `json:"customer_id"`
    Amount     float64 `json:"amount"`
    Items      []Item  `json:"items"`
}

type OrderShipped struct {
    BaseEvent
    TrackingNumber string `json:"tracking_number"`
    ShippingDate   time.Time `json:"shipping_date"`
    Carrier        string    `json:"carrier"`
}

type OrderCancelled struct {
    BaseEvent
    Reason        string    `json:"reason"`
    RefundAmount  float64   `json:"refund_amount"`
    CancelledDate time.Time `json:"cancelled_date"`
}

type Item struct {
    SKU      string  `json:"sku"`
    Quantity int     `json:"quantity"`
    Price    float64 `json:"price"`
}

func NewEventStore(js nats.JetStreamContext, streamName string) *EventStore {
    return &EventStore{
        js:         js,
        streamName: streamName,
    }
}

func (es *EventStore) AppendEvents(aggregateID string, events []Event, 
    expectedVersion int) error {
    
    // Check current version for optimistic concurrency
    currentVersion, err := es.getCurrentVersion(aggregateID)
    if err != nil {
        return fmt.Errorf("failed to get current version: %w", err)
    }
    
    if currentVersion != expectedVersion {
        return fmt.Errorf("concurrency conflict: expected version %d, got %d", 
            expectedVersion, currentVersion)
    }
    
    // Publish events atomically using transactions
    for i, event := range events {
        // Set version for this event
        if setter, ok := event.(interface{ SetVersion(int) }); ok {
            setter.SetVersion(expectedVersion + i + 1)
        }
        
        subject := fmt.Sprintf("events.%s.%s", aggregateID, event.GetEventType())
        
        eventData, err := json.Marshal(event)
        if err != nil {
            return fmt.Errorf("failed to marshal event: %w", err)
        }
        
        // Publish with headers for metadata
        headers := nats.Header{}
        headers.Set("Aggregate-ID", aggregateID)
        headers.Set("Event-Type", event.GetEventType())
        headers.Set("Event-Version", fmt.Sprintf("%d", event.GetVersion()))
        headers.Set("Event-Timestamp", event.GetTimestamp().Format(time.RFC3339))
        
        msg := &nats.Msg{
            Subject: subject,
            Data:    eventData,
            Header:  headers,
        }
        
        ack, err := es.js.PublishMsg(msg)
        if err != nil {
            return fmt.Errorf("failed to publish event: %w", err)
        }
        
        fmt.Printf("Published event %s for aggregate %s (seq: %d)\n", 
            event.GetEventType(), aggregateID, ack.Sequence)
    }
    
    return nil
}

func (es *EventStore) GetEvents(aggregateID string, fromVersion int) ([]Event, error) {
    subject := fmt.Sprintf("events.%s.>", aggregateID)
    
    // Create temporary consumer for reading events
    consumerConfig := &nats.ConsumerConfig{
        DeliverPolicy: nats.DeliverByStartSequence,
        OptStartSeq:   uint64(fromVersion),
        AckPolicy:     nats.AckExplicitPolicy,
        FilterSubject: subject,
    }
    
    sub, err := es.js.PullSubscribe(subject, "", nats.ConsumerConfig(*consumerConfig))
    if err != nil {
        return nil, fmt.Errorf("failed to create subscription: %w", err)
    }
    defer sub.Unsubscribe()
    
    var events []Event
    
    // Fetch events in batches
    for {
        msgs, err := sub.Fetch(100, nats.MaxWait(1*time.Second))
        if err != nil {
            if err == nats.ErrTimeout {
                break // No more messages
            }
            return nil, fmt.Errorf("failed to fetch messages: %w", err)
        }
        
        for _, msg := range msgs {
            event, err := es.deserializeEvent(msg)
            if err != nil {
                return nil, fmt.Errorf("failed to deserialize event: %w", err)
            }
            
            events = append(events, event)
            msg.Ack()
        }
        
        if len(msgs) == 0 {
            break
        }
    }
    
    return events, nil
}

func (es *EventStore) deserializeEvent(msg *nats.Msg) (Event, error) {
    eventType := msg.Header.Get("Event-Type")
    
    var event Event
    
    switch eventType {
    case "OrderCreated":
        event = &OrderCreated{}
    case "OrderShipped":
        event = &OrderShipped{}
    case "OrderCancelled":
        event = &OrderCancelled{}
    default:
        return nil, fmt.Errorf("unknown event type: %s", eventType)
    }
    
    if err := json.Unmarshal(msg.Data, event); err != nil {
        return nil, fmt.Errorf("failed to unmarshal event: %w", err)
    }
    
    return event, nil
}

func (es *EventStore) getCurrentVersion(aggregateID string) (int, error) {
    // Get the last event for this aggregate
    subject := fmt.Sprintf("events.%s.>", aggregateID)
    
    // Use consumer to get the last message
    consumerConfig := &nats.ConsumerConfig{
        DeliverPolicy: nats.DeliverLastPerSubject,
        AckPolicy:     nats.AckExplicitPolicy,
        FilterSubject: subject,
    }
    
    sub, err := es.js.PullSubscribe(subject, "", nats.ConsumerConfig(*consumerConfig))
    if err != nil {
        return 0, fmt.Errorf("failed to create subscription: %w", err)
    }
    defer sub.Unsubscribe()
    
    msgs, err := sub.Fetch(1, nats.MaxWait(1*time.Second))
    if err != nil {
        if err == nats.ErrTimeout {
            return 0, nil // No events yet
        }
        return 0, fmt.Errorf("failed to fetch last message: %w", err)
    }
    
    if len(msgs) == 0 {
        return 0, nil
    }
    
    versionStr := msgs[0].Header.Get("Event-Version")
    var version int
    if _, err := fmt.Sscanf(versionStr, "%d", &version); err != nil {
        return 0, fmt.Errorf("failed to parse version: %w", err)
    }
    
    msgs[0].Ack()
    return version, nil
}

// Aggregate reconstruction
type Order struct {
    ID         string    `json:"id"`
    CustomerID string    `json:"customer_id"`
    Amount     float64   `json:"amount"`
    Items      []Item    `json:"items"`
    Status     string    `json:"status"`
    CreatedAt  time.Time `json:"created_at"`
    ShippedAt  *time.Time `json:"shipped_at,omitempty"`
    Version    int       `json:"version"`
}

func (es *EventStore) ReconstructAggregate(aggregateID string) (*Order, error) {
    events, err := es.GetEvents(aggregateID, 0)
    if err != nil {
        return nil, fmt.Errorf("failed to get events: %w", err)
    }
    
    if len(events) == 0 {
        return nil, fmt.Errorf("aggregate not found: %s", aggregateID)
    }
    
    order := &Order{ID: aggregateID}
    
    for _, event := range events {
        switch e := event.(type) {
        case *OrderCreated:
            order.CustomerID = e.CustomerID
            order.Amount = e.Amount
            order.Items = e.Items
            order.Status = "created"
            order.CreatedAt = e.Timestamp
            
        case *OrderShipped:
            order.Status = "shipped"
            order.ShippedAt = &e.ShippingDate
            
        case *OrderCancelled:
            order.Status = "cancelled"
        }
        
        order.Version = event.GetVersion()
    }
    
    return order, nil
}

// Usage example
func ExampleEventSourcing() {
    // Connect to NATS JetStream
    nc, _ := nats.Connect("nats://localhost:4222")
    js, _ := nc.JetStream()
    
    eventStore := NewEventStore(js, "event-store")
    
    // Create new order
    orderID := "order-123"
    events := []Event{
        &OrderCreated{
            BaseEvent: BaseEvent{
                AggregateID: orderID,
                EventType:   "OrderCreated",
                Timestamp:   time.Now(),
            },
            CustomerID: "customer-456",
            Amount:     99.99,
            Items: []Item{
                {SKU: "SKU001", Quantity: 2, Price: 49.995},
            },
        },
    }
    
    err := eventStore.AppendEvents(orderID, events, 0)
    if err != nil {
        log.Printf("Failed to append events: %v", err)
        return
    }
    
    // Ship the order
    shipEvents := []Event{
        &OrderShipped{
            BaseEvent: BaseEvent{
                AggregateID: orderID,
                EventType:   "OrderShipped",
                Timestamp:   time.Now(),
            },
            TrackingNumber: "TRACK123",
            ShippingDate:   time.Now(),
            Carrier:        "FedEx",
        },
    }
    
    err = eventStore.AppendEvents(orderID, shipEvents, 1)
    if err != nil {
        log.Printf("Failed to append ship events: %v", err)
        return
    }
    
    // Reconstruct the order
    order, err := eventStore.ReconstructAggregate(orderID)
    if err != nil {
        log.Printf("Failed to reconstruct order: %v", err)
        return
    }
    
    fmt.Printf("Reconstructed order: %+v\n", order)
}

CQRS with Read Model Projections

Implementing Command Query Responsibility Segregation with projections:

package cqrs

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "sync"
    "time"

    "github.com/nats-io/nats.go"
)

type ProjectionManager struct {
    js          nats.JetStreamContext
    projections map[string]*Projection
    mu          sync.RWMutex
}

type Projection struct {
    Name            string
    StreamName      string
    ConsumerName    string
    FilterSubjects  []string
    Handler         ProjectionHandler
    subscription    *nats.Subscription
    lastProcessed   uint64
    mu              sync.RWMutex
}

type ProjectionHandler interface {
    Handle(event Event) error
    GetName() string
}

// Order summary projection for fast queries
type OrderSummaryProjection struct {
    store map[string]*OrderSummary // In production, use Redis/MongoDB
    mu    sync.RWMutex
}

type OrderSummary struct {
    ID           string    `json:"id"`
    CustomerID   string    `json:"customer_id"`
    Status       string    `json:"status"`
    TotalAmount  float64   `json:"total_amount"`
    ItemCount    int       `json:"item_count"`
    CreatedAt    time.Time `json:"created_at"`
    LastUpdated  time.Time `json:"last_updated"`
}

func NewOrderSummaryProjection() *OrderSummaryProjection {
    return &OrderSummaryProjection{
        store: make(map[string]*OrderSummary),
    }
}

func (p *OrderSummaryProjection) GetName() string {
    return "order-summary"
}

func (p *OrderSummaryProjection) Handle(event Event) error {
    p.mu.Lock()
    defer p.mu.Unlock()
    
    aggregateID := event.GetAggregateID()
    
    switch e := event.(type) {
    case *OrderCreated:
        summary := &OrderSummary{
            ID:          aggregateID,
            CustomerID:  e.CustomerID,
            Status:      "created",
            TotalAmount: e.Amount,
            ItemCount:   len(e.Items),
            CreatedAt:   e.Timestamp,
            LastUpdated: e.Timestamp,
        }
        p.store[aggregateID] = summary
        log.Printf("Created order summary for %s", aggregateID)
        
    case *OrderShipped:
        if summary, exists := p.store[aggregateID]; exists {
            summary.Status = "shipped"
            summary.LastUpdated = e.Timestamp
            log.Printf("Updated order summary for %s: shipped", aggregateID)
        }
        
    case *OrderCancelled:
        if summary, exists := p.store[aggregateID]; exists {
            summary.Status = "cancelled"
            summary.LastUpdated = e.Timestamp
            log.Printf("Updated order summary for %s: cancelled", aggregateID)
        }
    }
    
    return nil
}

func (p *OrderSummaryProjection) GetOrderSummary(orderID string) (*OrderSummary, error) {
    p.mu.RLock()
    defer p.mu.RUnlock()
    
    summary, exists := p.store[orderID]
    if !exists {
        return nil, fmt.Errorf("order summary not found: %s", orderID)
    }
    
    // Return a copy to prevent mutations
    result := *summary
    return &result, nil
}

func (p *OrderSummaryProjection) GetOrdersByCustomer(customerID string) ([]*OrderSummary, error) {
    p.mu.RLock()
    defer p.mu.RUnlock()
    
    var orders []*OrderSummary
    for _, summary := range p.store {
        if summary.CustomerID == customerID {
            // Copy to prevent mutations
            orderCopy := *summary
            orders = append(orders, &orderCopy)
        }
    }
    
    return orders, nil
}

// Customer analytics projection
type CustomerAnalyticsProjection struct {
    analytics map[string]*CustomerAnalytics
    mu        sync.RWMutex
}

type CustomerAnalytics struct {
    CustomerID       string    `json:"customer_id"`
    TotalOrders      int       `json:"total_orders"`
    TotalSpent       float64   `json:"total_spent"`
    AverageOrderSize float64   `json:"average_order_size"`
    LastOrderDate    time.Time `json:"last_order_date"`
    Status           string    `json:"status"`
}

func NewCustomerAnalyticsProjection() *CustomerAnalyticsProjection {
    return &CustomerAnalyticsProjection{
        analytics: make(map[string]*CustomerAnalytics),
    }
}

func (p *CustomerAnalyticsProjection) GetName() string {
    return "customer-analytics"
}

func (p *CustomerAnalyticsProjection) Handle(event Event) error {
    p.mu.Lock()
    defer p.mu.Unlock()
    
    switch e := event.(type) {
    case *OrderCreated:
        analytics, exists := p.analytics[e.CustomerID]
        if !exists {
            analytics = &CustomerAnalytics{
                CustomerID: e.CustomerID,
                Status:     "active",
            }
            p.analytics[e.CustomerID] = analytics
        }
        
        analytics.TotalOrders++
        analytics.TotalSpent += e.Amount
        analytics.AverageOrderSize = analytics.TotalSpent / float64(analytics.TotalOrders)
        analytics.LastOrderDate = e.Timestamp
        
        log.Printf("Updated customer analytics for %s: %d orders, $%.2f total", 
            e.CustomerID, analytics.TotalOrders, analytics.TotalSpent)
    }
    
    return nil
}

func (p *CustomerAnalyticsProjection) GetCustomerAnalytics(customerID string) (*CustomerAnalytics, error) {
    p.mu.RLock()
    defer p.mu.RUnlock()
    
    analytics, exists := p.analytics[customerID]
    if !exists {
        return nil, fmt.Errorf("customer analytics not found: %s", customerID)
    }
    
    result := *analytics
    return &result, nil
}

func NewProjectionManager(js nats.JetStreamContext) *ProjectionManager {
    return &ProjectionManager{
        js:          js,
        projections: make(map[string]*Projection),
    }
}

func (pm *ProjectionManager) RegisterProjection(streamName string, 
    handler ProjectionHandler, filterSubjects []string) error {
    
    pm.mu.Lock()
    defer pm.mu.Unlock()
    
    projection := &Projection{
        Name:           handler.GetName(),
        StreamName:     streamName,
        ConsumerName:   fmt.Sprintf("projection-%s", handler.GetName()),
        FilterSubjects: filterSubjects,
        Handler:        handler,
    }
    
    // Create durable consumer for the projection
    consumerConfig := &nats.ConsumerConfig{
        Durable:           projection.ConsumerName,
        DeliverPolicy:     nats.DeliverAllPolicy,
        AckPolicy:         nats.AckExplicitPolicy,
        MaxDeliver:        3,
        AckWait:           30 * time.Second,
        ReplayPolicy:      nats.ReplayInstantPolicy,
        FilterSubject:     buildFilterSubject(filterSubjects),
        MaxRequestBatch:   100,
        MaxRequestExpires: 5 * time.Second,
    }
    
    _, err := pm.js.AddConsumer(streamName, consumerConfig)
    if err != nil {
        return fmt.Errorf("failed to create consumer for projection %s: %w", 
            projection.Name, err)
    }
    
    pm.projections[projection.Name] = projection
    log.Printf("Registered projection: %s", projection.Name)
    
    return nil
}

func buildFilterSubject(subjects []string) string {
    if len(subjects) == 1 {
        return subjects[0]
    }
    // For multiple subjects, use the most general pattern
    return "events.>"
}

func (pm *ProjectionManager) StartProjections(ctx context.Context) error {
    pm.mu.RLock()
    defer pm.mu.RUnlock()
    
    for _, projection := range pm.projections {
        if err := pm.startProjection(ctx, projection); err != nil {
            return fmt.Errorf("failed to start projection %s: %w", 
                projection.Name, err)
        }
    }
    
    log.Printf("Started %d projections", len(pm.projections))
    return nil
}

func (pm *ProjectionManager) startProjection(ctx context.Context, 
    projection *Projection) error {
    
    // Create pull subscription
    sub, err := pm.js.PullSubscribe("", projection.ConsumerName)
    if err != nil {
        return fmt.Errorf("failed to create subscription: %w", err)
    }
    
    projection.subscription = sub
    
    // Start processing in goroutine
    go func() {
        defer sub.Unsubscribe()
        
        for {
            select {
            case <-ctx.Done():
                log.Printf("Stopping projection: %s", projection.Name)
                return
            default:
                pm.processBatch(projection)
            }
        }
    }()
    
    return nil
}

func (pm *ProjectionManager) processBatch(projection *Projection) {
    // Fetch batch of messages
    msgs, err := projection.subscription.Fetch(50, nats.MaxWait(1*time.Second))
    if err != nil {
        if err != nats.ErrTimeout {
            log.Printf("Error fetching messages for projection %s: %v", 
                projection.Name, err)
        }
        return
    }
    
    for _, msg := range msgs {
        if err := pm.processMessage(projection, msg); err != nil {
            log.Printf("Error processing message in projection %s: %v", 
                projection.Name, err)
            msg.Nak()
        } else {
            msg.Ack()
        }
    }
}

func (pm *ProjectionManager) processMessage(projection *Projection, 
    msg *nats.Msg) error {
    
    // Deserialize event
    event, err := deserializeEventFromMessage(msg)
    if err != nil {
        return fmt.Errorf("failed to deserialize event: %w", err)
    }
    
    // Check if this event should be processed by this projection
    if !pm.shouldProcessEvent(projection, msg.Subject) {
        return nil
    }
    
    // Handle the event
    if err := projection.Handler.Handle(event); err != nil {
        return fmt.Errorf("handler error: %w", err)
    }
    
    // Update last processed sequence
    projection.mu.Lock()
    meta, _ := msg.Metadata()
    projection.lastProcessed = meta.Sequence.Stream
    projection.mu.Unlock()
    
    return nil
}

func (pm *ProjectionManager) shouldProcessEvent(projection *Projection, 
    subject string) bool {
    
    for _, filter := range projection.FilterSubjects {
        if matchSubject(filter, subject) {
            return true
        }
    }
    return false
}

func matchSubject(pattern, subject string) bool {
    // Simplified subject matching - use nats.subjectMatches in production
    if pattern == ">" || pattern == subject {
        return true
    }
    // Add more sophisticated pattern matching as needed
    return false
}

func deserializeEventFromMessage(msg *nats.Msg) (Event, error) {
    eventType := msg.Header.Get("Event-Type")
    
    var event Event
    switch eventType {
    case "OrderCreated":
        event = &OrderCreated{}
    case "OrderShipped":
        event = &OrderShipped{}
    case "OrderCancelled":
        event = &OrderCancelled{}
    default:
        return nil, fmt.Errorf("unknown event type: %s", eventType)
    }
    
    if err := json.Unmarshal(msg.Data, event); err != nil {
        return nil, err
    }
    
    return event, nil
}

// Example usage
func ExampleCQRS() {
    nc, _ := nats.Connect("nats://localhost:4222")
    js, _ := nc.JetStream()
    
    // Create projection manager
    pm := NewProjectionManager(js)
    
    // Register projections
    orderSummary := NewOrderSummaryProjection()
    customerAnalytics := NewCustomerAnalyticsProjection()
    
    pm.RegisterProjection("event-store", orderSummary, 
        []string{"events.>.OrderCreated", "events.>.OrderShipped", "events.>.OrderCancelled"})
    pm.RegisterProjection("event-store", customerAnalytics, 
        []string{"events.>.OrderCreated"})
    
    // Start processing
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    
    if err := pm.StartProjections(ctx); err != nil {
        log.Fatalf("Failed to start projections: %v", err)
    }
    
    // Example queries
    time.Sleep(5 * time.Second) // Let some events process
    
    // Query order summary
    summary, err := orderSummary.GetOrderSummary("order-123")
    if err != nil {
        log.Printf("Order summary query failed: %v", err)
    } else {
        log.Printf("Order summary: %+v", summary)
    }
    
    // Query customer analytics
    analytics, err := customerAnalytics.GetCustomerAnalytics("customer-456")
    if err != nil {
        log.Printf("Customer analytics query failed: %v", err)
    } else {
        log.Printf("Customer analytics: %+v", analytics)
    }
}

Guaranteed Delivery Mechanisms

JetStream provides multiple mechanisms to ensure message delivery even in failure scenarios.

At-Least-Once and Exactly-Once Delivery

Implementing robust delivery guarantees:

package delivery

import (
    "context"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "log"
    "sync"
    "time"

    "github.com/nats-io/nats.go"
)

type DeliveryGuarantee int

const (
    AtMostOnce DeliveryGuarantee = iota
    AtLeastOnce
    ExactlyOnce
)

type ReliablePublisher struct {
    js              nats.JetStreamContext
    pendingAcks     map[string]*PendingMessage
    mu              sync.RWMutex
    retryInterval   time.Duration
    maxRetries      int
    ackTimeout      time.Duration
}

type PendingMessage struct {
    ID          string
    Subject     string
    Data        []byte
    Headers     nats.Header
    PublishedAt time.Time
    Retries     int
    AckReceived bool
}

func NewReliablePublisher(js nats.JetStreamContext) *ReliablePublisher {
    return &ReliablePublisher{
        js:            js,
        pendingAcks:   make(map[string]*PendingMessage),
        retryInterval: 5 * time.Second,
        maxRetries:    3,
        ackTimeout:    30 * time.Second,
    }
}

func (rp *ReliablePublisher) PublishWithGuarantee(subject string, data []byte, 
    headers nats.Header, guarantee DeliveryGuarantee) error {
    
    switch guarantee {
    case AtMostOnce:
        return rp.publishAtMostOnce(subject, data, headers)
    case AtLeastOnce:
        return rp.publishAtLeastOnce(subject, data, headers)
    case ExactlyOnce:
        return rp.publishExactlyOnce(subject, data, headers)
    default:
        return fmt.Errorf("unsupported delivery guarantee: %d", guarantee)
    }
}

func (rp *ReliablePublisher) publishAtMostOnce(subject string, data []byte, 
    headers nats.Header) error {
    
    // Fire and forget - no retries or acknowledgment tracking
    msg := &nats.Msg{
        Subject: subject,
        Data:    data,
        Header:  headers,
    }
    
    _, err := rp.js.PublishMsg(msg)
    return err
}

func (rp *ReliablePublisher) publishAtLeastOnce(subject string, data []byte, 
    headers nats.Header) error {
    
    messageID := rp.generateMessageID(data)
    
    msg := &nats.Msg{
        Subject: subject,
        Data:    data,
        Header:  headers,
    }
    
    if msg.Header == nil {
        msg.Header = make(nats.Header)
    }
    msg.Header.Set("Message-ID", messageID)
    
    // Track the message for retries
    rp.mu.Lock()
    rp.pendingAcks[messageID] = &PendingMessage{
        ID:          messageID,
        Subject:     subject,
        Data:        data,
        Headers:     headers,
        PublishedAt: time.Now(),
        Retries:     0,
        AckReceived: false,
    }
    rp.mu.Unlock()
    
    // Publish with acknowledgment tracking
    ack, err := rp.js.PublishMsg(msg)
    if err != nil {
        rp.removePendingMessage(messageID)
        return fmt.Errorf("failed to publish message: %w", err)
    }
    
    log.Printf("Published message %s (stream seq: %d)", messageID, ack.Sequence)
    
    // Start retry mechanism in background
    go rp.monitorAcknowledgment(messageID)
    
    return nil
}

func (rp *ReliablePublisher) publishExactlyOnce(subject string, data []byte, 
    headers nats.Header) error {
    
    messageID := rp.generateMessageID(data)
    
    msg := &nats.Msg{
        Subject: subject,
        Data:    data,
        Header:  headers,
    }
    
    if msg.Header == nil {
        msg.Header = make(nats.Header)
    }
    msg.Header.Set("Message-ID", messageID)
    
    // Use JetStream's built-in deduplication
    ack, err := rp.js.PublishMsg(msg, nats.MsgId(messageID))
    if err != nil {
        return fmt.Errorf("failed to publish message: %w", err)
    }
    
    // Check if message was deduplicated
    if ack.Duplicate {
        log.Printf("Message %s was deduplicated", messageID)
    } else {
        log.Printf("Published unique message %s (stream seq: %d)", 
            messageID, ack.Sequence)
    }
    
    return nil
}

func (rp *ReliablePublisher) generateMessageID(data []byte) string {
    hash := sha256.Sum256(data)
    return hex.EncodeToString(hash[:])[:16] // Use first 16 chars
}

func (rp *ReliablePublisher) monitorAcknowledgment(messageID string) {
    ticker := time.NewTicker(rp.retryInterval)
    defer ticker.Stop()
    
    timeout := time.After(rp.ackTimeout)
    
    for {
        select {
        case <-timeout:
            log.Printf("Acknowledgment timeout for message %s", messageID)
            rp.removePendingMessage(messageID)
            return
            
        case <-ticker.C:
            rp.mu.RLock()
            pending, exists := rp.pendingAcks[messageID]
            rp.mu.RUnlock()
            
            if !exists {
                return // Message was acknowledged or removed
            }
            
            if pending.AckReceived {
                rp.removePendingMessage(messageID)
                return
            }
            
            // Retry if max retries not reached
            if pending.Retries < rp.maxRetries {
                rp.retryMessage(pending)
            } else {
                log.Printf("Max retries exceeded for message %s", messageID)
                rp.removePendingMessage(messageID)
                return
            }
        }
    }
}

func (rp *ReliablePublisher) retryMessage(pending *PendingMessage) {
    rp.mu.Lock()
    pending.Retries++
    rp.mu.Unlock()
    
    msg := &nats.Msg{
        Subject: pending.Subject,
        Data:    pending.Data,
        Header:  pending.Headers,
    }
    
    ack, err := rp.js.PublishMsg(msg)
    if err != nil {
        log.Printf("Failed to retry message %s: %v", pending.ID, err)
        return
    }
    
    log.Printf("Retried message %s (attempt %d, stream seq: %d)", 
        pending.ID, pending.Retries, ack.Sequence)
}

func (rp *ReliablePublisher) removePendingMessage(messageID string) {
    rp.mu.Lock()
    delete(rp.pendingAcks, messageID)
    rp.mu.Unlock()
}

// Idempotent consumer for exactly-once processing
type IdempotentConsumer struct {
    js              nats.JetStreamContext
    subscription    *nats.Subscription
    processedMsgs   map[string]bool
    mu              sync.RWMutex
    handler         MessageHandler
}

type MessageHandler interface {
    ProcessMessage(msg *nats.Msg) error
}

func NewIdempotentConsumer(js nats.JetStreamContext, 
    streamName, consumerName string, handler MessageHandler) (*IdempotentConsumer, error) {
    
    // Create or get existing consumer
    consumerConfig := &nats.ConsumerConfig{
        Durable:           consumerName,
        DeliverPolicy:     nats.DeliverAllPolicy,
        AckPolicy:         nats.AckExplicitPolicy,
        MaxDeliver:        3,
        AckWait:           30 * time.Second,
        BackOff:           []time.Duration{1 * time.Second, 5 * time.Second, 10 * time.Second},
        MaxRequestBatch:   10,
        MaxRequestExpires: 5 * time.Second,
    }
    
    _, err := js.AddConsumer(streamName, consumerConfig)
    if err != nil {
        return nil, fmt.Errorf("failed to create consumer: %w", err)
    }
    
    sub, err := js.PullSubscribe("", consumerName)
    if err != nil {
        return nil, fmt.Errorf("failed to create subscription: %w", err)
    }
    
    return &IdempotentConsumer{
        js:            js,
        subscription:  sub,
        processedMsgs: make(map[string]bool),
        handler:       handler,
    }, nil
}

func (ic *IdempotentConsumer) Start(ctx context.Context) error {
    go func() {
        defer ic.subscription.Unsubscribe()
        
        for {
            select {
            case <-ctx.Done():
                log.Println("Stopping idempotent consumer")
                return
            default:
                ic.processBatch()
            }
        }
    }()
    
    return nil
}

func (ic *IdempotentConsumer) processBatch() {
    msgs, err := ic.subscription.Fetch(10, nats.MaxWait(1*time.Second))
    if err != nil {
        if err != nats.ErrTimeout {
            log.Printf("Error fetching messages: %v", err)
        }
        return
    }
    
    for _, msg := range msgs {
        if err := ic.processMessage(msg); err != nil {
            log.Printf("Error processing message: %v", err)
            msg.Nak()
        } else {
            msg.Ack()
        }
    }
}

func (ic *IdempotentConsumer) processMessage(msg *nats.Msg) error {
    messageID := msg.Header.Get("Message-ID")
    if messageID == "" {
        // Generate ID from message content for non-ID'd messages
        messageID = ic.generateMessageID(msg.Data)
    }
    
    // Check if already processed
    ic.mu.RLock()
    alreadyProcessed := ic.processedMsgs[messageID]
    ic.mu.RUnlock()
    
    if alreadyProcessed {
        log.Printf("Skipping already processed message %s", messageID)
        return nil // Skip processing but ack the message
    }
    
    // Process the message
    if err := ic.handler.ProcessMessage(msg); err != nil {
        return fmt.Errorf("handler failed: %w", err)
    }
    
    // Mark as processed
    ic.mu.Lock()
    ic.processedMsgs[messageID] = true
    ic.mu.Unlock()
    
    log.Printf("Successfully processed message %s", messageID)
    return nil
}

func (ic *IdempotentConsumer) generateMessageID(data []byte) string {
    hash := sha256.Sum256(data)
    return hex.EncodeToString(hash[:])[:16]
}

// Circuit breaker for fault tolerance
type CircuitBreaker struct {
    maxFailures      int
    resetTimeout     time.Duration
    failureCount     int
    lastFailureTime  time.Time
    state           CircuitState
    mu              sync.RWMutex
}

type CircuitState int

const (
    CircuitClosed CircuitState = iota
    CircuitOpen
    CircuitHalfOpen
)

func NewCircuitBreaker(maxFailures int, resetTimeout time.Duration) *CircuitBreaker {
    return &CircuitBreaker{
        maxFailures:  maxFailures,
        resetTimeout: resetTimeout,
        state:       CircuitClosed,
    }
}

func (cb *CircuitBreaker) Execute(fn func() error) error {
    cb.mu.Lock()
    defer cb.mu.Unlock()
    
    switch cb.state {
    case CircuitOpen:
        if time.Since(cb.lastFailureTime) > cb.resetTimeout {
            cb.state = CircuitHalfOpen
            cb.failureCount = 0
        } else {
            return fmt.Errorf("circuit breaker is open")
        }
    }
    
    err := fn()
    
    if err != nil {
        cb.failureCount++
        cb.lastFailureTime = time.Now()
        
        if cb.failureCount >= cb.maxFailures {
            cb.state = CircuitOpen
        }
        
        return err
    }
    
    // Success - reset circuit
    cb.failureCount = 0
    cb.state = CircuitClosed
    
    return nil
}

// Example usage with circuit breaker
type ResilientMessageHandler struct {
    circuitBreaker *CircuitBreaker
    actualHandler  MessageHandler
}

func NewResilientMessageHandler(handler MessageHandler) *ResilientMessageHandler {
    return &ResilientMessageHandler{
        circuitBreaker: NewCircuitBreaker(5, 30*time.Second),
        actualHandler:  handler,
    }
}

func (rmh *ResilientMessageHandler) ProcessMessage(msg *nats.Msg) error {
    return rmh.circuitBreaker.Execute(func() error {
        return rmh.actualHandler.ProcessMessage(msg)
    })
}

Clustering and High Availability

Setting up NATS JetStream clusters for production resilience.

JetStream Cluster Configuration

Configuring a highly available JetStream cluster:

# NATS Server Configuration for HA Cluster
server_name: nats-server-1
port: 4222
http_port: 8222

# Cluster configuration
cluster: {
  name: production-cluster
  port: 6222
  
  # Cluster routes for discovery
  routes: [
    nats://nats-server-1:6222
    nats://nats-server-2:6222
    nats://nats-server-3:6222
  ]
  
  # Cluster authentication
  authorization: {
    user: cluster_user
    password: secure_cluster_password
    timeout: 2
  }
  
  # Connection pooling
  pool_size: 10
  accounts: $NATS_ACCOUNTS_FILE
}

# JetStream configuration
jetstream: {
  # Storage directory (use persistent volume in K8s)
  store_dir: "/data/jetstream"
  
  # Memory and storage limits
  max_memory_store: 1GB
  max_file_store: 100GB
  
  # Domain for multi-tenancy
  domain: "production"
  
  # Unique server ID for the cluster
  unique_tag: "server:nats-server-1,zone:us-east-1a,rack:rack1"
}

# Monitoring and metrics
monitoring: {
  http_port: 8222
  https_port: 8223
}

# Logging
log_file: "/var/log/nats/nats-server.log"
logtime: true
log_size_limit: 100MB
max_traced_msg_len: 1024

# TLS Configuration
tls: {
  cert_file: "/etc/nats/server-cert.pem"
  key_file: "/etc/nats/server-key.pem"
  ca_file: "/etc/nats/ca.pem"
  verify: true
  verify_and_map: true
  timeout: 2
}

# Connection limits and timeouts
max_connections: 64K
max_subscriptions: 0
max_pending: 64MB
max_payload: 8MB

# Slow consumer handling
write_deadline: "10s"
max_closed_clients: 10000

# Accounts and security
accounts: $NATS_ACCOUNTS_FILE
system_account: SYS

Kubernetes deployment for the cluster:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nats-jetstream
  namespace: nats-system
spec:
  serviceName: nats-jetstream
  replicas: 3
  selector:
    matchLabels:
      app: nats-jetstream
  template:
    metadata:
      labels:
        app: nats-jetstream
    spec:
      terminationGracePeriodSeconds: 60
      securityContext:
        fsGroup: 1000
        runAsUser: 1000
        runAsNonRoot: true
      containers:
      - name: nats
        image: nats:2.10-alpine
        ports:
        - containerPort: 4222
          name: client
        - containerPort: 6222
          name: cluster
        - containerPort: 8222
          name: monitor
        command:
        - "nats-server"
        - "--config"
        - "/etc/nats-config/nats.conf"
        
        # Resource limits for production
        resources:
          requests:
            cpu: 1000m
            memory: 2Gi
          limits:
            cpu: 2000m
            memory: 4Gi
            
        # Health checks
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8222
          initialDelaySeconds: 10
          timeoutSeconds: 5
          periodSeconds: 30
          failureThreshold: 3
          
        readinessProbe:
          httpGet:
            path: /healthz?js-enabled-only=true
            port: 8222
          initialDelaySeconds: 10
          timeoutSeconds: 5
          periodSeconds: 10
          failureThreshold: 3
        
        # Volume mounts
        volumeMounts:
        - name: config-volume
          mountPath: /etc/nats-config
        - name: jetstream-storage
          mountPath: /data/jetstream
        - name: tls-certs
          mountPath: /etc/nats/certs
        
        # Environment variables
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: SERVER_NAME
          value: $(POD_NAME)
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
              
        # Lifecycle hooks for graceful shutdown
        lifecycle:
          preStop:
            exec:
              command:
              - nats-server
              - --signal
              - quit
      
      volumes:
      - name: config-volume
        configMap:
          name: nats-config
      - name: tls-certs
        secret:
          secretName: nats-tls-certs
          
  # Persistent volume claims for JetStream storage
  volumeClaimTemplates:
  - metadata:
      name: jetstream-storage
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: fast-ssd
      resources:
        requests:
          storage: 100Gi
---
apiVersion: v1
kind: Service
metadata:
  name: nats-jetstream
  namespace: nats-system
spec:
  clusterIP: None
  selector:
    app: nats-jetstream
  ports:
  - name: client
    port: 4222
  - name: cluster
    port: 6222
  - name: monitor
    port: 8222
---
apiVersion: v1
kind: Service
metadata:
  name: nats-jetstream-lb
  namespace: nats-system
spec:
  type: LoadBalancer
  selector:
    app: nats-jetstream
  ports:
  - name: client
    port: 4222
    targetPort: 4222

Disaster Recovery and Backup Strategies

Implementing comprehensive backup and recovery:

package backup

import (
    "archive/tar"
    "compress/gzip"
    "context"
    "fmt"
    "io"
    "os"
    "path/filepath"
    "time"

    "github.com/nats-io/nats.go"
)

type BackupManager struct {
    js           nats.JetStreamContext
    storageDir   string
    backupDir    string
    retention    time.Duration
}

type BackupMetadata struct {
    Timestamp    time.Time           `json:"timestamp"`
    Streams      []StreamBackup      `json:"streams"`
    Consumers    []ConsumerBackup    `json:"consumers"`
    ClusterInfo  ClusterInfo         `json:"cluster_info"`
}

type StreamBackup struct {
    Name     string                 `json:"name"`
    Config   *nats.StreamConfig     `json:"config"`
    State    *nats.StreamState      `json:"state"`
    Messages []BackedUpMessage      `json:"messages,omitempty"`
}

type ConsumerBackup struct {
    StreamName string                 `json:"stream_name"`
    Name       string                 `json:"name"`
    Config     *nats.ConsumerConfig   `json:"config"`
    Info       *nats.ConsumerInfo     `json:"info"`
}

type BackedUpMessage struct {
    Subject   string              `json:"subject"`
    Data      []byte              `json:"data"`
    Header    map[string][]string `json:"header,omitempty"`
    Sequence  uint64              `json:"sequence"`
    Timestamp time.Time           `json:"timestamp"`
}

type ClusterInfo struct {
    Leader   string   `json:"leader"`
    Replicas []string `json:"replicas"`
}

func NewBackupManager(js nats.JetStreamContext, storageDir, backupDir string) *BackupManager {
    return &BackupManager{
        js:         js,
        storageDir: storageDir,
        backupDir:  backupDir,
        retention:  7 * 24 * time.Hour, // 7 days
    }
}

func (bm *BackupManager) CreateFullBackup(ctx context.Context) error {
    timestamp := time.Now()
    backupName := fmt.Sprintf("jetstream-backup-%s", 
        timestamp.Format("2006-01-02-15-04-05"))
    backupPath := filepath.Join(bm.backupDir, backupName)
    
    if err := os.MkdirAll(backupPath, 0755); err != nil {
        return fmt.Errorf("failed to create backup directory: %w", err)
    }
    
    metadata := &BackupMetadata{
        Timestamp: timestamp,
    }
    
    // Backup streams
    if err := bm.backupStreams(ctx, backupPath, metadata); err != nil {
        return fmt.Errorf("failed to backup streams: %w", err)
    }
    
    // Backup consumers
    if err := bm.backupConsumers(ctx, backupPath, metadata); err != nil {
        return fmt.Errorf("failed to backup consumers: %w", err)
    }
    
    // Backup cluster info
    if err := bm.backupClusterInfo(ctx, metadata); err != nil {
        return fmt.Errorf("failed to backup cluster info: %w", err)
    }
    
    // Save metadata
    if err := bm.saveMetadata(backupPath, metadata); err != nil {
        return fmt.Errorf("failed to save metadata: %w", err)
    }
    
    // Create compressed archive
    archivePath := backupPath + ".tar.gz"
    if err := bm.createArchive(backupPath, archivePath); err != nil {
        return fmt.Errorf("failed to create archive: %w", err)
    }
    
    // Clean up uncompressed backup
    if err := os.RemoveAll(backupPath); err != nil {
        log.Printf("Warning: failed to clean up backup directory: %v", err)
    }
    
    log.Printf("Backup completed: %s", archivePath)
    return nil
}

func (bm *BackupManager) backupStreams(ctx context.Context, backupPath string, 
    metadata *BackupMetadata) error {
    
    // List all streams
    streams := bm.js.StreamNames()
    
    for stream := range streams {
        select {
        case <-ctx.Done():
            return ctx.Err()
        default:
        }
        
        streamInfo, err := bm.js.StreamInfo(stream)
        if err != nil {
            return fmt.Errorf("failed to get stream info for %s: %w", stream, err)
        }
        
        streamBackup := StreamBackup{
            Name:   stream,
            Config: &streamInfo.Config,
            State:  &streamInfo.State,
        }
        
        // Backup messages (optional, for smaller streams)
        if streamInfo.State.Msgs < 10000 { // Only backup small streams
            messages, err := bm.backupStreamMessages(ctx, stream)
            if err != nil {
                return fmt.Errorf("failed to backup messages for %s: %w", stream, err)
            }
            streamBackup.Messages = messages
        }
        
        metadata.Streams = append(metadata.Streams, streamBackup)
    }
    
    return nil
}

func (bm *BackupManager) backupStreamMessages(ctx context.Context, streamName string) ([]BackedUpMessage, error) {
    var messages []BackedUpMessage
    
    // Create temporary consumer to read all messages
    consumerConfig := &nats.ConsumerConfig{
        DeliverPolicy: nats.DeliverAllPolicy,
        AckPolicy:     nats.AckNonePolicy,
        ReplayPolicy:  nats.ReplayInstantPolicy,
    }
    
    sub, err := bm.js.PullSubscribe("", "", nats.BindStream(streamName), 
        nats.ConsumerConfig(*consumerConfig))
    if err != nil {
        return nil, fmt.Errorf("failed to create backup subscription: %w", err)
    }
    defer sub.Unsubscribe()
    
    for {
        select {
        case <-ctx.Done():
            return messages, ctx.Err()
        default:
        }
        
        msgs, err := sub.Fetch(100, nats.MaxWait(1*time.Second))
        if err != nil {
            if err == nats.ErrTimeout {
                break // No more messages
            }
            return nil, fmt.Errorf("failed to fetch messages: %w", err)
        }
        
        if len(msgs) == 0 {
            break
        }
        
        for _, msg := range msgs {
            meta, err := msg.Metadata()
            if err != nil {
                continue
            }
            
            backedMsg := BackedUpMessage{
                Subject:   msg.Subject,
                Data:      msg.Data,
                Sequence:  meta.Sequence.Stream,
                Timestamp: meta.Timestamp,
            }
            
            if msg.Header != nil {
                backedMsg.Header = map[string][]string(msg.Header)
            }
            
            messages = append(messages, backedMsg)
        }
    }
    
    return messages, nil
}

func (bm *BackupManager) RestoreFromBackup(ctx context.Context, backupPath string) error {
    // Extract archive
    extractedPath := backupPath[:len(backupPath)-7] // Remove .tar.gz
    if err := bm.extractArchive(backupPath, extractedPath); err != nil {
        return fmt.Errorf("failed to extract archive: %w", err)
    }
    defer os.RemoveAll(extractedPath)
    
    // Load metadata
    metadata, err := bm.loadMetadata(extractedPath)
    if err != nil {
        return fmt.Errorf("failed to load metadata: %w", err)
    }
    
    // Restore streams
    for _, streamBackup := range metadata.Streams {
        if err := bm.restoreStream(ctx, streamBackup); err != nil {
            return fmt.Errorf("failed to restore stream %s: %w", 
                streamBackup.Name, err)
        }
    }
    
    // Restore consumers
    for _, consumerBackup := range metadata.Consumers {
        if err := bm.restoreConsumer(ctx, consumerBackup); err != nil {
            return fmt.Errorf("failed to restore consumer %s: %w", 
                consumerBackup.Name, err)
        }
    }
    
    log.Printf("Restore completed from backup: %s", backupPath)
    return nil
}

func (bm *BackupManager) restoreStream(ctx context.Context, 
    streamBackup StreamBackup) error {
    
    // Create stream with backed up configuration
    _, err := bm.js.AddStream(streamBackup.Config)
    if err != nil {
        return fmt.Errorf("failed to create stream: %w", err)
    }
    
    // Restore messages if available
    if len(streamBackup.Messages) > 0 {
        for _, msg := range streamBackup.Messages {
            select {
            case <-ctx.Done():
                return ctx.Err()
            default:
            }
            
            natsMsg := &nats.Msg{
                Subject: msg.Subject,
                Data:    msg.Data,
            }
            
            if msg.Header != nil {
                natsMsg.Header = nats.Header(msg.Header)
            }
            
            _, err := bm.js.PublishMsg(natsMsg)
            if err != nil {
                log.Printf("Warning: failed to restore message seq %d: %v", 
                    msg.Sequence, err)
            }
        }
    }
    
    log.Printf("Restored stream: %s", streamBackup.Name)
    return nil
}

func (bm *BackupManager) ScheduleBackups(ctx context.Context, interval time.Duration) {
    ticker := time.NewTicker(interval)
    defer ticker.Stop()
    
    for {
        select {
        case <-ctx.Done():
            log.Println("Stopping backup scheduler")
            return
        case <-ticker.C:
            log.Println("Starting scheduled backup")
            if err := bm.CreateFullBackup(ctx); err != nil {
                log.Printf("Scheduled backup failed: %v", err)
            }
            
            // Clean up old backups
            if err := bm.cleanupOldBackups(); err != nil {
                log.Printf("Failed to cleanup old backups: %v", err)
            }
        }
    }
}

func (bm *BackupManager) cleanupOldBackups() error {
    cutoff := time.Now().Add(-bm.retention)
    
    entries, err := os.ReadDir(bm.backupDir)
    if err != nil {
        return fmt.Errorf("failed to read backup directory: %w", err)
    }
    
    for _, entry := range entries {
        if entry.IsDir() {
            continue
        }
        
        info, err := entry.Info()
        if err != nil {
            continue
        }
        
        if info.ModTime().Before(cutoff) {
            backupPath := filepath.Join(bm.backupDir, entry.Name())
            if err := os.Remove(backupPath); err != nil {
                log.Printf("Failed to remove old backup %s: %v", backupPath, err)
            } else {
                log.Printf("Removed old backup: %s", backupPath)
            }
        }
    }
    
    return nil
}

func (bm *BackupManager) createArchive(source, target string) error {
    file, err := os.Create(target)
    if err != nil {
        return err
    }
    defer file.Close()
    
    gzipWriter := gzip.NewWriter(file)
    defer gzipWriter.Close()
    
    tarWriter := tar.NewWriter(gzipWriter)
    defer tarWriter.Close()
    
    return filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }
        
        header, err := tar.FileInfoHeader(info, info.Name())
        if err != nil {
            return err
        }
        
        relPath, err := filepath.Rel(source, path)
        if err != nil {
            return err
        }
        header.Name = relPath
        
        if err := tarWriter.WriteHeader(header); err != nil {
            return err
        }
        
        if info.IsDir() {
            return nil
        }
        
        file, err := os.Open(path)
        if err != nil {
            return err
        }
        defer file.Close()
        
        _, err = io.Copy(tarWriter, file)
        return err
    })
}

// Example usage
func ExampleBackupRestore() {
    nc, _ := nats.Connect("nats://localhost:4222")
    js, _ := nc.JetStream()
    
    backupManager := NewBackupManager(js, "/data/jetstream", "/backups")
    
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    
    // Schedule daily backups
    go backupManager.ScheduleBackups(ctx, 24*time.Hour)
    
    // Create immediate backup
    if err := backupManager.CreateFullBackup(ctx); err != nil {
        log.Fatalf("Backup failed: %v", err)
    }
    
    // Example restore (in disaster recovery scenario)
    // backupPath := "/backups/jetstream-backup-2024-01-15-10-30-00.tar.gz"
    // if err := backupManager.RestoreFromBackup(ctx, backupPath); err != nil {
    //     log.Fatalf("Restore failed: %v", err)
    // }
}

Integration with Microservices

Building robust microservice communication patterns with JetStream.

Service-to-Service Communication Patterns

Implementing reliable communication patterns:

package microservices

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "time"

    "github.com/nats-io/nats.go"
)

// Service interface for microservices
type Service interface {
    Start(ctx context.Context) error
    Stop(ctx context.Context) error
    Health() HealthStatus
}

type HealthStatus struct {
    Status    string            `json:"status"`
    Timestamp time.Time         `json:"timestamp"`
    Details   map[string]string `json:"details"`
}

// Base microservice with JetStream integration
type BaseService struct {
    name         string
    js           nats.JetStreamContext
    nc           *nats.Conn
    subscriptions []*nats.Subscription
    handlers     map[string]MessageHandler
    metrics      *ServiceMetrics
}

type ServiceMetrics struct {
    MessagesProcessed   int64     `json:"messages_processed"`
    MessagesPublished   int64     `json:"messages_published"`
    ErrorCount         int64     `json:"error_count"`
    LastActivity       time.Time `json:"last_activity"`
    AverageProcessTime time.Duration `json:"average_process_time"`
}

func NewBaseService(name string, natsURL string) (*BaseService, error) {
    nc, err := nats.Connect(natsURL,
        nats.RetryOnFailedConnect(true),
        nats.MaxReconnects(5),
        nats.ReconnectWait(2*time.Second),
    )
    if err != nil {
        return nil, fmt.Errorf("failed to connect to NATS: %w", err)
    }
    
    js, err := nc.JetStream()
    if err != nil {
        return nil, fmt.Errorf("failed to create JetStream context: %w", err)
    }
    
    return &BaseService{
        name:     name,
        js:       js,
        nc:       nc,
        handlers: make(map[string]MessageHandler),
        metrics:  &ServiceMetrics{},
    }, nil
}

// Request-Response pattern with timeout
func (bs *BaseService) Request(subject string, data []byte, timeout time.Duration) (*nats.Msg, error) {
    request := &ServiceRequest{
        ID:        generateRequestID(),
        Timestamp: time.Now(),
        Service:   bs.name,
        Data:      data,
    }
    
    requestData, err := json.Marshal(request)
    if err != nil {
        return nil, fmt.Errorf("failed to marshal request: %w", err)
    }
    
    msg, err := bs.nc.Request(subject, requestData, timeout)
    if err != nil {
        return nil, fmt.Errorf("request failed: %w", err)
    }
    
    bs.metrics.MessagesPublished++
    return msg, nil
}

// Publish-Subscribe pattern with guaranteed delivery
func (bs *BaseService) PublishEvent(subject string, event interface{}) error {
    eventData, err := json.Marshal(event)
    if err != nil {
        return fmt.Errorf("failed to marshal event: %w", err)
    }
    
    eventEnvelope := &EventEnvelope{
        ID:        generateEventID(),
        Type:      fmt.Sprintf("%T", event),
        Source:    bs.name,
        Subject:   subject,
        Time:      time.Now(),
        Data:      eventData,
    }
    
    envelopeData, err := json.Marshal(eventEnvelope)
    if err != nil {
        return fmt.Errorf("failed to marshal event envelope: %w", err)
    }
    
    _, err = bs.js.Publish(subject, envelopeData)
    if err != nil {
        return fmt.Errorf("failed to publish event: %w", err)
    }
    
    bs.metrics.MessagesPublished++
    log.Printf("Published event %s to %s", eventEnvelope.ID, subject)
    return nil
}

// Subscribe to events with error handling and retry
func (bs *BaseService) SubscribeToEvents(streamName, consumerName string, 
    subjects []string, handler MessageHandler) error {
    
    // Create or update consumer
    consumerConfig := &nats.ConsumerConfig{
        Durable:           consumerName,
        DeliverPolicy:     nats.DeliverAllPolicy,
        AckPolicy:         nats.AckExplicitPolicy,
        MaxDeliver:        3,
        AckWait:           30 * time.Second,
        BackOff:           []time.Duration{1*time.Second, 5*time.Second, 10*time.Second},
        FilterSubject:     subjects[0], // TODO: support multiple subjects
        MaxRequestBatch:   10,
        MaxRequestExpires: 5 * time.Second,
    }
    
    _, err := bs.js.AddConsumer(streamName, consumerConfig)
    if err != nil {
        return fmt.Errorf("failed to create consumer: %w", err)
    }
    
    sub, err := bs.js.PullSubscribe("", consumerName)
    if err != nil {
        return fmt.Errorf("failed to create subscription: %w", err)
    }
    
    bs.subscriptions = append(bs.subscriptions, sub)
    
    // Start processing messages
    go bs.processMessages(sub, handler)
    
    log.Printf("Subscribed to stream %s with consumer %s", streamName, consumerName)
    return nil
}

func (bs *BaseService) processMessages(sub *nats.Subscription, handler MessageHandler) {
    for {
        msgs, err := sub.Fetch(10, nats.MaxWait(1*time.Second))
        if err != nil {
            if err != nats.ErrTimeout {
                log.Printf("Error fetching messages: %v", err)
            }
            continue
        }
        
        for _, msg := range msgs {
            start := time.Now()
            
            if err := bs.handleMessage(msg, handler); err != nil {
                log.Printf("Error handling message: %v", err)
                bs.metrics.ErrorCount++
                msg.Nak()
            } else {
                msg.Ack()
                bs.metrics.MessagesProcessed++
                
                // Update average processing time
                duration := time.Since(start)
                bs.metrics.AverageProcessTime = 
                    (bs.metrics.AverageProcessTime + duration) / 2
            }
            
            bs.metrics.LastActivity = time.Now()
        }
    }
}

func (bs *BaseService) handleMessage(msg *nats.Msg, handler MessageHandler) error {
    var envelope EventEnvelope
    if err := json.Unmarshal(msg.Data, &envelope); err != nil {
        return fmt.Errorf("failed to unmarshal event envelope: %w", err)
    }
    
    return handler.HandleEvent(&envelope)
}

// Saga pattern for distributed transactions
type SagaOrchestrator struct {
    *BaseService
    sagas map[string]*SagaInstance
    mu    sync.RWMutex
}

type SagaInstance struct {
    ID          string                 `json:"id"`
    Type        string                 `json:"type"`
    State       SagaState              `json:"state"`
    Steps       []SagaStep             `json:"steps"`
    CurrentStep int                    `json:"current_step"`
    Context     map[string]interface{} `json:"context"`
    CreatedAt   time.Time              `json:"created_at"`
    UpdatedAt   time.Time              `json:"updated_at"`
}

type SagaState string

const (
    SagaStarted   SagaState = "started"
    SagaRunning   SagaState = "running"
    SagaCompleted SagaState = "completed"
    SagaFailed    SagaState = "failed"
    SagaAborted   SagaState = "aborted"
)

type SagaStep struct {
    Name            string        `json:"name"`
    Command         string        `json:"command"`
    CompensateCommand string      `json:"compensate_command"`
    Timeout         time.Duration `json:"timeout"`
    RetryCount      int           `json:"retry_count"`
    MaxRetries      int           `json:"max_retries"`
    Status          StepStatus    `json:"status"`
}

type StepStatus string

const (
    StepPending    StepStatus = "pending"
    StepRunning    StepStatus = "running"
    StepCompleted  StepStatus = "completed"
    StepFailed     StepStatus = "failed"
    StepCompensated StepStatus = "compensated"
)

func NewSagaOrchestrator(name string, natsURL string) (*SagaOrchestrator, error) {
    baseService, err := NewBaseService(name, natsURL)
    if err != nil {
        return nil, err
    }
    
    return &SagaOrchestrator{
        BaseService: baseService,
        sagas:       make(map[string]*SagaInstance),
    }, nil
}

func (so *SagaOrchestrator) StartSaga(sagaType string, steps []SagaStep, 
    context map[string]interface{}) (string, error) {
    
    sagaID := generateSagaID()
    
    saga := &SagaInstance{
        ID:          sagaID,
        Type:        sagaType,
        State:       SagaStarted,
        Steps:       steps,
        CurrentStep: 0,
        Context:     context,
        CreatedAt:   time.Now(),
        UpdatedAt:   time.Now(),
    }
    
    so.mu.Lock()
    so.sagas[sagaID] = saga
    so.mu.Unlock()
    
    // Publish saga started event
    event := SagaStartedEvent{
        SagaID:   sagaID,
        SagaType: sagaType,
        Context:  context,
    }
    
    if err := so.PublishEvent("sagas.started", event); err != nil {
        return "", fmt.Errorf("failed to publish saga started event: %w", err)
    }
    
    // Start executing steps
    go so.executeSaga(sagaID)
    
    log.Printf("Started saga %s of type %s", sagaID, sagaType)
    return sagaID, nil
}

func (so *SagaOrchestrator) executeSaga(sagaID string) {
    so.mu.RLock()
    saga, exists := so.sagas[sagaID]
    so.mu.RUnlock()
    
    if !exists {
        log.Printf("Saga not found: %s", sagaID)
        return
    }
    
    saga.State = SagaRunning
    
    for i := saga.CurrentStep; i < len(saga.Steps); i++ {
        step := &saga.Steps[i]
        step.Status = StepRunning
        saga.CurrentStep = i
        saga.UpdatedAt = time.Now()
        
        if err := so.executeStep(saga, step); err != nil {
            log.Printf("Step %s failed in saga %s: %v", step.Name, sagaID, err)
            step.Status = StepFailed
            
            if step.RetryCount < step.MaxRetries {
                step.RetryCount++
                log.Printf("Retrying step %s (attempt %d)", step.Name, step.RetryCount)
                i-- // Retry the same step
                continue
            }
            
            // Start compensation
            go so.compensateSaga(sagaID, i)
            return
        }
        
        step.Status = StepCompleted
        log.Printf("Completed step %s in saga %s", step.Name, sagaID)
    }
    
    // All steps completed successfully
    saga.State = SagaCompleted
    saga.UpdatedAt = time.Now()
    
    // Publish saga completed event
    event := SagaCompletedEvent{
        SagaID:   sagaID,
        SagaType: saga.Type,
        Context:  saga.Context,
    }
    
    so.PublishEvent("sagas.completed", event)
    log.Printf("Saga %s completed successfully", sagaID)
}

func (so *SagaOrchestrator) executeStep(saga *SagaInstance, step *SagaStep) error {
    command := StepCommand{
        SagaID:     saga.ID,
        StepName:   step.Name,
        Command:    step.Command,
        Context:    saga.Context,
        Timestamp:  time.Now(),
    }
    
    response, err := so.Request(step.Command, mustMarshal(command), step.Timeout)
    if err != nil {
        return fmt.Errorf("command execution failed: %w", err)
    }
    
    var result StepResult
    if err := json.Unmarshal(response.Data, &result); err != nil {
        return fmt.Errorf("failed to unmarshal step result: %w", err)
    }
    
    if !result.Success {
        return fmt.Errorf("step failed: %s", result.Error)
    }
    
    // Update saga context with step results
    for k, v := range result.Context {
        saga.Context[k] = v
    }
    
    return nil
}

func (so *SagaOrchestrator) compensateSaga(sagaID string, failedStepIndex int) {
    so.mu.RLock()
    saga, exists := so.sagas[sagaID]
    so.mu.RUnlock()
    
    if !exists {
        log.Printf("Saga not found for compensation: %s", sagaID)
        return
    }
    
    saga.State = SagaFailed
    
    // Execute compensation commands in reverse order
    for i := failedStepIndex - 1; i >= 0; i-- {
        step := &saga.Steps[i]
        
        if step.Status != StepCompleted {
            continue
        }
        
        if step.CompensateCommand == "" {
            log.Printf("No compensation command for step %s", step.Name)
            continue
        }
        
        log.Printf("Compensating step %s in saga %s", step.Name, sagaID)
        
        command := StepCommand{
            SagaID:     sagaID,
            StepName:   step.Name,
            Command:    step.CompensateCommand,
            Context:    saga.Context,
            Timestamp:  time.Now(),
        }
        
        _, err := so.Request(step.CompensateCommand, mustMarshal(command), step.Timeout)
        if err != nil {
            log.Printf("Compensation failed for step %s: %v", step.Name, err)
            saga.State = SagaAborted
            break
        }
        
        step.Status = StepCompensated
    }
    
    saga.UpdatedAt = time.Now()
    
    // Publish saga failed event
    event := SagaFailedEvent{
        SagaID:      sagaID,
        SagaType:    saga.Type,
        FailedStep:  saga.Steps[failedStepIndex].Name,
        Context:     saga.Context,
    }
    
    so.PublishEvent("sagas.failed", event)
    log.Printf("Saga %s compensation completed", sagaID)
}

// Event types for saga orchestration
type SagaStartedEvent struct {
    SagaID   string                 `json:"saga_id"`
    SagaType string                 `json:"saga_type"`
    Context  map[string]interface{} `json:"context"`
}

type SagaCompletedEvent struct {
    SagaID   string                 `json:"saga_id"`
    SagaType string                 `json:"saga_type"`
    Context  map[string]interface{} `json:"context"`
}

type SagaFailedEvent struct {
    SagaID     string                 `json:"saga_id"`
    SagaType   string                 `json:"saga_type"`
    FailedStep string                 `json:"failed_step"`
    Context    map[string]interface{} `json:"context"`
}

type StepCommand struct {
    SagaID    string                 `json:"saga_id"`
    StepName  string                 `json:"step_name"`
    Command   string                 `json:"command"`
    Context   map[string]interface{} `json:"context"`
    Timestamp time.Time              `json:"timestamp"`
}

type StepResult struct {
    Success bool                   `json:"success"`
    Error   string                 `json:"error,omitempty"`
    Context map[string]interface{} `json:"context,omitempty"`
}

// Example order processing saga
func ExampleOrderProcessingSaga() {
    orchestrator, _ := NewSagaOrchestrator("order-orchestrator", "nats://localhost:4222")
    
    steps := []SagaStep{
        {
            Name:               "reserve-inventory",
            Command:            "inventory.reserve",
            CompensateCommand:  "inventory.release",
            Timeout:            30 * time.Second,
            MaxRetries:         3,
        },
        {
            Name:               "process-payment",
            Command:            "payment.process",
            CompensateCommand:  "payment.refund",
            Timeout:            60 * time.Second,
            MaxRetries:         2,
        },
        {
            Name:               "ship-order",
            Command:            "shipping.ship",
            CompensateCommand:  "shipping.cancel",
            Timeout:            120 * time.Second,
            MaxRetries:         1,
        },
    }
    
    context := map[string]interface{}{
        "order_id":    "order-123",
        "customer_id": "customer-456",
        "items":       []string{"item1", "item2"},
        "amount":      99.99,
    }
    
    sagaID, err := orchestrator.StartSaga("order-processing", steps, context)
    if err != nil {
        log.Fatalf("Failed to start saga: %v", err)
    }
    
    log.Printf("Started order processing saga: %s", sagaID)
}

func mustMarshal(v interface{}) []byte {
    data, err := json.Marshal(v)
    if err != nil {
        panic(err)
    }
    return data
}

func generateRequestID() string {
    return fmt.Sprintf("req_%d", time.Now().UnixNano())
}

func generateSagaID() string {
    return fmt.Sprintf("saga_%d", time.Now().UnixNano())
}

Performance Optimization

Optimizing NATS JetStream for maximum throughput and minimal latency.

Benchmarking and Profiling

Comprehensive performance testing framework:

package performance

import (
    "context"
    "fmt"
    "log"
    "runtime"
    "sync"
    "sync/atomic"
    "time"

    "github.com/nats-io/nats.go"
)

type BenchmarkConfig struct {
    Publishers      int
    Consumers       int
    MessageSize     int
    MessageCount    int64
    Duration        time.Duration
    StreamName      string
    Subject         string
    BatchSize       int
    UseCompression  bool
    ReplicationFactor int
}

type BenchmarkResult struct {
    Config              BenchmarkConfig   `json:"config"`
    PublishRate         float64           `json:"publish_rate_msgs_per_sec"`
    ConsumeRate         float64           `json:"consume_rate_msgs_per_sec"`
    AverageLatency      time.Duration     `json:"average_latency"`
    P95Latency          time.Duration     `json:"p95_latency"`
    P99Latency          time.Duration     `json:"p99_latency"`
    TotalMessages       int64             `json:"total_messages"`
    ErrorCount          int64             `json:"error_count"`
    ThroughputMBps      float64           `json:"throughput_mbps"`
    CPUUsage            float64           `json:"cpu_usage_percent"`
    MemoryUsage         int64             `json:"memory_usage_bytes"`
    Duration            time.Duration     `json:"actual_duration"`
}

type LatencyTracker struct {
    samples []time.Duration
    mu      sync.RWMutex
}

func (lt *LatencyTracker) Record(latency time.Duration) {
    lt.mu.Lock()
    lt.samples = append(lt.samples, latency)
    lt.mu.Unlock()
}

func (lt *LatencyTracker) GetPercentile(percentile float64) time.Duration {
    lt.mu.RLock()
    defer lt.mu.RUnlock()
    
    if len(lt.samples) == 0 {
        return 0
    }
    
    // Simple percentile calculation (in production, use proper sorting)
    index := int(float64(len(lt.samples)) * percentile / 100.0)
    if index >= len(lt.samples) {
        index = len(lt.samples) - 1
    }
    
    return lt.samples[index]
}

func (lt *LatencyTracker) GetAverage() time.Duration {
    lt.mu.RLock()
    defer lt.mu.RUnlock()
    
    if len(lt.samples) == 0 {
        return 0
    }
    
    var total time.Duration
    for _, sample := range lt.samples {
        total += sample
    }
    
    return total / time.Duration(len(lt.samples))
}

type JetStreamBenchmark struct {
    js          nats.JetStreamContext
    nc          *nats.Conn
    config      BenchmarkConfig
    latencyTracker *LatencyTracker
    
    publishedCount int64
    consumedCount  int64
    errorCount     int64
    
    startTime time.Time
    endTime   time.Time
}

func NewJetStreamBenchmark(natsURL string, config BenchmarkConfig) (*JetStreamBenchmark, error) {
    // Configure NATS connection for performance
    opts := []nats.Option{
        nats.ReconnectWait(2 * time.Second),
        nats.MaxReconnects(5),
        nats.ReconnectBufSize(16 * 1024 * 1024), // 16MB buffer
        nats.DrainTimeout(30 * time.Second),
        nats.ClosedHandler(func(nc *nats.Conn) {
            log.Printf("NATS connection closed")
        }),
    }
    
    nc, err := nats.Connect(natsURL, opts...)
    if err != nil {
        return nil, fmt.Errorf("failed to connect to NATS: %w", err)
    }
    
    js, err := nc.JetStream()
    if err != nil {
        return nil, fmt.Errorf("failed to create JetStream context: %w", err)
    }
    
    return &JetStreamBenchmark{
        js:             js,
        nc:             nc,
        config:         config,
        latencyTracker: &LatencyTracker{},
    }, nil
}

func (jsb *JetStreamBenchmark) RunBenchmark(ctx context.Context) (*BenchmarkResult, error) {
    // Setup stream for benchmark
    if err := jsb.setupStream(); err != nil {
        return nil, fmt.Errorf("failed to setup stream: %w", err)
    }
    
    jsb.startTime = time.Now()
    
    // Start publishers
    publisherCtx, publisherCancel := context.WithCancel(ctx)
    publisherWg := &sync.WaitGroup{}
    
    for i := 0; i < jsb.config.Publishers; i++ {
        publisherWg.Add(1)
        go jsb.runPublisher(publisherCtx, publisherWg, i)
    }
    
    // Start consumers
    consumerCtx, consumerCancel := context.WithCancel(ctx)
    consumerWg := &sync.WaitGroup{}
    
    for i := 0; i < jsb.config.Consumers; i++ {
        consumerWg.Add(1)
        go jsb.runConsumer(consumerCtx, consumerWg, i)
    }
    
    // Wait for benchmark completion
    timer := time.NewTimer(jsb.config.Duration)
    defer timer.Stop()
    
    select {
    case <-timer.C:
        log.Println("Benchmark duration completed")
    case <-ctx.Done():
        log.Println("Benchmark cancelled")
    }
    
    // Stop publishers and consumers
    publisherCancel()
    consumerCancel()
    
    publisherWg.Wait()
    consumerWg.Wait()
    
    jsb.endTime = time.Now()
    
    return jsb.calculateResults(), nil
}

func (jsb *JetStreamBenchmark) setupStream() error {
    streamConfig := &nats.StreamConfig{
        Name:              jsb.config.StreamName,
        Subjects:          []string{jsb.config.Subject},
        Storage:           nats.FileStorage,
        Replicas:          jsb.config.ReplicationFactor,
        MaxMsgs:           -1,
        MaxBytes:          -1,
        MaxAge:            24 * time.Hour,
        MaxMsgSize:        int32(jsb.config.MessageSize * 2),
        NoAck:             false,
        Discard:           nats.DiscardOld,
        DuplicateWindow:   2 * time.Minute,
    }
    
    if jsb.config.UseCompression {
        streamConfig.Compression = nats.S2Compression
    }
    
    // Delete existing stream if it exists
    jsb.js.DeleteStream(jsb.config.StreamName)
    
    _, err := jsb.js.AddStream(streamConfig)
    return err
}

func (jsb *JetStreamBenchmark) runPublisher(ctx context.Context, wg *sync.WaitGroup, id int) {
    defer wg.Done()
    
    // Generate message payload
    payload := make([]byte, jsb.config.MessageSize)
    for i := range payload {
        payload[i] = byte(i % 256)
    }
    
    ticker := time.NewTicker(time.Millisecond) // Adjust for desired rate
    defer ticker.Stop()
    
    batch := make([]*nats.Msg, 0, jsb.config.BatchSize)
    
    for {
        select {
        case <-ctx.Done():
            // Publish remaining batch
            if len(batch) > 0 {
                jsb.publishBatch(batch)
            }
            return
        case <-ticker.C:
            msg := &nats.Msg{
                Subject: jsb.config.Subject,
                Data:    payload,
                Header:  nats.Header{},
            }
            
            // Add timestamp for latency measurement
            msg.Header.Set("Publish-Time", 
                fmt.Sprintf("%d", time.Now().UnixNano()))
            msg.Header.Set("Publisher-ID", fmt.Sprintf("%d", id))
            
            batch = append(batch, msg)
            
            if len(batch) >= jsb.config.BatchSize {
                jsb.publishBatch(batch)
                batch = batch[:0] // Reset slice
            }
        }
    }
}

func (jsb *JetStreamBenchmark) publishBatch(batch []*nats.Msg) {
    for _, msg := range batch {
        _, err := jsb.js.PublishMsg(msg)
        if err != nil {
            atomic.AddInt64(&jsb.errorCount, 1)
            log.Printf("Publish error: %v", err)
        } else {
            atomic.AddInt64(&jsb.publishedCount, 1)
        }
    }
}

func (jsb *JetStreamBenchmark) runConsumer(ctx context.Context, wg *sync.WaitGroup, id int) {
    defer wg.Done()
    
    consumerName := fmt.Sprintf("benchmark-consumer-%d", id)
    
    // Create consumer
    consumerConfig := &nats.ConsumerConfig{
        Durable:           consumerName,
        DeliverPolicy:     nats.DeliverAllPolicy,
        AckPolicy:         nats.AckExplicitPolicy,
        MaxDeliver:        1,
        AckWait:           5 * time.Second,
        MaxRequestBatch:   jsb.config.BatchSize,
        MaxRequestExpires: 1 * time.Second,
    }
    
    _, err := jsb.js.AddConsumer(jsb.config.StreamName, consumerConfig)
    if err != nil {
        log.Printf("Failed to create consumer: %v", err)
        return
    }
    
    sub, err := jsb.js.PullSubscribe("", consumerName)
    if err != nil {
        log.Printf("Failed to create subscription: %v", err)
        return
    }
    defer sub.Unsubscribe()
    
    for {
        select {
        case <-ctx.Done():
            return
        default:
        }
        
        msgs, err := sub.Fetch(jsb.config.BatchSize, nats.MaxWait(100*time.Millisecond))
        if err != nil {
            if err != nats.ErrTimeout {
                atomic.AddInt64(&jsb.errorCount, 1)
            }
            continue
        }
        
        for _, msg := range msgs {
            // Calculate latency
            publishTimeStr := msg.Header.Get("Publish-Time")
            if publishTimeStr != "" {
                var publishTime int64
                if _, err := fmt.Sscanf(publishTimeStr, "%d", &publishTime); err == nil {
                    latency := time.Since(time.Unix(0, publishTime))
                    jsb.latencyTracker.Record(latency)
                }
            }
            
            msg.Ack()
            atomic.AddInt64(&jsb.consumedCount, 1)
        }
    }
}

func (jsb *JetStreamBenchmark) calculateResults() *BenchmarkResult {
    duration := jsb.endTime.Sub(jsb.startTime)
    publishedCount := atomic.LoadInt64(&jsb.publishedCount)
    consumedCount := atomic.LoadInt64(&jsb.consumedCount)
    errorCount := atomic.LoadInt64(&jsb.errorCount)
    
    publishRate := float64(publishedCount) / duration.Seconds()
    consumeRate := float64(consumedCount) / duration.Seconds()
    
    throughputBytes := float64(consumedCount * int64(jsb.config.MessageSize))
    throughputMBps := (throughputBytes / duration.Seconds()) / (1024 * 1024)
    
    // Get memory stats
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    
    return &BenchmarkResult{
        Config:         jsb.config,
        PublishRate:    publishRate,
        ConsumeRate:    consumeRate,
        AverageLatency: jsb.latencyTracker.GetAverage(),
        P95Latency:     jsb.latencyTracker.GetPercentile(95),
        P99Latency:     jsb.latencyTracker.GetPercentile(99),
        TotalMessages:  publishedCount,
        ErrorCount:     errorCount,
        ThroughputMBps: throughputMBps,
        MemoryUsage:    int64(m.Alloc),
        Duration:       duration,
    }
}

// Performance optimization recommendations
func (jsb *JetStreamBenchmark) GenerateOptimizationReport(result *BenchmarkResult) string {
    report := fmt.Sprintf(`
JetStream Performance Benchmark Report
=====================================

Configuration:
- Publishers: %d
- Consumers: %d
- Message Size: %d bytes
- Replication Factor: %d
- Compression: %v

Results:
- Publish Rate: %.2f msgs/sec
- Consume Rate: %.2f msgs/sec
- Throughput: %.2f MB/s
- Average Latency: %v
- P95 Latency: %v
- P99 Latency: %v
- Error Rate: %.2f%%

Optimization Recommendations:
`, 
        result.Config.Publishers,
        result.Config.Consumers,
        result.Config.MessageSize,
        result.Config.ReplicationFactor,
        result.Config.UseCompression,
        result.PublishRate,
        result.ConsumeRate,
        result.ThroughputMBps,
        result.AverageLatency,
        result.P95Latency,
        result.P99Latency,
        float64(result.ErrorCount)/float64(result.TotalMessages)*100,
    )
    
    // Generate specific recommendations
    if result.PublishRate < 1000 {
        report += "- Consider increasing batch size for publishers\n"
        report += "- Optimize message serialization\n"
        report += "- Check network latency between clients and NATS\n"
    }
    
    if result.AverageLatency > 10*time.Millisecond {
        report += "- Consider using memory storage for low-latency scenarios\n"
        report += "- Reduce replication factor if data loss is acceptable\n"
        report += "- Optimize consumer acknowledgment strategy\n"
    }
    
    if result.ThroughputMBps < 100 {
        report += "- Enable compression for large messages\n"
        report += "- Use larger message batches\n"
        report += "- Consider message aggregation patterns\n"
    }
    
    if result.Config.ReplicationFactor > 1 && result.PublishRate < 5000 {
        report += "- High replication factor may be limiting throughput\n"
        report += "- Consider async replication for non-critical data\n"
    }
    
    return report
}

// Example benchmark execution
func ExampleBenchmark() {
    config := BenchmarkConfig{
        Publishers:        5,
        Consumers:         3,
        MessageSize:       1024,
        Duration:          60 * time.Second,
        StreamName:        "benchmark-stream",
        Subject:           "benchmark.test",
        BatchSize:         10,
        UseCompression:    true,
        ReplicationFactor: 3,
    }
    
    benchmark, err := NewJetStreamBenchmark("nats://localhost:4222", config)
    if err != nil {
        log.Fatalf("Failed to create benchmark: %v", err)
    }
    
    ctx, cancel := context.WithTimeout(context.Background(), 70*time.Second)
    defer cancel()
    
    result, err := benchmark.RunBenchmark(ctx)
    if err != nil {
        log.Fatalf("Benchmark failed: %v", err)
    }
    
    report := benchmark.GenerateOptimizationReport(result)
    fmt.Println(report)
    
    // Save results for comparison
    resultJSON, _ := json.MarshalIndent(result, "", "  ")
    fmt.Printf("Detailed Results:\n%s\n", resultJSON)
}

Conclusion

NATS JetStream provides a powerful foundation for building modern event-driven architectures. Its unique combination of simplicity, performance, and reliability makes it an excellent choice for microservices communication, real-time data processing, and distributed system coordination.

Key takeaways from this comprehensive guide:

  1. Architecture Flexibility: JetStream’s stream and consumer model provides the flexibility to implement various messaging patterns from simple pub-sub to complex event sourcing and CQRS architectures.

  2. Guaranteed Delivery: Multiple delivery guarantees (at-most-once, at-least-once, exactly-once) enable you to choose the right trade-off between performance and reliability for each use case.

  3. High Availability: Built-in clustering, replication, and backup capabilities ensure your event streaming infrastructure can handle production demands with minimal downtime.

  4. Performance Optimization: Through careful tuning of configuration parameters, batching strategies, and resource allocation, JetStream can achieve exceptional throughput and low latency.

  5. Microservices Integration: Rich patterns for service-to-service communication, including request-response, pub-sub, and distributed transactions via sagas, simplify building resilient distributed systems.

Best practices for production deployment:

  • Start with simple configurations and optimize based on actual performance requirements
  • Implement comprehensive monitoring and alerting for early issue detection
  • Use proper backup and disaster recovery procedures
  • Consider message schema evolution and backward compatibility
  • Plan for capacity growth and implement proper resource management

As event-driven architectures continue to evolve, NATS JetStream positions itself as a mature, scalable solution that can grow with your organization’s needs while maintaining operational simplicity.