Refactoring Currency: Mastering Universal Currency Value Objects with Enums and Attributes

Kostiantyn Bilous
SharpAssembly
Published in
4 min readJan 31, 2024

Precision is paramount in financial software development, where even seemingly minor components can have major implications. Among these, the representation of Currency stands as a foundational element that demands meticulous attention. This article delves into the art of refining Currency objects within financial systems. By adopting Domain-Driven Design (DDD) principles, we’ll explore how to transform Currency from a basic Entity into a well-defined Value Object. Discover how crafting robust and universal Currency Value Objects can subtly yet significantly enhance the clarity and reliability of financial applications.

Theoretical Underpinnings: Entities and Value Objects in Domain-Driven Design

In Domain-Driven Design (DDD), understanding the distinction between Entities and Value Objects is fundamental. Entities are defined by their identity, while Value Objects are characterized by their attributes. Often misconstrued as a mere attribute, Currency holds substantial complexity, primarily when interacting with different system parts. It could be misconceptually implemented as an Entity in the Bounded Context, where it naturally behaves as a Value Object.

Why Refactor Currency into a Value Object?

  • Immutability: Value Objects are immutable. This immutability aligns perfectly with the nature of the Currency, where the value shouldn’t change once instantiated.
  • Validation and Consistency: Encapsulating the currency as a Value Object allows centralized validation and business rule enforcement, ensuring Currency consistency across the system.
  • Reduction of Complexity: Representing Currency as a Value Object simplifies operations, abstracting the intricacies of currency handling into well-defined methods.

Practical Application: The Refactoring Process

Let’s dive into the practicalities using an example from the InWestMan application. This application is designed for personal investment tracking and uses Money as a Value Object.

Original Scenario: Currency as an Entity

Initially, the Currency in our system was an Entity laden with unnecessary complexity and potential for inconsistencies.

public class Currency : BaseEntity<int> 
{
public string Code { get; set; }
public string Name { get; set; }
public int FractionDigits { get; set; }
}

This Currency class is a part of the Money class, an Enterprise Pattern commonly used in Domain-Driven Design.

public sealed class Money : ValueObject
{
private readonly decimal _amount;

public Money(decimal amount, Currency currency)
{
_amount = amount;
Currency = currency;
}

public Currency Currency { get; }

public decimal Amount => decimal.Round(_amount, Currency.FractionDigits, MidpointRounding.ToEven);

protected override IEnumerable<object> GetEqualityComponents()
{
yield return Amount;
yield return CurrencyCode;
}

// Methods
}

Now, let’s define our refactoring strategy.

Refactoring Steps

1 Defining the Immutable Enum: We define a new CurrencyCode enum (e.g., ISO 4217), encapsulating all relevant properties and behaviors in its attributes.

public enum CurrencyCode
{
[EnumMember(Value = "USD")]
[Description("United States dollar")]
[FractionDigit(2)]
USD = 840,

[EnumMember(Value = "UAH")]
[Description("Ukrainian hryvnia")]
[FractionDigit(2)]
UAH = 980,

[EnumMember(Value = "EUR")]
[Description("Euro")]
[FractionDigit(2)]
EUR = 978

...
}

2 Store data in memory as Attributes: We ensure that instances of CurrencyCode contain all relevant data.

[AttributeUsage(AttributeTargets.Field)]
public class FractionDigitAttribute : Attribute
{
public FractionDigitAttribute(int fractionDigit)
{
FractionDigit = fractionDigit;
}

public int FractionDigit { get; private set; }
}

3 Centralizing Business Logic: Currency-related data (like full name, fraction digits, etc.) are centralized within the CurrencyCode enum, promoting code reusability and maintainability by static extensions.

public static class CurrencyCodeExtensions
{
public static int GetFractionDigit(this CurrencyCode currencyCode)
{
var type = currencyCode.GetType();
var memInfo = type.GetMember(currencyCode.ToString());
if (memInfo.Length > 0)
{
var attributes = memInfo[0].GetCustomAttributes(typeof(FractionDigitAttribute), false);
if (attributes.Length > 0)
return ((FractionDigitAttribute)attributes[0]).FractionDigit;
}

return 2;
}
}

In the context of our Money class, the refactoring leads to more transparent, more concise operations:

public sealed class Money : ValueObject
{
private readonly decimal _amount;
private readonly int _fractionDigits;

public Money(decimal amount, CurrencyCode currencyCode)
{
_amount = amount;
CurrencyCode = currencyCode;
_fractionDigits = CurrencyCode.GetFractionDigit();
}

public decimal Amount => decimal.Round(_amount, _fractionDigits, MidpointRounding.ToEven);

public CurrencyCode CurrencyCode { get; }

protected override IEnumerable<object> GetEqualityComponents()
{
yield return Amount;
yield return CurrencyCode;
}

// Other methods
}

The Money class, now interacting with CurrencyCode as a universal Value Object and represents the financial domain more expressively.

Conclusion: The Strategic Impact of Refactoring

The transition from treating Currency as an Entity to a Value Object in financial software isn’t merely a technical refactor but a strategic enhancement. It aligns the codebase with the principles of Domain-Driven Design, promoting clarity, consistency, and maintainability. This refactoring journey exemplifies how thoughtful changes in code structure can lead to more robust, reliable, and scalable financial systems.

Subscribe for more insights and in-depth analysis:

Credits: DALL·E generated

#DDD #InWestMan

--

--

Kostiantyn Bilous
SharpAssembly

Senior Software Engineer (.Net/C#) at SimCorp, Ph.D. in Finance