๐งน Clean Event-Driven Architecture Refactoring
๐ฏ Goals Achieved
This refactoring successfully addressed the messy event handler registration and achieved the following goals:
- โ No misleading events - Clear event flow and responsibilities
- โ DRY (Don't Repeat Yourself) - Generic payment handlers instead of operation-specific ones
- โ SRP (Single Responsibility Principle) - Each handler has one clear responsibility
- โ No payment handlers per operation - Single generic payment handlers that subscribe to multiple validation events
๐ Before vs After
๐งจ Before (Messy)
// Operation-specific payment handlers (violates DRY)
bus.Subscribe("Deposit.CurrencyConverted", deposithandler.ConversionDoneHandler(bus, deps.Uow, deps.PaymentProvider, deps.Logger))
bus.Subscribe("Withdraw.CurrencyConverted", withdrawhandler.ConversionDoneHandler(bus, deps.Uow, deps.PaymentProvider, deps.Logger))
// Duplicate payment persistence handlers (violates DRY)
bus.Subscribe("Payment.Initiated", deposithandler.PaymentPersistenceHandler(bus, deps.Uow, deps.Logger))
bus.Subscribe("Payment.Initiated", withdrawhandler.PaymentPersistenceHandler(bus, deps.Uow, deps.Logger))
// Conversion handlers doing payment initiation (violates SRP)
// ConversionDoneHandler was doing both business validation AND payment initiation
โจ After (Clean)
// Generic payment handlers (DRY)
bus.Subscribe("Deposit.Validated", paymenthandler.PaymentInitiationHandler(bus, deps.PaymentProvider, deps.Logger))
bus.Subscribe("Withdraw.Validated", paymenthandler.PaymentInitiationHandler(bus, deps.PaymentProvider, deps.Logger))
// Single generic payment persistence handler (DRY)
bus.Subscribe("Payment.Initiated", paymenthandler.PaymentPersistenceHandler(bus, deps.Uow, deps.Logger))
// Conversion handlers focus only on business validation (SRP)
bus.Subscribe("Deposit.CurrencyConverted", deposithandler.ConversionDoneHandler(bus, deps.Uow, deps.Logger))
bus.Subscribe("Withdraw.CurrencyConverted", withdrawhandler.ConversionDoneHandler(bus, deps.Uow, deps.Logger))
๐๏ธ Architecture Changes
๐ณ 1. Generic Payment Initiation Handler
Location: pkg/handler/payment/initiation_handler.go
Responsibilities:
- Handles
Deposit.ValidatedandWithdraw.Validated - Initiates payment with the payment provider
- Emits
Payment.Initiated
Benefits:
- โ DRY: Single handler for all payment initiation
- โ SRP: Only handles payment initiation, not business validation
- โ
Extensible: Easy to add new validation events (e.g.,
Transfer.Validated)
๐พ 2. Generic Payment HandleProcessed Handler
Location: pkg/handler/payment/persistence_handler.go
Responsibilities:
- Handles
Payment.Initiatedfor all operations - Updates transaction with payment ID
- Emits
PaymentIdPersistedEvent
Benefits:
- โ DRY: Single handler for all payment persistence
- โ SRP: Only handles payment persistence, not business logic
- โ Consistent: Same persistence logic for all operations
โ 3. Clean Conversion Done Handlers
Location: pkg/handler/account/deposit/conversion_done.go and pkg/handler/account/withdraw/conversion_done.go
Responsibilities:
- Handle
*CurrencyConvertedevents (e.g.,Deposit.CurrencyConverted,Withdraw.CurrencyConverted) - Perform business validation after conversion
- Emit validation events to trigger payment initiation
Benefits:
- โ SRP: Only handles business validation, not payment initiation
- โ Clear Flow: Validation โ Payment Initiation (not Validation + Payment)
- โ Testable: Easy to test business validation logic separately
๐ Event Flow
๐ฅ Deposit Flow
Deposit.Requested โ ValidationHandler โ Deposit.Validated โ PaymentInitiationHandler โ Payment.Initiated โ PaymentPersistenceHandler
๐ค Withdraw Flow
Withdraw.Requested โ ValidationHandler โ Withdraw.Validated โ PaymentInitiationHandler โ Payment.Initiated โ PaymentPersistenceHandler
๐ Transfer Flow (No Payment)
Transfer.Requested โ ValidationHandler โ Transfer.Validated โ DomainOpHandler โ Transfer.Completed โ PersistenceHandler
๐งช Testing
๐งช Updated Tests
- โ
Payment Initiation Handler: Tests for
Deposit.ValidatedandWithdraw.Validated - โ
Payment HandleProcessed Handler: Tests for
Payment.Initiated - โ Conversion Done Handlers: Tests for business validation only
๐ Test Coverage
๐๏ธ File Changes
โ Added/Modified
- โ
pkg/handler/payment/initiation_handler.go- Generic payment initiation - โ
pkg/handler/payment/persistence_handler.go- Generic payment persistence - โ
pkg/handler/account/deposit/conversion_done.go- Clean business validation - โ
pkg/handler/account/withdraw/conversion_done.go- Clean business validation - โ
app/app.go- Updated event handler registration
๐๏ธ Removed
- โ
pkg/handler/account/deposit/payment_persistence.go- Duplicate code - โ
pkg/handler/account/withdraw/payment_persistence.go- Duplicate code
๐ฏ Key Principles Applied
๐ฃ 1. Event-Driven Design
- Events trigger next steps, not conditional logic
- Clear event flow: Validation โ Payment โ HandleProcessed
- No if-else statements for control flow
๐ฏ 2. Single Responsibility Principle
- Each handler has one clear responsibility
- Conversion handlers: Business validation only
- Payment handlers: Payment operations only
- HandleProcessed handlers: Database operations only
โป๏ธ 3. Don't Repeat Yourself
- Generic payment handlers for all operations
- Single payment persistence logic
- Reusable event structures
๐งฉ 4. Separation of Concerns
- Business validation separated from payment initiation
- Payment logic separated from conversion logic
- HandleProcessed logic separated from business logic
๐ Benefits
๐ ๏ธ 1. Maintainability
- Clear separation of concerns
- Easy to understand and modify individual components
- Consistent patterns across all handlers
๐งช 2. Testability
- Each handler can be tested independently
- Clear input/output expectations
- Easy to mock dependencies
๐งฑ 3. Extensibility
- Easy to add new operations (e.g., loan payments)
- Easy to add new payment providers
- Easy to add new validation rules
๐ก๏ธ 4. Reliability
- No duplicate code to maintain
- Clear error handling paths
- Consistent event flow
๐ฎ Future Enhancements
โ 1. Add Transfer Payment Support
// Easy to extend - just add new event subscription
bus.Subscribe("Transfer.Validated", paymenthandler.PaymentInitiationHandler(bus, deps.PaymentProvider, deps.Logger))
โ 2. Add Payment Completion Handlers
// Generic payment completion handling
bus.Subscribe("Payment.Completed", paymenthandler.PaymentCompletionHandler(bus, deps.Uow, deps.Logger))
๐จ 3. Add Payment Failure Handlers
// Generic payment failure handling
bus.Subscribe("Payment.Failed", paymenthandler.PaymentFailureHandler(bus, deps.Uow, deps.Logger))