Files
banks2ff/docs/architecture.md
Jacob Kiers 74d362b412 Handle expired agreements and rewrite README
- Implement robust End User Agreement expiry detection and handling
- Add graceful error recovery for failed accounts
- Rewrite README.md to focus on user benefits
- Add documentation guidelines to AGENTS.md
2025-11-21 19:30:54 +01:00

125 lines
5.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 with robust error handling:
1. **Account Discovery**: Fetch active accounts from GoCardless (filtered by End User Agreement (EUA) validity)
2. **Agreement Validation**: Check EUA expiry status for each account's requisition
3. **Account Matching**: Match GoCardless accounts to Firefly asset accounts by IBAN
4. **Error-Aware Processing**: Continue with valid accounts when some have expired agreements
5. **Date Window**: Calculate sync range (Last Firefly transaction + 1 to Yesterday)
6. **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 `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
### 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` → 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 including End User Agreement (EUA) expiry (`SyncError::AgreementExpired`)
- **Propagation**: `anyhow` for 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**: `tracing` for 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)