This makes the code much easier to follow and shortens main.rs from >1000 lines to around 150.
6.6 KiB
6.6 KiB
Architecture Documentation
Overview
Banks2FF implements a Hexagonal (Ports & Adapters) Architecture to synchronize bank transactions from GoCardless to Firefly III. This architecture separates business logic from external concerns, making the system testable and maintainable.
Workspace Structure
banks2ff/
├── banks2ff/ # Main CLI application
│ └── src/
│ ├── commands/ # Command handlers
│ │ ├── accounts/ # Account management commands
│ │ │ ├── mod.rs # Account commands dispatch
│ │ │ ├── link.rs # Account linking logic
│ │ │ ├── list.rs # Account listing functionality
│ │ │ └── status.rs # Account status functionality
│ │ ├── transactions/ # Transaction management commands
│ │ ├── list.rs # Source/destination listing
│ │ └── sync.rs # Sync command handler
│ ├── cli/ # CLI utilities and formatting
│ ├── core/ # Domain logic and models
│ ├── adapters/ # External service integrations
│ └── main.rs # CLI entry point and command dispatch
├── firefly-client/ # Firefly III API client library
├── gocardless-client/ # GoCardless API client library
└── docs/ # Architecture documentation
Core Components
1. Domain Core (banks2ff/src/core/)
models.rs: Defines domain entities
BankTransaction: Core transaction model with multi-currency supportAccount: Bank account representation- Supports
foreign_amountandforeign_currencyfor international transactions
ports.rs: Defines abstraction traits
TransactionSource: Interface for fetching transactions (implemented by GoCardless adapter)TransactionDestination: Interface for storing transactions (implemented by Firefly adapter)- Traits are mockable for isolated testing
sync.rs: Synchronization engine
run_sync(): Orchestrates the entire sync process- Implements "Healer" strategy for idempotency
- Smart date range calculation (Last Transaction Date + 1 to Yesterday)
2. Adapters (banks2ff/src/adapters/)
gocardless/: GoCardless integration
client.rs: Wrapper for GoCardless client with token managementmapper.rs: Converts GoCardless API responses to domain modelscache.rs: Caches account mappings to reduce API calls- Correctly handles multi-currency via
currencyExchangearray parsing
firefly/: Firefly III integration
client.rs: Wrapper for Firefly client for transaction storage- Maps domain models to Firefly API format
3. Command Handlers (banks2ff/src/commands/)
The CLI commands are organized into focused modules:
- sync.rs: Handles transaction synchronization between sources and destinations
- accounts/: Account management including linking, listing, and status
- transactions/: Transaction inspection, caching, and cache management
- list.rs: Simple listing of available sources and destinations
4. API Clients
Both clients are hand-crafted using reqwest:
- Strongly-typed DTOs for compile-time safety
- Custom error handling with
thiserror - Rate limit awareness and graceful degradation
Synchronization Process
The "Healer" strategy ensures idempotency with robust error handling:
- Account Discovery: Fetch active accounts from GoCardless (filtered by End User Agreement (EUA) validity)
- Agreement Validation: Check EUA expiry status for each account's requisition
- Account Matching: Match GoCardless accounts to Firefly asset accounts by IBAN
- Error-Aware Processing: Continue with valid accounts when some have expired agreements
- Date Window: Calculate sync range (Last Firefly transaction + 1 to Yesterday)
- Transaction Processing (with error recovery):
- Search: Look for existing transaction using windowed heuristic (date ± 3 days, exact amount)
- Heal: If found without
external_id, update with GoCardless transaction ID - Skip: If found with matching
external_id, ignore - Create: If not found, create new transaction in Firefly
- Error Handling: Log issues but continue with other transactions/accounts
Key Features
Multi-Currency Support
- Parses
currencyExchangearray from GoCardless responses - Calculates
foreign_amount = amount * exchange_rate - Maps to Firefly's
foreign_amountandforeign_currency_codefields
Rate Limit Management
- Caching: Stores
AccountId -> IBANmappings to reduce requisition calls - Token Reuse: Maintains tokens until expiry to minimize auth requests
- Graceful Handling: Continues sync for other accounts when encountering 429 errors
Agreement Expiry Handling
- Proactive Validation: Checks End User Agreement (EUA) expiry before making API calls to avoid unnecessary requests
- Reactive Recovery: Detects expired agreements from API 401 errors and skips affected accounts
- Continued Operation: Maintains partial sync success even when some accounts are inaccessible
- User Feedback: Provides detailed reporting on account status and re-authorization needs
- Multiple Requisitions: Supports accounts linked to multiple requisitions, using the most recent valid one
Idempotency
- GoCardless
transactionId→ Fireflyexternal_idmapping - Windowed duplicate detection prevents double-creation
- Historical transaction healing for pre-existing data
Data Flow
GoCardless API → GoCardlessAdapter → TransactionSource → SyncEngine → TransactionDestination → FireflyAdapter → Firefly API
Testing Strategy
- Unit Tests: Core logic with
mockallfor trait mocking - Integration Tests: API clients with
wiremockfor HTTP mocking - Fixture Testing: Real JSON responses for adapter mapping validation
- Isolation: Business logic tested without external dependencies
Error Handling
- Custom Errors:
thiserrorfor domain-specific error types including End User Agreement (EUA) expiry (SyncError::AgreementExpired) - Propagation:
anyhowfor error context across async boundaries - Graceful Degradation: Rate limits, network issues, and expired agreements don't crash entire sync
- Partial Success: Continues processing available accounts when some fail
- Structured Logging:
tracingfor observability and debugging with account-level context
Configuration Management
- Environment variables loaded via
dotenvy - Workspace-level dependency management
- Feature flags for optional functionality
- Secure credential handling (no hardcoded secrets)