Define domain layer
This commit is contained in:
parent
ffc0b0d0ac
commit
a537d1c0bb
131
domain/entities.go
Normal file
131
domain/entities.go
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// Domain-specific errors
|
||||
var (
|
||||
ErrInvalidCurrency = errors.New("invalid currency")
|
||||
ErrInvalidAmount = errors.New("invalid amount")
|
||||
ErrNegativeAmount = errors.New("amount cannot be negative")
|
||||
ErrInvalidRate = errors.New("invalid exchange rate")
|
||||
ErrCurrencyMismatch = errors.New("currency mismatch")
|
||||
ErrRateNotFound = errors.New("exchange rate not found")
|
||||
ErrUnsupportedCurrency = errors.New("unsupported currency")
|
||||
)
|
||||
|
||||
// Currency represents a currency with its metadata
|
||||
type Currency struct {
|
||||
Code string
|
||||
Name string
|
||||
Precision int
|
||||
}
|
||||
|
||||
// NewCurrency creates a new Currency with validation
|
||||
func NewCurrency(code, name string, precision int) (Currency, error) {
|
||||
if strings.TrimSpace(code) == "" {
|
||||
return Currency{}, ErrInvalidCurrency
|
||||
}
|
||||
|
||||
if precision < 0 {
|
||||
return Currency{}, ErrInvalidCurrency
|
||||
}
|
||||
|
||||
return Currency{
|
||||
Code: strings.ToUpper(strings.TrimSpace(code)),
|
||||
Name: strings.TrimSpace(name),
|
||||
Precision: precision,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the currency code
|
||||
func (c Currency) String() string {
|
||||
return c.Code
|
||||
}
|
||||
|
||||
// Money represents an amount in a specific currency
|
||||
type Money struct {
|
||||
Amount decimal.Decimal
|
||||
Currency Currency
|
||||
}
|
||||
|
||||
// NewMoney creates a new Money instance from string amount
|
||||
func NewMoney(amount string, currency Currency) (Money, error) {
|
||||
if strings.TrimSpace(amount) == "" {
|
||||
return Money{}, ErrInvalidAmount
|
||||
}
|
||||
|
||||
decimalAmount, err := decimal.NewFromString(strings.TrimSpace(amount))
|
||||
if err != nil {
|
||||
return Money{}, ErrInvalidAmount
|
||||
}
|
||||
|
||||
if decimalAmount.IsNegative() {
|
||||
return Money{}, ErrNegativeAmount
|
||||
}
|
||||
|
||||
return Money{
|
||||
Amount: decimalAmount,
|
||||
Currency: currency,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewMoneyFromDecimal creates a new Money instance from decimal.Decimal
|
||||
func NewMoneyFromDecimal(amount decimal.Decimal, currency Currency) (Money, error) {
|
||||
if amount.IsNegative() {
|
||||
return Money{}, ErrNegativeAmount
|
||||
}
|
||||
|
||||
return Money{
|
||||
Amount: amount,
|
||||
Currency: currency,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns formatted money representation
|
||||
func (m Money) String() string {
|
||||
return fmt.Sprintf("%s %s", m.Amount.StringFixed(int32(m.Currency.Precision)), m.Currency.Code)
|
||||
}
|
||||
|
||||
// Rate represents an exchange rate between two currencies
|
||||
type Rate struct {
|
||||
From Currency
|
||||
To Currency
|
||||
Value decimal.Decimal
|
||||
Timestamp time.Time
|
||||
Source string
|
||||
}
|
||||
|
||||
// NewRate creates a new Rate with validation
|
||||
func NewRate(from, to Currency, value decimal.Decimal, source string) (Rate, error) {
|
||||
if from.Code == to.Code {
|
||||
return Rate{}, ErrInvalidRate
|
||||
}
|
||||
|
||||
if value.IsNegative() || value.IsZero() {
|
||||
return Rate{}, ErrInvalidRate
|
||||
}
|
||||
|
||||
return Rate{
|
||||
From: from,
|
||||
To: to,
|
||||
Value: value,
|
||||
Timestamp: time.Now(),
|
||||
Source: strings.TrimSpace(source),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
// String returns formatted rate representation
|
||||
func (r Rate) String() string {
|
||||
return fmt.Sprintf("1 %s = %s %s",
|
||||
r.From.Code,
|
||||
r.Value.StringFixed(int32(r.To.Precision)),
|
||||
r.To.Code)
|
||||
}
|
||||
Loading…
Reference in a new issue