12 KiB
Event Broker System Implementation Plan
Overview
Implement a comprehensive event handler/broker system for ResolveSpec that follows existing architectural patterns (Provider interface, Hook system, Config management, Graceful shutdown).
Requirements Met
- ✅ Events with sources (database, websocket, frontend, system)
- ✅ Event statuses (pending, processing, completed, failed)
- ✅ Timestamps, JSON payloads, user IDs, session IDs
- ✅ Program instance IDs for tracking server instances
- ✅ Both sync and async processing modes
- ✅ Multiple provider backends (in-memory, Redis, NATS, database)
- ✅ Cross-instance pub/sub support
Architecture
Core Components
Event Structure (with full metadata):
type Event struct {
ID string // UUID
Source EventSource // database, websocket, system, frontend
Type string // Pattern: schema.entity.operation
Status EventStatus // pending, processing, completed, failed
Payload json.RawMessage // JSON payload
UserID int
SessionID string
InstanceID string // Server instance identifier
Schema string
Entity string
Operation string // create, update, delete, read
CreatedAt time.Time
ProcessedAt *time.Time
CompletedAt *time.Time
Error string
Metadata map[string]interface{}
RetryCount int
}
Provider Pattern (like cache.Provider):
type Provider interface {
Store(ctx context.Context, event *Event) error
Get(ctx context.Context, id string) (*Event, error)
List(ctx context.Context, filter *EventFilter) ([]*Event, error)
UpdateStatus(ctx context.Context, id string, status EventStatus, error string) error
Stream(ctx context.Context, pattern string) (<-chan *Event, error)
Publish(ctx context.Context, event *Event) error
Close() error
Stats(ctx context.Context) (*ProviderStats, error)
}
Broker Interface:
type Broker interface {
Publish(ctx context.Context, event *Event) error // Mode-dependent
PublishSync(ctx context.Context, event *Event) error // Blocks
PublishAsync(ctx context.Context, event *Event) error // Non-blocking
Subscribe(pattern string, handler EventHandler) (SubscriptionID, error)
Unsubscribe(id SubscriptionID) error
Start(ctx context.Context) error
Stop(ctx context.Context) error
Stats(ctx context.Context) (*BrokerStats, error)
}
Implementation Steps
Phase 1: Core Foundation (Files: 1-5)
1. Create pkg/eventbroker/event.go
- Event struct with all required fields (status, timestamps, user, instance ID, etc.)
- EventSource enum (database, websocket, frontend, system, internal)
- EventStatus enum (pending, processing, completed, failed)
- Helper:
EventType(schema, entity, operation string) string - Helper:
NewEvent()constructor with UUID generation
2. Create pkg/eventbroker/provider.go
- Provider interface definition
- EventFilter struct for queries
- ProviderStats struct
3. Create pkg/eventbroker/handler.go
- EventHandler interface
- EventHandlerFunc adapter type
4. Create pkg/eventbroker/broker.go
- Broker interface definition
- EventBroker struct implementation
- ProcessingMode enum (sync, async)
- Options struct with functional options (WithProvider, WithMode, WithWorkerCount, etc.)
- NewBroker() constructor
- Sync processing implementation
5. Create pkg/eventbroker/subscription.go
- Pattern matching using glob syntax (e.g., "public.users.", ".*.create")
- subscriptionManager struct
- SubscriptionID type
- Subscribe/Unsubscribe logic
Phase 2: Configuration & Integration (Files: 6-8)
6. Create pkg/eventbroker/config.go
- EventBrokerConfig struct
- RedisConfig, NATSConfig, DatabaseConfig structs
- RetryPolicyConfig struct
7. Update pkg/config/config.go
- Add
EventBroker EventBrokerConfigfield to Config struct
8. Update pkg/config/manager.go
- Add event broker defaults to
setDefaults():v.SetDefault("event_broker.enabled", false) v.SetDefault("event_broker.provider", "memory") v.SetDefault("event_broker.mode", "async") v.SetDefault("event_broker.worker_count", 10) v.SetDefault("event_broker.buffer_size", 1000)
Phase 3: Memory Provider (Files: 9)
9. Create pkg/eventbroker/provider_memory.go
- MemoryProvider struct with mutex-protected map
- In-memory event storage
- Pattern matching for subscriptions
- Channel-based streaming for real-time events
- LRU eviction when max size reached
- Cleanup goroutine for old completed events
- Note: Single-instance only (no cross-instance pub/sub)
Phase 4: Async Processing (Update File: 4)
10. Update pkg/eventbroker/broker.go (add async support)
- workerPool struct with configurable worker count
- Buffered channel for event queue
- Worker goroutines that process events
- PublishAsync() queues to channel
- Graceful shutdown: stop accepting events, drain queue, wait for workers
- Retry logic with exponential backoff
Phase 5: Hook Integration (Files: 11)
11. Create pkg/eventbroker/hooks.go
RegisterCRUDHooks(broker Broker, hookRegistry *restheadspec.HookRegistry)- Registers AfterCreate, AfterUpdate, AfterDelete, AfterRead hooks
- Extracts UserContext from hook context
- Creates Event with proper metadata
- Calls
broker.PublishAsync()to not block CRUD operations
Phase 6: Global Singleton & Factory (Files: 12-13)
12. Create pkg/eventbroker/eventbroker.go
- Global
defaultBrokervariable Initialize(config *config.Config) error- creates broker from configSetDefaultBroker(broker Broker)GetDefaultBroker() Broker- Helper functions:
Publish(),PublishAsync(),PublishSync(),Subscribe() RegisterShutdown(broker Broker)- registers with server.RegisterShutdownCallback()
13. Create pkg/eventbroker/factory.go
NewProviderFromConfig(config EventBrokerConfig) (Provider, error)- Provider selection logic (memory, redis, nats, database)
- Returns appropriate provider based on config
Phase 7: Redis Provider (Files: 14)
14. Create pkg/eventbroker/provider_redis.go
- RedisProvider using Redis Streams (XADD, XREAD, XGROUP)
- Consumer group for distributed processing
- Cross-instance pub/sub support
- Stream(pattern) subscribes to consumer group
- Publish() uses XADD to append to stream
- Graceful shutdown: acknowledge pending messages
Dependencies: github.com/redis/go-redis/v9
Phase 8: NATS Provider (Files: 15)
15. Create pkg/eventbroker/provider_nats.go
- NATSProvider using NATS JetStream
- Subject-based routing:
events.{source}.{type} - Wildcard subscriptions support
- Durable consumers for replay
- At-least-once delivery semantics
Dependencies: github.com/nats-io/nats.go
Phase 9: Database Provider (Files: 16)
16. Create pkg/eventbroker/provider_database.go
- DatabaseProvider using
common.Databaseinterface - Table schema creation (events table with indexes)
- Polling-based event consumption (configurable interval)
- Full SQL query support via List(filter)
- Transaction support for atomic operations
- Good for audit trails and debugging
Phase 10: Metrics Integration (Files: 17)
17. Create pkg/eventbroker/metrics.go
- Integrate with existing
metrics.Provider - Record metrics:
eventbroker_events_published_total{source, type}eventbroker_events_processed_total{source, type, status}eventbroker_event_processing_duration_seconds{source, type}eventbroker_queue_sizeeventbroker_workers_active
18. Update pkg/metrics/interfaces.go
- Add methods to Provider interface:
RecordEventPublished(source, eventType string) RecordEventProcessed(source, eventType, status string, duration time.Duration) UpdateEventQueueSize(size int64)
Phase 11: Testing & Examples (Files: 19-20)
19. Create pkg/eventbroker/eventbroker_test.go
- Unit tests for Event marshaling
- Pattern matching tests
- MemoryProvider tests
- Sync vs async mode tests
- Concurrent publish/subscribe tests
- Graceful shutdown tests
20. Create pkg/eventbroker/example_usage.go
- Basic publish example
- Subscribe with patterns example
- Hook integration example
- Multiple handlers example
- Error handling example
Integration Points
Hook System Integration
// In application initialization (e.g., main.go)
eventbroker.RegisterCRUDHooks(broker, handler.Hooks())
This automatically publishes events for all CRUD operations:
schema.entity.createafter insertsschema.entity.updateafter updatesschema.entity.deleteafter deletesschema.entity.readafter reads
Shutdown Integration
// In application initialization
eventbroker.RegisterShutdown(broker)
Ensures event broker flushes pending events before shutdown.
Configuration Example
event_broker:
enabled: true
provider: redis # memory, redis, nats, database
mode: async # sync, async
worker_count: 10
buffer_size: 1000
instance_id: "${HOSTNAME}"
redis:
stream_name: "resolvespec:events"
consumer_group: "resolvespec-workers"
host: "localhost"
port: 6379
Usage Examples
Publishing Custom Events
// WebSocket event
event := &eventbroker.Event{
Source: eventbroker.EventSourceWebSocket,
Type: "chat.message",
Payload: json.RawMessage(`{"room": "lobby", "msg": "Hello"}`),
UserID: userID,
SessionID: sessionID,
}
eventbroker.PublishAsync(ctx, event)
Subscribing to Events
// Subscribe to all user creation events
eventbroker.Subscribe("public.users.create", eventbroker.EventHandlerFunc(
func(ctx context.Context, event *eventbroker.Event) error {
log.Printf("New user created: %s", event.Payload)
// Send welcome email, update cache, etc.
return nil
},
))
// Subscribe to all events from database
eventbroker.Subscribe("*", eventbroker.EventHandlerFunc(
func(ctx context.Context, event *eventbroker.Event) error {
if event.Source == eventbroker.EventSourceDatabase {
// Audit logging
}
return nil
},
))
Critical Files Reference
Patterns to Follow:
pkg/cache/provider.go- Provider interface patternpkg/restheadspec/hooks.go- Hook system integrationpkg/config/manager.go- Configuration patternpkg/server/shutdown.go- Shutdown callbacks
Files to Modify:
pkg/config/config.go- Add EventBroker fieldpkg/config/manager.go- Add defaultspkg/metrics/interfaces.go- Add event broker metrics
New Package:
pkg/eventbroker/(20 files total)
Provider Feature Matrix
| Feature | Memory | Redis | NATS | Database |
|---|---|---|---|---|
| Persistence | ❌ | ✅ | ✅ | ✅ |
| Cross-instance | ❌ | ✅ | ✅ | ✅ |
| Real-time | ✅ | ✅ | ✅ | ⚠️ (polling) |
| Query history | Limited | Limited | ✅ (replay) | ✅ (SQL) |
| External deps | None | Redis | NATS | None |
| Complexity | Low | Medium | Medium | Low |
Implementation Order Priority
- Core + Memory Provider (Phase 1-3) - Functional in-process event system
- Async + Hooks (Phase 4-5) - Non-blocking event dispatch integrated with CRUD
- Config + Singleton (Phase 6) - Easy initialization and usage
- Redis Provider (Phase 7) - Production-ready distributed events
- Metrics (Phase 10) - Observability
- NATS/Database (Phase 8-9) - Alternative backends
- Tests + Examples (Phase 11) - Documentation and reliability
Next Steps
After approval, implement in order of phases. Each phase builds on previous phases and can be tested independently.