Skip to content

๐Ÿงน 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.Validated and Withdraw.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.Initiated for 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 *CurrencyConverted events (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.Validated and Withdraw.Validated
  • โœ… Payment HandleProcessed Handler: Tests for Payment.Initiated
  • โœ… Conversion Done Handlers: Tests for business validation only

๐Ÿ“ˆ Test Coverage

go test ./pkg/handler/payment/... -v  # โœ… All passing
go test ./pkg/handler/... -v          # โœ… All passing

๐Ÿ—‚๏ธ 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))