This guide provides extensive documentation for using Gossip in real-world applications.
- Getting Started
- Basic Concepts
- Setting Up Your Project
- Real-World Examples
- Advanced Patterns
- Best Practices
- Common Pitfalls
go get github.com/seyallius/gossipimport gossip "github.com/seyallius/gossip/event"Event types are strongly-typed string constants that identify events. Never use raw strings.
// ✅ Good - Strongly typed
const (
UserCreated gossip.EventType = "user.created"
OrderPaid gossip.EventType = "order.paid"
)
// ❌ Bad - Raw strings prone to typos
bus.Publish(gossip.NewEvent("user.created", data))Convention: Use hierarchical naming with dots: domain.entity.action
Examples:
auth.user.createdauth.login.successorder.payment.completedinventory.stock.depleted
Events contain:
- Type: Strongly-typed identifier
- Timestamp: Automatic creation time
- Data: Event-specific payload (any type)
- Metadata: Additional context (map)
event := gossip.NewEvent(UserCreated, &UserData{
UserID: "123",
Email: "user@example.com",
})
// Add metadata for context
event.WithMetadata("request_id", "req-abc-123")
event.WithMetadata("source", "api")Processors are functions that process events:
func myProcessor(ctx context.Context, event *event.Event) error {
// Type assert the data
data := event.Data.(*UserData)
// Process the event
log.Printf("Processing user: %s", data.UserID)
// Return error if processing fails
return nil
}Key points:
- Always type-assert
event.Datato expected type - Return
errorif processing fails - Use
ctxfor cancellation/timeout - Processors should be idempotent when possible
Create a dedicated file for event types:
// events/types.go
package events
import gossip "github.com/seyallius/gossip/event"
const (
// User events
UserCreated gossip.EventType = "user.created"
UserUpdated gossip.EventType = "user.updated"
UserDeleted gossip.EventType = "user.deleted"
// Auth events
LoginSuccess gossip.EventType = "auth.login.success"
LoginFailed gossip.EventType = "auth.login.failed"
PasswordChanged gossip.EventType = "auth.password.changed"
// Order events
OrderCreated gossip.EventType = "order.created"
OrderPaid gossip.EventType = "order.paid"
OrderShipped gossip.EventType = "order.shipped"
)Create structs for event payloads:
// events/data.go
package events
type UserCreatedData struct {
UserID string
Email string
Username string
}
type LoginSuccessData struct {
UserID string
IPAddress string
UserAgent string
}
type OrderCreatedData struct {
OrderID string
CustomerID string
Amount float64
Items []string
}In your main application:
// main.go
package main
import (
"log"
"github.com/seyallius/gossip/event/bus"
)
var eventBus *bus.EventBus
func initEventBus() {
config := &bus.Config{
Workers: 10, // Adjust based on load
BufferSize: 1000, // Adjust based on event volume
}
eventBus = bus.NewEventBus(config)
log.Println("Event bus initialized")
}
func main() {
initEventBus()
defer eventBus.Shutdown()
// Register handlers
registerProcessors()
// Start your application
// ...
}Create handler registration function:
// handlers/registration.go
package handlers
import (
"github.com/seyallius/gossip"
"github.com/seyallius/gossip/event/bus"
"yourapp/events"
)
func RegisterProcessors(eventBus *bus.EventBus) {
// User handlers
eventBus.Subscribe(events.UserCreated, EmailNotificationProcessor)
eventBus.Subscribe(events.UserCreated, AuditLogProcessor)
eventBus.Subscribe(events.UserCreated, MetricsProcessor)
// Auth handlers
eventBus.Subscribe(events.LoginSuccess, SecurityProcessor)
eventBus.Subscribe(events.LoginSuccess, AnalyticsProcessor)
// Order handlers
eventBus.Subscribe(events.OrderCreated, InventoryProcessor)
eventBus.Subscribe(events.OrderPaid, PaymentProcessorProcessor)
}// services/user_service.go
package services
import (
"github.com/seyallius/gossip"
"github.com/seyallius/gossip/event"
"github.com/seyallius/gossip/event/bus"
"yourapp/events"
)
type UserService struct {
eventBus *bus.EventBus
}
func (s *UserService) CreateUser(email, username string) error {
// Core business logic
userID := generateUserID()
// ... save to database ...
// Publish event
eventData := &events.UserCreatedData{
UserID: userID,
Email: email,
Username: username,
}
event := event.NewEvent(events.UserCreated, eventData).
WithMetadata("source", "api").
WithMetadata("request_id", getRequestID())
s.eventBus.Publish(event)
return nil
}File: examples/auth_service/main.go
This example demonstrates a complete authentication service using Gossip for event-driven architecture.
Overview
The example shows:
- User registration with welcome emails
- Login tracking and notifications
- Password change alerts
- Audit logging for all auth events
- Security monitoring
Run:
cd examples/auth_service
go run main.goKey Concepts Demonstrated
- Event Fan-out: Multiple handlers for single event
- Middleware Composition: Combining retry, timeout, and recovery middleware
- Metadata Usage: Using metadata for request tracking and context
- Security Patterns: Implementing security monitoring with events
Components Used
- Event bus for decoupling services
- Middleware for error handling and recovery
- Metadata for request correlation
File: examples/ecommerce/main.go
This example demonstrates an e-commerce platform using Gossip for event-driven order processing and notifications.
Overview
The example shows:
- Order creation and processing
- Batch email notifications
- Inventory management
- Conditional analytics (high-value orders)
- Payment processing pipeline
Run:
cd examples/ecommerce
go run main.goKey Concepts Demonstrated
- Batch Processing: Efficient processing of high-volume events
- Event Filtering: Conditional processing based on event properties
- Async Operations: Asynchronous inventory updates
- Pipeline Processing: Sequential event processing patterns
Components Used
- Batch processors for efficient email sending
- Event filters for conditional processing
- Multiple event handlers for different business operations
File: examples/microservices/main.go
This example demonstrates microservices communication using Gossip for cross-service event-driven architecture.
Overview
The example shows:
- Cross-service event communication
- Service-to-service decoupling
- Multiple services reacting to same event
- Event chaining (service publishes events other services consume)
Run:
cd examples/microservices
go run main.goKey Concepts Demonstrated
- Service Decoupling: Loose coupling between services using events
- Event Chaining: Services publishing events that trigger other services
- Shared Event Bus: Using a common event bus across multiple services
- Self-Registering Handlers: Services registering their own event handlers
Components Used
- Shared event bus for inter-service communication
- Self-registering event handlers
- Event-driven orchestration patterns
File: examples/error_handling/main.go
This example demonstrates how to use Gossip's standard error types for conditional error handling in event-driven applications.
Overview
The example shows:
- How to use different error types (
RetryableError,ValidationError,FatalError, etc.) - How to implement conditional error handling based on error types
- How to create custom middleware that handles errors differently based on their type
- How to combine error handling with other middleware like retry and timeout
Run:
cd examples/error_handling
go run main.goKey Concepts Demonstrated
- Standard Error Types: Using
gossip/event/errorspackage to create typed errors - Conditional Error Handling: Using
IsRetryable,IsValidation, etc. to handle different error types differently - Integration with Middleware: How error types work with existing middleware like retry and timeout
- Custom Error Handling Middleware: Creating middleware that responds to specific error types
Error Types Used
RetryableError: For transient failures that should be retriedValidationError: For input validation failuresFatalError: For unrecoverable errors that should not be retriedTimeoutError: For operations that exceed time limitsProcessingError: For general processing failures
File: examples/type_assertion_example/main.go
This example demonstrates proper handling of type assertion failures using Gossip's error handling patterns.
Overview
The example shows:
- How to properly handle type assertion failures using
TypeAssertionError - Similar to
redis.Nilpattern for type mismatches - How to implement conditional error handling for type-related issues
- Best practices for type-safe event data processing
Run:
cd examples/type_assertion_example
go run main.goKey Concepts Demonstrated
- Safe Type Assertions: Using proper type assertion patterns with error handling
- Type-Safe Processing: Ensuring type-safe event data processing
- Conditional Error Handling: Handling type-related errors differently based on context
- Error Type Integration: How type assertion errors work with other error handling patterns
Error Types Used
TypeAssertionError: For type assertion failures
Chain multiple middleware for robust handling:
handler := middleware.Chain(
middleware.WithRecovery(), // Catch panics
middleware.WithRetry(3, 100*time.Millisecond), // Retry on failure
middleware.WithTimeout(5*time.Second), // Prevent hanging
middleware.WithLogging(), // Log execution
)(myProcessor)
bus.Subscribe(UserCreated, handler)Order matters: Place recovery first to catch all panics.
Only execute handlers when conditions are met:
// Only process events from specific source
apiFilter := filter.FilterByMetadata("source", "api")
bus.GetGlobalBus().Subscribe(UserCreated, gossip.NewFilteredProcessor(apiFilter, apiProcessor))
// Combine filters with AND/OR logic
complexFilter := filter.And(
filter.FilterByMetadataExists("user_id"),
filter.Or(
filter.FilterByMetadata("source", "api"),
filter.FilterByMetadata("source", "web"),
),
)Efficient processing of high-volume events:
batchConfig := batch.BatchConfig{
BatchSize: 100, // Process 100 events at once
FlushPeriod: 5 * time.Second, // Or every 5 seconds
}
batchProcessor := func(ctx context.Context, events []*event.Event) error {
// Process all events together
log.Printf("Processing batch of %d events", len(events))
// Bulk database insert, bulk email send, etc.
return bulkInsertToDatabase(events)
}
processor := gossip.NewBatchProcessor(OrderCreated, batchConfig, batchProcessor)
defer processor.Shutdown()
bus.Subscribe(OrderCreated, processor.AsEventProcessor())When to use:
- Email notifications (batch send)
- Database inserts (bulk insert)
- API calls (batch requests)
- Metric aggregation
When you need immediate results:
event := gossip.NewEvent(CriticalTransaction, data)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
errors := bus.PublishSync(ctx, event)
if len(errors) > 0 {
// Some handlers failed
log.Printf("Processor failures: %v", errors)
return handleFailure(errors)
}
log.Println("All handlers succeeded")Use cases:
- Critical transactions requiring validation
- Rollback scenarios
- Immediate feedback needed
- Testing
✅ Do:
- Use hierarchical naming:
domain.entity.action - Be specific:
order.payment.completednotorder.updated - Use past tense:
user.creatednotuser.create - Group related events:
auth.login.*,order.payment.*
❌ Don't:
- Use generic names:
updated,changed - Mix tenses:
user.creating,order.created - Use abbreviations:
usr.crtd
✅ Do:
- Keep handlers small and focused
- Make handlers idempotent when possible
- Return errors for failures
- Use context for cancellation
- Log handler actions
❌ Don't:
- Block for long periods
- Panic in handlers (use middleware recovery)
- Modify shared state without locking
- Ignore errors
func myProcessor(ctx context.Context, event *event.Event) error {
// Type assertion with check
data, ok := event.Data.(*UserData)
if !ok {
return fmt.Errorf("invalid event data type")
}
// Business logic with error handling
if err := processUser(data); err != nil {
return fmt.Errorf("failed to process user: %w", err)
}
return nil
}func TestUserCreatedProcessor(t *testing.T) {
// Create test event
event := gossip.NewEvent(UserCreated, &UserData{
UserID: "test-123",
Email: "test@example.com",
})
// Execute handler
err := myProcessor(context.Background(), event)
// Assert results
assert.NoError(t, err)
assert.True(t, emailWasSent("test@example.com"))
}// Low volume (< 100 events/sec)
config := &bus.Config{
Workers: 5,
BufferSize: 500,
}
// Medium volume (100-1000 events/sec)
config := &bus.Config{
Workers: 10,
BufferSize: 1000,
}
// High volume (> 1000 events/sec)
config := &bus.Config{
Workers: 20,
BufferSize: 5000,
}❌ Wrong:
data := event.Data.(*UserData) // Panics if wrong type✅ Correct:
data, ok := event.Data.(*UserData)
if !ok {
return fmt.Errorf("invalid data type")
}❌ Wrong:
func slowProcessor(ctx context.Context, event *event.Event) error {
time.Sleep(30 * time.Second) // Blocks worker
return nil
}✅ Correct:
func fastProcessor(ctx context.Context, event *event.Event) error {
// Offload heavy work
go processInBackground(event.Data)
return nil
}❌ Wrong:
func main() {
bus := gossip.NewEventBus(config)
// ... application code ...
// No cleanup!
}✅ Correct:
func main() {
bus := gossip.NewEventBus(config)
defer bus.Shutdown() // Graceful cleanup
// ... application code ...
}❌ Wrong:
// Processor A publishes Event B
func handlerA(ctx context.Context, event *event.Event) error {
bus.Publish(gossip.NewEvent(EventB, nil))
return nil
}
// Processor B publishes Event A (circular!)
func handlerB(ctx context.Context, event *event.Event) error {
bus.Publish(gossip.NewEvent(EventA, nil))
return nil
}✅ Correct:
- Avoid circular dependencies
- Use event metadata to track origin and prevent loops
- Design event flow as a DAG (Directed Acyclic Graph)
❌ Wrong:
// Using sync for everything defeats the purpose
for _, item := range items {
bus.PublishSync(ctx, event) // Slow!
}✅ Correct:
// Use async for most cases
for _, item := range items {
bus.Publish(event) // Fast!
}
// Use sync only when necessary
criticalEvent := gossip.NewEvent(CriticalOp, data)
errors := bus.PublishSync(ctx, criticalEvent)- Tune worker count based on handler latency
- Use batch processing for high-volume events
- Implement filtering to avoid unnecessary work
- Monitor buffer fullness - increase if events are dropped
- Profile handler performance - optimize slow handlers
- Use middleware wisely - each layer adds overhead
- Check if handlers are registered before publishing
- Verify event type matches subscription
- Ensure bus hasn't been shutdown
- Check logs for handler errors
- Profile handler execution time
- Check for blocking operations
- Increase worker count
- Use batch processing
- Reduce buffer size
- Implement event filtering
- Fix handler memory leaks
- Monitor goroutine count
- Main README - Library overview
- API Documentation
- GitHub Issues
Questions? Open an issue on GitHub or reach out to the community!