142 lines
3.8 KiB
Go
142 lines
3.8 KiB
Go
package nats
|
|
|
|
import (
|
|
"caatsm/internal/infra/config"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// StreamManager handles JetStream stream lifecycle management
|
|
type StreamManager struct {
|
|
js nats.JetStreamContext
|
|
streamName string
|
|
subjects []string
|
|
logger *zap.Logger
|
|
cfg *config.StreamLimitsConfig
|
|
}
|
|
|
|
// NewStreamManager creates a new stream manager
|
|
func NewStreamManager(js nats.JetStreamContext, streamName string, subjects []string, logger *zap.Logger) *StreamManager {
|
|
return &StreamManager{
|
|
js: js,
|
|
streamName: streamName,
|
|
subjects: subjects,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// NewStreamManagerWithConfig creates a new stream manager with full stream configuration
|
|
func NewStreamManagerWithConfig(js nats.JetStreamContext, streamName string, subjects []string, streamLimits *config.StreamLimitsConfig, logger *zap.Logger) *StreamManager {
|
|
return &StreamManager{
|
|
js: js,
|
|
streamName: streamName,
|
|
subjects: subjects,
|
|
logger: logger,
|
|
cfg: streamLimits,
|
|
}
|
|
}
|
|
|
|
// EnsureStream ensures that the configured JetStream stream exists
|
|
func (sm *StreamManager) EnsureStream() error {
|
|
// Build stream configuration
|
|
streamConfig := sm.buildStreamConfig()
|
|
|
|
info, err := sm.js.StreamInfo(sm.streamName)
|
|
if err != nil {
|
|
if errors.Is(err, nats.ErrStreamNotFound) {
|
|
if shouldBootstrapStream() {
|
|
if _, err = sm.js.AddStream(streamConfig); err != nil {
|
|
sm.logger.Error("failed to create stream",
|
|
zap.String("stream", sm.streamName),
|
|
zap.Strings("subjects", sm.subjects),
|
|
zap.Error(err),
|
|
)
|
|
return fmt.Errorf("failed to create stream %s: %w", sm.streamName, err)
|
|
}
|
|
sm.logger.Info("Created JetStream stream",
|
|
zap.String("stream", sm.streamName),
|
|
zap.Strings("subjects", sm.subjects),
|
|
)
|
|
return nil
|
|
}
|
|
sm.logger.Error("stream not found and auto-creation disabled",
|
|
zap.String("stream", sm.streamName),
|
|
zap.Strings("expected_subjects", sm.subjects),
|
|
)
|
|
return fmt.Errorf("stream %s not found and auto-creation disabled", sm.streamName)
|
|
}
|
|
sm.logger.Error("failed to fetch stream info",
|
|
zap.String("stream", sm.streamName),
|
|
zap.Error(err),
|
|
)
|
|
return fmt.Errorf("failed to fetch stream info for %s: %w", sm.streamName, err)
|
|
}
|
|
|
|
// Stream exists: validate subjects but do not fail hard if they differ.
|
|
sm.validateStreamConfig(info)
|
|
return nil
|
|
}
|
|
|
|
// buildStreamConfig builds the stream configuration from manager settings
|
|
func (sm *StreamManager) buildStreamConfig() *nats.StreamConfig {
|
|
config := &nats.StreamConfig{
|
|
Name: sm.streamName,
|
|
Subjects: sm.subjects,
|
|
Retention: nats.LimitsPolicy,
|
|
Storage: nats.FileStorage,
|
|
}
|
|
|
|
// Apply stream limits configuration if provided
|
|
if sm.cfg != nil {
|
|
config.MaxMsgs = sm.cfg.MaxMsgs
|
|
config.MaxBytes = sm.cfg.MaxBytes
|
|
config.MaxAge = sm.cfg.MaxAge
|
|
config.Replicas = sm.cfg.Replicas
|
|
|
|
// Map storage type
|
|
switch strings.ToLower(sm.cfg.Storage) {
|
|
case "memory":
|
|
config.Storage = nats.MemoryStorage
|
|
case "file":
|
|
config.Storage = nats.FileStorage
|
|
}
|
|
|
|
// Map discard policy
|
|
if strings.EqualFold(sm.cfg.Discard, "new") {
|
|
config.Discard = nats.DiscardNew
|
|
} else {
|
|
config.Discard = nats.DiscardOld
|
|
}
|
|
}
|
|
|
|
return config
|
|
}
|
|
|
|
// validateStreamConfig validates the stream configuration
|
|
func (sm *StreamManager) validateStreamConfig(info *nats.StreamInfo) {
|
|
if info == nil {
|
|
return
|
|
}
|
|
|
|
missing := make([]string, 0)
|
|
for _, subj := range sm.subjects {
|
|
if subj == "" {
|
|
continue
|
|
}
|
|
if !containsSubject(info.Config.Subjects, subj) {
|
|
missing = append(missing, subj)
|
|
}
|
|
}
|
|
if len(missing) > 0 {
|
|
sm.logger.Warn("JetStream stream subjects missing expected entries",
|
|
zap.String("stream", info.Config.Name),
|
|
zap.Strings("stream_subjects", info.Config.Subjects),
|
|
zap.Strings("missing_subjects", missing),
|
|
)
|
|
}
|
|
}
|