# Stream Configuration Per-stream configuration for fine-grained control over retention, dead letter queues, lifecycle, performance, and access control. ## Overview Stream configuration allows you to customize behavior on a per-stream basis, enabling: - **Retention Policies** - Time, size, and count-based retention per stream - **Dead Letter Queues** - Error handling and retry logic - **Lifecycle Management** - Automatic archival and deletion - **Performance Tuning** - Batch sizes, compression, indexing - **Access Control** - Stream-level permissions and rate limits **Key Features:** - ✅ **Per-Stream Settings** - Override global configuration per stream - ✅ **Retention Control** - MaxAge, MaxSizeBytes, MaxEventCount - ✅ **DLQ Configuration** - Automatic retry and dead letter handling - ✅ **Auto-Archival** - Move old events to cold storage - ✅ **Performance Options** - Batching, compression, indexing - ✅ **Access Control** - Stream-level permissions - ✅ **Tag-Based Filtering** - Organize streams by tags ## Quick Start ```csharp using Svrnty.CQRS.Events.Abstractions; using Svrnty.CQRS.Events.PostgreSQL; var builder = WebApplication.CreateBuilder(args); // Register stream configuration builder.Services.AddPostgresStreamConfiguration(); var app = builder.Build(); // Configure stream var configStore = app.Services.GetRequiredService(); await configStore.SetConfigurationAsync(new StreamConfiguration { StreamName = "orders", Retention = new RetentionConfiguration { MaxAge = TimeSpan.FromDays(90), MaxEventCount = 1000000 }, DeadLetterQueue = new DeadLetterQueueConfiguration { Enabled = true, MaxDeliveryAttempts = 5 } }); app.Run(); ``` ## Configuration Components ### Retention Configuration Control event retention per stream: ```csharp var retention = new RetentionConfiguration { MaxAge = TimeSpan.FromDays(30), // Keep 30 days MaxSizeBytes = 10L * 1024 * 1024 * 1024, // 10 GB limit MaxEventCount = 1000000, // Keep last 1M events EnablePartitioning = true, // Partition by time PartitionInterval = PartitionInterval.Daily }; ``` [Learn more about Retention Configuration →](retention-config.md) ### Dead Letter Queue Configuration Handle failed events automatically: ```csharp var dlq = new DeadLetterQueueConfiguration { Enabled = true, DeadLetterStreamName = "orders-dlq", // Custom DLQ stream MaxDeliveryAttempts = 5, // Retry 5 times RetryDelay = TimeSpan.FromMinutes(5), // Wait 5 min between retries EnableExponentialBackoff = true // Exponential retry delays }; ``` [Learn more about Dead Letter Queues →](dead-letter-queues.md) ### Lifecycle Configuration Automate archival and cleanup: ```csharp var lifecycle = new LifecycleConfiguration { AutoCreate = true, // Create stream on first append AutoArchive = true, // Enable archival ArchiveAfter = TimeSpan.FromDays(365), // Archive after 1 year ArchiveLocation = "s3://archive/orders", // S3 bucket AutoDelete = true, // Delete after archive DeleteAfter = TimeSpan.FromDays(400) // Delete 400 days old }; ``` [Learn more about Lifecycle Configuration →](lifecycle-config.md) ### Performance Configuration Optimize stream performance: ```csharp var performance = new PerformanceConfiguration { BatchSize = 1000, // Read 1000 events per query EnableCompression = true, // Compress event data CompressionAlgorithm = "gzip", EnableIndexing = true, // Index metadata fields IndexedFields = new List { "userId", "tenantId" }, CacheSize = 10000 // Cache last 10k events }; ``` [Learn more about Performance Configuration →](performance-config.md) ### Access Control Configuration Control stream access: ```csharp var accessControl = new AccessControlConfiguration { PublicRead = false, // Require authentication PublicWrite = false, AllowedReaders = new List { "admin", "order-service" }, AllowedWriters = new List { "order-service" }, MaxConsumerGroups = 10, // Limit consumer groups MaxEventsPerSecond = 10000 // Rate limit writes }; ``` [Learn more about Access Control →](access-control.md) ## Complete Configuration Example ```csharp using Svrnty.CQRS.Events.Abstractions; // High-volume production stream var orderConfig = new StreamConfiguration { StreamName = "orders", Description = "Production order events", Tags = new List { "production", "critical", "orders" }, Retention = new RetentionConfiguration { MaxAge = TimeSpan.FromDays(90), MaxSizeBytes = 50L * 1024 * 1024 * 1024, // 50 GB MaxEventCount = 10000000, EnablePartitioning = true, PartitionInterval = PartitionInterval.Daily }, DeadLetterQueue = new DeadLetterQueueConfiguration { Enabled = true, DeadLetterStreamName = "orders-dlq", MaxDeliveryAttempts = 5, RetryDelay = TimeSpan.FromMinutes(5), EnableExponentialBackoff = true }, Lifecycle = new LifecycleConfiguration { AutoCreate = true, AutoArchive = true, ArchiveAfter = TimeSpan.FromDays(90), ArchiveLocation = "s3://prod-archive/orders", AutoDelete = false // Keep in archive indefinitely }, Performance = new PerformanceConfiguration { BatchSize = 1000, EnableCompression = true, CompressionAlgorithm = "gzip", EnableIndexing = true, IndexedFields = new List { "userId", "orderId", "tenantId" }, CacheSize = 100000 }, AccessControl = new AccessControlConfiguration { PublicRead = false, PublicWrite = false, AllowedReaders = new List { "admin", "order-service", "analytics-service" }, AllowedWriters = new List { "order-service" }, MaxConsumerGroups = 20, MaxEventsPerSecond = 50000 } }; await configStore.SetConfigurationAsync(orderConfig); ``` ## Getting Effective Configuration Merge stream-specific and global settings: ```csharp var configProvider = serviceProvider.GetRequiredService(); // Get effective configuration (stream-specific merged with defaults) var effectiveConfig = await configProvider.GetEffectiveConfigurationAsync("orders"); Console.WriteLine($"Retention: {effectiveConfig.Retention.MaxAge}"); Console.WriteLine($"DLQ Enabled: {effectiveConfig.DeadLetterQueue.Enabled}"); Console.WriteLine($"Batch Size: {effectiveConfig.Performance.BatchSize}"); ``` ## Configuration Precedence Configuration is resolved in this order: 1. **Stream-Specific Configuration** - Highest priority 2. **Global Configuration** - Fallback for missing values 3. **Framework Defaults** - Built-in defaults ```csharp // Example: Stream-specific overrides global // Global: MaxAge = 30 days // Stream "orders": MaxAge = 90 days // Effective for "orders": MaxAge = 90 days // Global: BatchSize = 100 // Stream "orders": BatchSize not set // Effective for "orders": BatchSize = 100 (from global) ``` ## Managing Configuration ### Set Configuration ```csharp var config = new StreamConfiguration { StreamName = "analytics", Retention = new RetentionConfiguration { MaxAge = TimeSpan.FromDays(7) } }; await configStore.SetConfigurationAsync(config); ``` ### Get Configuration ```csharp var config = await configStore.GetConfigurationAsync("analytics"); if (config == null) { Console.WriteLine("No configuration found, using defaults"); } ``` ### Update Configuration ```csharp var config = await configStore.GetConfigurationAsync("analytics"); if (config != null) { config.Retention.MaxAge = TimeSpan.FromDays(14); // Update retention await configStore.SetConfigurationAsync(config); } ``` ### Delete Configuration ```csharp await configStore.DeleteConfigurationAsync("analytics"); // Stream will now use global configuration ``` ## Configuration by Environment Different settings for dev vs production: ```csharp var environment = builder.Environment.EnvironmentName; var retention = environment == "Production" ? new RetentionConfiguration { MaxAge = TimeSpan.FromDays(90) } : new RetentionConfiguration { MaxAge = TimeSpan.FromDays(7) }; var config = new StreamConfiguration { StreamName = "orders", Retention = retention }; await configStore.SetConfigurationAsync(config); ``` ## Multi-Tenant Configuration Per-tenant stream configuration: ```csharp // Tenant A - high retention await configStore.SetConfigurationAsync(new StreamConfiguration { StreamName = "tenant-a-orders", Retention = new RetentionConfiguration { MaxAge = TimeSpan.FromDays(365) }, Tags = new List { "tenant-a", "premium" } }); // Tenant B - standard retention await configStore.SetConfigurationAsync(new StreamConfiguration { StreamName = "tenant-b-orders", Retention = new RetentionConfiguration { MaxAge = TimeSpan.FromDays(90) }, Tags = new List { "tenant-b", "standard" } }); ``` ## Querying by Tags ```csharp // Find all production streams var prodStreams = await configStore.GetStreamsByTagAsync("production"); foreach (var config in prodStreams) { Console.WriteLine($"Production stream: {config.StreamName}"); } ``` ## Best Practices ### ✅ DO - Use tags to organize streams - Set retention appropriate for data type - Enable DLQ for critical streams - Configure archival for compliance - Use compression for large events - Index fields used in queries - Limit consumer groups per stream - Test configuration in development first ### ❌ DON'T - Don't use the same configuration for all streams - Don't set retention too short for audit logs - Don't disable DLQ for critical streams - Don't forget to configure archival location - Don't over-index (impacts write performance) - Don't allow unlimited consumer groups - Don't forget environment-specific settings ## See Also - [Retention Configuration](retention-config.md) - [Dead Letter Queues](dead-letter-queues.md) - [Lifecycle Configuration](lifecycle-config.md) - [Performance Configuration](performance-config.md) - [Access Control](access-control.md) - [Retention Policies](../retention-policies/README.md) - [Event Streaming Overview](../README.md)