4.5 KiB
4.5 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/
│ ├── core/ # Domain logic and models
│ ├── adapters/ # External service integrations
│ └── main.rs # CLI entry point
├── 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. 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:
- Account Discovery: Fetch active accounts from GoCardless
- Account Matching: Match GoCardless accounts to Firefly asset accounts by IBAN
- Date Window: Calculate sync range (Last Firefly transaction + 1 to Yesterday)
- Transaction Processing:
- 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
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
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 - Propagation:
anyhowfor error context across async boundaries - Graceful Degradation: Rate limits and network issues don't crash entire sync
- Structured Logging:
tracingfor observability and debugging
Configuration Management
- Environment variables loaded via
dotenvy - Workspace-level dependency management
- Feature flags for optional functionality
- Secure credential handling (no hardcoded secrets)