Refactoring Currency: Mastering Universal Currency Value Objects with Enums and Attributes
Tags: ddd, programming, csharp, designpatterns
Published: 2024-01-30 21:16:14

[Original article on Medium](https://sharpassembly.medium.com/refactoring-currency-mastering-universal-currency-value-objects-with-enums-and-attributes-171b1ae5681d)

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 representation 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.
```csharp
public class Currency : BaseEntity<int> 
{
    public string Code { get; set; }
    public string Name { get; set; }
    public int FractionDigits { get; set; }
}
```
This Currency is used in theMoney class, which is an Enterprise Pattern commonly used in Domain-Driven Design.

**Link to Article on Money Patterns:**
[Money Patterns in Domain-Driven Design: Navigating the Complexities of Financial Systems](https://dev.to/sharpassembly/mastering-financial-complexity-with-money-patterns-in-domain-driven-design-13p0)

```csharp
public sealed class Money : ValueObject
{
    private readonly decimal _amount;
    private readonly Currency _currency;

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

    public Currency Currency => _currency;

    public decimal Amount => decimal.Round(_amount, _currency.FractionDigits, MidpointRounding.ToEven);
    
    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Amount;
        yield return Currency.Code;
    }

    // Methods
}
```

**Refactoring Steps:**
**1.** **Defining the Immutable Enum:** We define a new CurrencyCode enum (e.g. [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217)), encapsulating all relevant properties and behaviors in its attributes.

```csharp
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.** **Storing Data in Memory as Attributes:** We ensure that instances of CurrencyCode contain all relevant data.

```csharp
[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.

```csharp
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:
```csharp
public sealed class Money : ValueObject
{
    private readonly decimal _amount;
    private readonly CurrencyCode _currencyCode;
    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 => _currencyCode;

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

    // Other methods 
}
```

## **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.

[Original article on Medium](https://sharpassembly.medium.com)

Stay tuned for more insights and detailed analyses, and feel free to share your thoughts or questions in the comments below!

[SharpAssembly on Dev.to](https://dev.to/sharpassembly)
[SharpAssembly on Medium](https://sharpassembly.medium.com)
[SharpAssembly on Telegram](https://t.me/SharpAssembly)

_Cover credits: DALL·E generated_