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