Files
banks2ff/docs/architecture.md
2025-11-21 14:32:23 +01:00

114 lines
4.5 KiB
Markdown

# 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 support
- `Account`: Bank account representation
- Supports `foreign_amount` and `foreign_currency` for 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 management
- `mapper.rs`: Converts GoCardless API responses to domain models
- `cache.rs`: Caches account mappings to reduce API calls
- Correctly handles multi-currency via `currencyExchange` array 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:
1. **Account Discovery**: Fetch active accounts from GoCardless
2. **Account Matching**: Match GoCardless accounts to Firefly asset accounts by IBAN
3. **Date Window**: Calculate sync range (Last Firefly transaction + 1 to Yesterday)
4. **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 `currencyExchange` array from GoCardless responses
- Calculates `foreign_amount = amount * exchange_rate`
- Maps to Firefly's `foreign_amount` and `foreign_currency_code` fields
### Rate Limit Management
- **Caching**: Stores `AccountId -> IBAN` mappings 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` → Firefly `external_id` mapping
- 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 `mockall` for trait mocking
- **Integration Tests**: API clients with `wiremock` for HTTP mocking
- **Fixture Testing**: Real JSON responses for adapter mapping validation
- **Isolation**: Business logic tested without external dependencies
## Error Handling
- **Custom Errors**: `thiserror` for domain-specific error types
- **Propagation**: `anyhow` for error context across async boundaries
- **Graceful Degradation**: Rate limits and network issues don't crash entire sync
- **Structured Logging**: `tracing` for 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)