โก Event-Driven Deposit Flow
This document describes the current event-driven architecture for the deposit workflow in the fintech system.
๐ Overview
The deposit process is fully event-driven, with each business step handled by a dedicated event handler. This enables modularity, testability, and clear separation of concerns.
๐ผ๏ธ Current Event Flow
```mermaid sequenceDiagram participant U as "User" participant API as "API Handler" participant EB as "EventBus" participant VH as "DepositValidationHandler" participant DC as "DepositConversionHandler" participant DCD as "DepositConversionDone" participant PI as "PaymentInitiationHandler" participant P as "PersistenceHandler"
U->>API: POST /account/:id/deposit (DepositRequest)
API->>EB: DepositRequestedEvent
EB->>VH: DepositValidationHandler
VH->>EB: DepositValidatedEvent
EB->>DC: DepositConversionHandler (if needed)
DC->>EB: DepositConversionDone
EB->>PI: PaymentInitiationHandler
PI->>EB: PaymentInitiatedEvent
EB->>P: PersistenceHandler
P->>EB: DepositPersistedEvent
```
๐ Current Workflow: Event Chain
The deposit workflow follows this event chain:
DepositRequestedEvent
โ ValidationHandlerDepositValidatedEvent
โ PersistenceHandlerDepositPersistedEvent
โ ConversionHandler (if currency conversion needed)DepositBusinessValidationEvent
โ BusinessValidationHandlerDepositBusinessValidatedEvent
โ PaymentInitiationHandlerPaymentInitiatedEvent
โ PaymentPersistenceHandlerPaymentIdPersistedEvent
โ (End of flow)
๐ผ๏ธ Updated Deposit Workflow Diagram
mermaid
flowchart TD
A[DepositRequestedEvent] --> B[ValidationHandler]
B --> C[DepositValidatedEvent]
C --> D[PersistenceHandler]
D --> E[DepositPersistedEvent]
E --> F{Currency Conversion Needed?}
F -->|Yes| G[ConversionRequestedEvent]
F -->|No| H[DepositBusinessValidationEvent]
G --> I[ConversionHandler]
I --> H
H --> J[BusinessValidationHandler]
J --> K[DepositBusinessValidatedEvent]
K --> L[PaymentInitiationHandler]
L --> M[PaymentInitiatedEvent]
M --> N[PaymentPersistenceHandler]
N --> O[PaymentIdPersistedEvent]
๐งฉ Handler Responsibilities
1. Validation Handler (pkg/handler/account/deposit/validation.go
)
- Purpose: Validates deposit request and account ownership
- Events Consumed:
DepositRequestedEvent
- Events Emitted:
DepositValidatedEvent
- Validation Rules:
- Account exists and belongs to user
- Deposit amount is positive
- Account is in valid state for deposits
2. HandleProcessed Handler (pkg/handler/account/deposit/persistence.go
)
- Purpose: Persists deposit transaction to database
- Events Consumed:
DepositValidatedEvent
- Events Emitted:
DepositPersistedEvent
ConversionRequestedEvent
(if currency conversion needed)- Operations:
- Creates transaction record with "created" status
- Emits conversion request if deposit currency differs from account currency
3. Business Validation Handler (pkg/handler/account/deposit/business_validation.go
)
- Purpose: Performs final business validation after currency conversion
- Events Consumed:
DepositBusinessValidationEvent
- Events Emitted:
PaymentInitiationEvent
- Validation Rules:
- Re-validates account ownership with converted amount
- Ensures business rules are met in account currency
4. Payment Initiation Handler (pkg/handler/payment/initiation.go
)
- Purpose: Initiates payment with external providers
- Events Consumed:
PaymentInitiationEvent
- Events Emitted:
PaymentInitiatedEvent
- Operations:
- Integrates with payment providers (e.g., Stripe)
- Creates payment intent/session
5. Payment HandleProcessed Handler (pkg/handler/payment/persistence.go
)
- Purpose: Persists payment ID to transaction record
- Events Consumed:
PaymentInitiatedEvent
- Events Emitted:
PaymentIdPersistedEvent
- Operations:
- Updates transaction with payment provider ID
- Prevents duplicate payment ID persistence
๐ ๏ธ Key Implementation Details
Event Structure
All deposit events embed the common FlowEvent
:
type FlowEvent struct {
FlowType string // "deposit"
UserID uuid.UUID
AccountID uuid.UUID
CorrelationID uuid.UUID
}
Handler Pattern
Each handler follows a consistent pattern:
func HandlerName(bus eventbus.Bus, uow repository.UnitOfWork, logger *slog.Logger) func(ctx context.Context, e domain.Event) error {
return func(ctx context.Context, e domain.Event) error {
log := logger.With("handler", "HandlerName", "event_type", e.Type())
// Type assertion
event, ok := e.(events.SpecificEvent)
if !ok {
log.Debug("Skipping unexpected event type")
return nil
}
// Business logic
// ...
// Emit next event
return bus.Emit(ctx, nextEvent)
}
}
Currency Conversion Logic
The persistence handler conditionally emits conversion events:
// Only emit ConversionRequestedEvent if conversion is needed
if ve.Account != nil &&
ve.Amount.Currency().String() != "" &&
ve.Account.Currency().String() != "" &&
ve.Amount.Currency().String() != ve.Account.Currency().String() {
conversionEvent := events.ConversionRequestedEvent{
FlowEvent: ve.FlowEvent,
Amount: ve.Amount,
To: ve.Account.Currency(),
TransactionID: txID,
}
return bus.Emit(ctx, &conversionEvent)
}
๐ ๏ธ Benefits
1. Single Responsibility Principle
Each handler has one clear responsibility and can be developed/tested independently.
2. Extensibility
New handlers can be added without modifying existing code.
3. Testability
- Unit tests for individual handlers
- E2E tests for complete event chains
- Easy mocking of dependencies
4. Traceability
- Correlation IDs track requests across the entire flow
- Structured logging with emojis for clarity
- Complete audit trail through events
5. Error Handling
- Handlers can return errors to stop the flow
- Failed transactions are logged but don't create invalid state
- Clear error propagation through the event chain
๐งช Testing Strategy
Unit Tests
func TestValidation(t *testing.T) {
// Test individual handler with mocks
bus := mocks.NewMockBus(t)
uow := mocks.NewMockUnitOfWork(t)
handler := Validation(bus, uow, logger)
err := handler(ctx, depositRequestedEvent)
assert.NoError(t, err)
bus.AssertExpectations(t)
}
E2E Tests
func TestDepositE2EEventFlow(t *testing.T) {
// Test complete event chain
emitted := trackEventEmissions()
bus.Emit(ctx, events.DepositRequestedEvent{...})
assert.Equal(t, []string{
"DepositRequestedEvent",
"DepositValidatedEvent",
"DepositPersistedEvent",
"DepositBusinessValidationEvent",
"DepositBusinessValidatedEvent",
"PaymentInitiatedEvent",
}, emitted)
}
๐ง Error Scenarios
Validation Failures
- Account not found โ Handler logs error, returns nil (stops flow)
- Invalid user ID โ Handler logs error, returns validation error
- Negative amount โ Handler logs error, returns validation error
HandleProcessed Failures
- Database error โ Handler logs error, returns error (stops flow)
- Transaction creation fails โ Handler logs error, returns error
Payment Failures
- Provider error โ Handler logs error, may emit PaymentFailedEvent
- Network timeout โ Handler logs error, may retry or fail