Skip to main content
The Money Component provides a domain model for representing monetary values with their currency. It’s a foundational component used across the application for price representation.

Overview

The money component provides:
  • Type-safe money representation avoiding primitive obsession
  • Currency-aware values with amount and symbol
  • Shared domain model used by multiple components
  • Simple, immutable data structure
The Money component is used by Product, Cart, and Wishlist components for consistent price representation.

Domain Model

Money

A simple data class representing a monetary amount with its currency.
package com.denisbrandi.androidrealca.money.domain.model

data class Money(val amount: Double, val currencySymbol: String)
amount
Double
The numeric value of the money (e.g., 19.99)
currencySymbol
String
The currency symbol or code (e.g., ”$”, ”€”, “USD”)

Why a Money Type?

The Money component demonstrates the Value Object pattern and avoids primitive obsession.
Without Money type:
data class Product(
    val id: String,
    val name: String,
    val price: Double,        // What currency?
    val currency: String,     // Easy to mix up parameters
    val imageUrl: String
)

// Easy to make mistakes:
Product("1", "Item", "USD", 19.99, "url")  // Wrong parameter order!
With Money type:
data class Product(
    val id: String,
    val name: String,
    val money: Money,         // Clear intent
    val imageUrl: String
)

// Type-safe construction:
Product("1", "Item", Money(19.99, "USD"), "url")  // Can't mix up!

Benefits

Type Safety

The Money type prevents common errors:
// Bad: Easy to confuse price with quantity
fun calculateTotal(price: Double, quantity: Int): Double {
    return price * quantity
}
calculateTotal(5, 19.99)  // Oops, swapped parameters!

// Good: Type system enforces correctness
fun calculateTotal(price: Money, quantity: Int): Money {
    return price.copy(amount = price.amount * quantity)
}
calculateTotal(Money(19.99, "$"), 5)  // Clear intent

Domain Clarity

Money makes the domain model more expressive:
// Clear that these are monetary values
data class Cart(val items: List<CartItem>) {
    fun getSubtotal(): Money? { /* ... */ }
}

data class CartItem(
    val id: String,
    val name: String,
    val money: Money,  // Obviously a price
    val quantity: Int  // Obviously a count
)

Consistency

All monetary values across components use the same structure:
// Product component
data class Product(
    val money: Money
)

// Cart component
data class CartItem(
    val money: Money
)

// Wishlist component
data class WishlistItem(
    val money: Money
)
Consistent use of Money makes it easy to move data between components without conversion logic.

Usage Examples

Creating Money Values

// Simple creation
val price = Money(19.99, "$")
val euroPrice = Money(17.50, "€")
val yenPrice = Money(2000.0, "¥")

// From API data
val product = Product(
    id = "1",
    name = "Laptop",
    money = Money(jsonProduct.price, jsonProduct.currency),
    imageUrl = jsonProduct.imageUrl
)

Calculations

// In Cart domain model
data class Cart(val cartItems: List<CartItem>) {
    fun getSubtotal(): Money? {
        return if (cartItems.isNotEmpty()) {
            val currency = cartItems[0].money.currencySymbol
            var subtotal = 0.0
            cartItems.forEach {
                subtotal += it.money.amount * it.quantity
            }
            Money(subtotal, currency)
        } else {
            null
        }
    }
}
The current implementation assumes all items in a cart share the same currency. For multi-currency support, you’d need additional logic.

Display Formatting

// In UI layer
@Composable
fun MoneyText(money: Money) {
    Text(text = "${money.currencySymbol}${String.format("%.2f", money.amount)}")
}

// Usage
MoneyText(Money(19.99, "$"))  // Displays: $19.99
MoneyText(Money(17.5, "€"))   // Displays: €17.50

Mapping Between Layers

// Data layer to domain layer
fun mapToProduct(dto: JsonProductResponseDTO): Product {
    return Product(
        id = dto.id.toString(),
        name = dto.name,
        money = Money(dto.price, dto.currency),  // DTO -> Domain
        imageUrl = dto.imageUrl
    )
}

// Domain layer to data layer
fun mapToDto(wishlistItem: WishlistItem): JsonWishlistItemCacheDTO {
    return JsonWishlistItemCacheDTO(
        id = wishlistItem.id,
        name = wishlistItem.name,
        price = wishlistItem.money.amount,      // Domain -> DTO
        currency = wishlistItem.money.currencySymbol,
        imageUrl = wishlistItem.imageUrl
    )
}

Value Object Pattern

The Money type is a Value Object in Domain-Driven Design terminology:
1

Immutable

Money is a data class with val properties - once created, it cannot be changed
2

Equality by value

Two Money instances with the same amount and currency are considered equal
3

No identity

Money objects don’t have a unique ID - they’re defined entirely by their values
4

Self-validating

Money encapsulates its invariants (though this simple implementation could be enhanced)
// Immutability
val price = Money(10.0, "$")
price.amount = 20.0  // Compilation error!

// Value equality
val price1 = Money(10.0, "$")
val price2 = Money(10.0, "$")
price1 == price2  // true

// Creating new values instead of mutating
val discountedPrice = price1.copy(amount = price1.amount * 0.9)

Potential Enhancements

While the current implementation is simple and effective, here are some enhancements you might consider:
data class Money private constructor(
    val amount: Double,
    val currencySymbol: String
) {
    init {
        require(amount >= 0) { "Money amount cannot be negative" }
        require(currencySymbol.isNotBlank()) { "Currency symbol required" }
    }
    
    operator fun plus(other: Money): Money {
        require(currencySymbol == other.currencySymbol) {
            "Cannot add different currencies"
        }
        return Money(amount + other.amount, currencySymbol)
    }
    
    operator fun times(multiplier: Int): Money {
        return Money(amount * multiplier, currencySymbol)
    }
    
    fun format(): String {
        return "$currencySymbol${String.format("%.2f", amount)}"
    }
    
    companion object {
        fun of(amount: Double, currencySymbol: String): Money {
            return Money(amount, currencySymbol)
        }
    }
}
Enhancements:
  • Input validation (no negative amounts)
  • Arithmetic operators (+, *, etc.)
  • Currency mismatch detection
  • Built-in formatting
  • Factory method for controlled construction

Testing

The Money type is straightforward to test:
@Test
fun `money equality works by value`() {
    val money1 = Money(10.0, "$")
    val money2 = Money(10.0, "$")
    assertEquals(money1, money2)
}

@Test
fun `money with different amounts are not equal`() {
    val money1 = Money(10.0, "$")
    val money2 = Money(20.0, "$")
    assertNotEquals(money1, money2)
}

@Test
fun `money with different currencies are not equal`() {
    val money1 = Money(10.0, "$")
    val money2 = Money(10.0, "€")
    assertNotEquals(money1, money2)
}

@Test
fun `copy creates new instance with updated values`() {
    val original = Money(10.0, "$")
    val doubled = original.copy(amount = original.amount * 2)
    
    assertEquals(10.0, original.amount)
    assertEquals(20.0, doubled.amount)
    assertEquals("$", doubled.currencySymbol)
}

Real-World Usage

Here’s how Money flows through a typical operation:
// 1. API returns product data
val apiResponse = JsonProductResponseDTO(
    id = 1,
    name = "Wireless Mouse",
    price = 29.99,
    currency = "$",
    imageUrl = "https://..."
)

// 2. Repository maps to domain model
val product = Product(
    id = "1",
    name = "Wireless Mouse",
    money = Money(29.99, "$"),  // Created here
    imageUrl = "https://..."
)

// 3. User adds to cart
val cartItem = CartItem(
    id = product.id,
    name = product.name,
    money = product.money,      // Passed along
    imageUrl = product.imageUrl,
    quantity = 2
)

// 4. Cart calculates subtotal
val cart = Cart(listOf(cartItem))
val subtotal = cart.getSubtotal()  // Money(59.98, "$")

// 5. UI displays formatted price
Text("${subtotal?.currencySymbol}${subtotal?.amount}")  // "$59.98"
Notice how the Money object flows seamlessly through all layers without needing conversion or transformation.

Architecture Impact

The Money component demonstrates several architectural principles:

Value Objects

Money is a pure value object with no identity or mutable state

Avoid Primitives

Using Money instead of separate Double and String prevents errors

Domain Language

Money makes the domain model speak the language of the business

Cross-Component

Shared across Product, Cart, and Wishlist components

Product Component

Uses Money for product pricing

Cart Component

Uses Money for item prices and subtotals

Wishlist Component

Uses Money for wishlist item prices

Domain Layer

Learn more about domain modeling

Build docs developers (and LLMs) love