Monetary value representation with amount and currency
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.
The Money component demonstrates the Value Object pattern and avoids primitive obsession.
Primitive Obsession Anti-Pattern
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!
// Clear that these are monetary valuesdata 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)
All monetary values across components use the same structure:
// Product componentdata class Product( val money: Money)// Cart componentdata class CartItem( val money: Money)// Wishlist componentdata class WishlistItem( val money: Money)
Consistent use of Money makes it easy to move data between components without conversion logic.
@Testfun `money equality works by value`() { val money1 = Money(10.0, "$") val money2 = Money(10.0, "$") assertEquals(money1, money2)}@Testfun `money with different amounts are not equal`() { val money1 = Money(10.0, "$") val money2 = Money(20.0, "$") assertNotEquals(money1, money2)}@Testfun `money with different currencies are not equal`() { val money1 = Money(10.0, "$") val money2 = Money(10.0, "€") assertNotEquals(money1, money2)}@Testfun `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)}