Add README.md
This commit is contained in:
parent
17a695d895
commit
b94df9467e
203
README.md
Normal file
203
README.md
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
# Currency Converter CLI
|
||||||
|
|
||||||
|
A command-line utility for converting currencies using CoinMarketCap API, built with Go following Clean Architecture principles.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Go 1.19 or higher
|
||||||
|
- CoinMarketCap API key (get from https://coinmarketcap.com/api/)
|
||||||
|
|
||||||
|
### Installation & Setup
|
||||||
|
1. Clone the repository
|
||||||
|
2. Set your API key:
|
||||||
|
```bash
|
||||||
|
export CMC_API_KEY=your_api_key_here
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
#### Using Taskfile (Recommended)
|
||||||
|
```bash
|
||||||
|
# Install task runner (if not already installed)
|
||||||
|
go install github.com/go-task/task/v3/cmd/task@latest
|
||||||
|
|
||||||
|
# Build and run with parameters
|
||||||
|
task run -- 123.45 USD BTC
|
||||||
|
task run -- 1 BTC USD
|
||||||
|
task run -- 100 EUR BTC
|
||||||
|
|
||||||
|
# Other available tasks
|
||||||
|
task build # Build the application
|
||||||
|
task test # Run tests
|
||||||
|
task test-verbose # Run tests with verbose output
|
||||||
|
task clean # Clean build artifacts
|
||||||
|
task all # Full build pipeline
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using Go directly
|
||||||
|
```bash
|
||||||
|
# Build the application
|
||||||
|
go build -o bin/converter ./cmd/converter
|
||||||
|
|
||||||
|
# Run conversions
|
||||||
|
./bin/converter 123.45 USD BTC
|
||||||
|
./bin/converter 1 BTC USD
|
||||||
|
./bin/converter 100 EUR BTC
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using Go commands directly
|
||||||
|
```bash
|
||||||
|
# Run tests
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
# Build manually
|
||||||
|
mkdir -p bin && go build -o bin/converter ./cmd/converter
|
||||||
|
|
||||||
|
# Run with API key
|
||||||
|
CMC_API_KEY=your_key ./bin/converter 50 USD BTC
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
converter/
|
||||||
|
├── cmd/converter/ # CLI application entry point
|
||||||
|
│ └── main.go # Main function, argument parsing, DI setup
|
||||||
|
├── app/ # Application layer (Use Cases)
|
||||||
|
│ ├── convert_currency.go # Core use case implementation
|
||||||
|
│ ├── convert_currency_test.go # Use case tests with mocks
|
||||||
|
│ └── mocks/ # Generated mocks (mockery)
|
||||||
|
│ └── RateProvider.go # Mock for RateProvider interface
|
||||||
|
├── domain/ # Domain layer (Business Logic)
|
||||||
|
│ ├── entities.go # Value objects (Currency, Money, Rate)
|
||||||
|
│ ├── entities_test.go # Domain entity tests
|
||||||
|
│ ├── services.go # Domain service (CurrencyConverter)
|
||||||
|
│ └── services_test.go # Domain service tests
|
||||||
|
├── infrastructure/ # Infrastructure layer (External APIs)
|
||||||
|
│ └── coinmarketcap/ # CoinMarketCap API client
|
||||||
|
│ ├── client.go # HTTP client with retry/fallback logic
|
||||||
|
│ └── models.go # API response DTOs
|
||||||
|
├── Taskfile.yml # Task runner configuration
|
||||||
|
├── go.mod # Go module definition
|
||||||
|
├── go.sum # Go module checksums
|
||||||
|
├── TASK.md # Original task requirements (Russian)
|
||||||
|
├── CLAUDE.md # Project configuration for Claude Code
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
### Architecture Overview
|
||||||
|
|
||||||
|
This project implements **Clean Architecture** with clear separation of concerns:
|
||||||
|
|
||||||
|
- **Domain Layer**: Pure business logic with no external dependencies
|
||||||
|
- Value objects (Currency, Money, Rate) with validation
|
||||||
|
- Domain services (CurrencyConverter) for business operations
|
||||||
|
- Domain errors and business rules
|
||||||
|
|
||||||
|
- **Application Layer**: Use cases that orchestrate domain logic
|
||||||
|
- ConvertCurrencyUseCase: Main conversion workflow
|
||||||
|
- RateProvider interface: Defines contract for rate fetching
|
||||||
|
- DTO to domain object mapping with reversal detection
|
||||||
|
|
||||||
|
- **Infrastructure Layer**: External integrations and technical details
|
||||||
|
- CoinMarketCap API client with automatic retry logic
|
||||||
|
- HTTP error handling and response mapping
|
||||||
|
- Rate reversal logic (USD→BTC becomes BTC→USD inverted)
|
||||||
|
|
||||||
|
- **CLI Layer**: User interface and dependency injection
|
||||||
|
- Command-line parsing and validation
|
||||||
|
- Environment variable configuration
|
||||||
|
- Error formatting for end users
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
The project includes comprehensive test coverage using **testify** and **mockery**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
task test
|
||||||
|
# or
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
# Run tests with verbose output
|
||||||
|
task test-verbose
|
||||||
|
# or
|
||||||
|
go test -v ./...
|
||||||
|
|
||||||
|
# Run tests with coverage
|
||||||
|
task test-coverage
|
||||||
|
# or
|
||||||
|
go test -coverprofile=coverage.out ./...
|
||||||
|
go tool cover -html=coverage.out
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Structure
|
||||||
|
- **Unit Tests**: Domain entities and services with table-driven tests
|
||||||
|
- **Use Case Tests**: Application layer with generated mocks using testify/mockery
|
||||||
|
- **Mock Generation**: Use `task generate-mocks` to regenerate mocks
|
||||||
|
|
||||||
|
## Architecture Decisions
|
||||||
|
|
||||||
|
### Why decimal.Decimal for Money Handling
|
||||||
|
|
||||||
|
We chose `shopspring/decimal` over floating-point numbers and other alternatives for the following critical reasons:
|
||||||
|
|
||||||
|
#### Financial Accuracy Requirements
|
||||||
|
- Floating point arithmetic cannot represent decimal numbers exactly
|
||||||
|
- Example: `0.1 + 0.2 ≠ 0.3` in floating-point arithmetic
|
||||||
|
- Currency conversion errors compound with multiple operations
|
||||||
|
- CoinMarketCap API returns exchange rates with high precision (8+ decimal places)
|
||||||
|
|
||||||
|
#### Real-world Impact
|
||||||
|
```go
|
||||||
|
// Problematic with float64
|
||||||
|
Bad: float64(123.45) * float64(0.000016) = 0.001975200000000001
|
||||||
|
|
||||||
|
// Accurate with decimal.Decimal
|
||||||
|
Good: decimal(123.45) * decimal(0.000016) = 0.001975200000000000
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Industry Standards
|
||||||
|
- Banking and financial systems never use floating-point for monetary calculations
|
||||||
|
- Major payment APIs (Stripe, PayPal) use string representations to avoid precision loss
|
||||||
|
- `shopspring/decimal` is the Go standard for financial applications (used by Kubernetes billing, trading systems)
|
||||||
|
|
||||||
|
#### Alternative Analysis
|
||||||
|
- **big.Rat**: Overkill for this use case, overly complex API for basic currency operations
|
||||||
|
- **Integer scaling**: Requires manual precision management, error-prone across currencies with different decimal places (USD=2, BTC=8, ETH=18)
|
||||||
|
- **float64**: Unacceptable precision loss for financial data
|
||||||
|
|
||||||
|
#### Performance vs Precision Trade-off
|
||||||
|
- Using integer scaling would significantly increase development complexity with manual precision management across different currency types
|
||||||
|
- decimal.Decimal adds only microseconds vs potentially costly conversion errors
|
||||||
|
- Ensures user trust by avoiding strange rounding artifacts
|
||||||
|
- Better maintainability with clear intent and JSON-friendly serialization
|
||||||
|
|
||||||
|
**Conclusion**: The cost of precision errors in financial software far exceeds the minimal performance overhead of using decimal.Decimal.
|
||||||
|
|
||||||
|
### Currency Handling Strategy
|
||||||
|
|
||||||
|
Our implementation uses CoinMarketCap's `/v1/cryptocurrency/quotes/latest` endpoint with intelligent fallback logic:
|
||||||
|
|
||||||
|
- **Direct request**: Try the requested conversion (e.g., `USD → BTC`)
|
||||||
|
- **Fallback on empty data**: If no data returned, try reverse direction (`BTC → USD`) and invert the rate
|
||||||
|
- **Error handling**: Distinguish between unknown symbols, invalid parameters, and network issues
|
||||||
|
|
||||||
|
This approach handles both crypto-to-fiat and fiat-to-crypto conversions using only the cryptocurrency endpoint:
|
||||||
|
- **BTC → USD**: Direct API call works
|
||||||
|
- **USD → BTC**: API call fails (USD not in crypto DB), retry as BTC → USD, then invert rate
|
||||||
|
|
||||||
|
The automatic fallback with rate inversion eliminates the need for separate fiat currency handling while maintaining mathematical precision using decimal arithmetic.
|
||||||
|
|
||||||
|
### Currency Precision Strategy
|
||||||
|
|
||||||
|
To keep the implementation simple, we use a uniform precision rule:
|
||||||
|
|
||||||
|
- **All currencies**: 8 decimal places (covers cryptocurrency precision requirements)
|
||||||
|
|
||||||
|
This approach handles high-precision cryptocurrencies properly while being acceptable for fiat currencies (even though they typically use 2 decimal places). In a production environment, we would maintain per-currency precision metadata, but 8 decimals covers all practical use cases without overcomplicating the initial implementation.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
This project follows Clean Architecture principles and SOLID design patterns as specified in the requirements.
|
||||||
Loading…
Reference in a new issue