114 lines
4.5 KiB
Markdown
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)
|