Leveraging value objects & domain services

Introduction

Over the years I have come to see and also been guilty of not modelling concepts that should be treated as such in the domain your working in and instead what this leads to is 2 primary code smells:

The first manifests itself as values that get passed around together but are always taken by functions as standalone parameters there is usually a breakage of DRY here with knowledge of what the parameters represent scattered around the codebase. The second relates to a refusal to try and apply object thinking and instead procedures are created with no real intent as to where the behaviour should reside and because were in an OOP language they have to live somewhere right? So we end up with badly named classes that just become containers of procedures (and if you’re really unlucky they deal with a whole host of unrelated responsibilities!)

Resolving

So how can we resolve these issues? Well as it turns out we can usually stamp out both by introducing a Value Object that properly represents the concept in our domain by making this first step we can usually start moving behaviour related to this new concept from the *Util class into the actual object here it becomes a much more rich object with it’s own behaviour instead of just being a dumb data holder.

There are however times when certain behaviour cannot just be moved inside the new concept object and for these cases you will probably want to introduce a Domain Service object the difference here between the Util class vs. the Domain Service is that it is specific for a certain operation that you want to perform and as such can be properly named around the current domain your working in.

Example

The example I’m going to show has been a really common scenario I have found while working on various financial systems for a number of years.

In a financial domain you will have a concept of Money it will consist of:

  • Amount
  • Currency

Seems fairly straightforward and it is, however lets look at how I typically see this concept dealt with in code.

public static string FormatMoney(IDictionary<string, int> currencyDecimalPlacesLookup, decimal amount, string currency, bool includeCurrency)
{
    // lookup for decimal places
    // string.Format using amount with found decimal places and currency flag
}

public static string FormatCurrencyDescription(Dictionary<string, string> currencyDescLookup, string currency)
{
    // lookup by currency code
}

public static decimal ConvertGBP(Dictionary<string, decimal> xrates, string currency, decimal amount)
{
   // lookup rate by currency code
   // apply conversion
}

Here is an example of their usage:

string balance = MoneyUtil.FormatMoney(_decimalPlacesLookup, account.PostedBalance, account.CurrencyCode);

string currDesc = MoneyUtil.FormatCurrencyDescription(_currDescLookup, account.CurrencyCode);

decimal inGBP = MoneyUtil.ConvertGBP(_xrateLookup, account.CurrencyCode, account.PostedBalance);

So to start with these probably were just inlined inside one of the calling functions but then someone saw they needed to do the same thing somewhere else and created the MoneyUtil class hooray for reuse! As you could imagine these will then continue to grow and become dumping grounds for any behaviour related to Money, you can already see that unrelated responsibilities are being introduced formatting & currency conversion have no reason to live together, you can also see that the caller is having to manage lookup dictionaries which is another sign of primitive obsession and that modelling concepts are being missed.

As described in the introduction we can see that certain values are being passed around together in this case the amount & currency however we have not captured the intent of these values by introducing a Money object, instead we will introduce the concepts of the domain and see how that changes things:

public class Currency : IEquatable<Currency>
{
	public Currency(string code, int decimalPlaces, string description)
	{
		Code = code;
		DecimalPlaces = decimalPlaces;
		Description = description;
	}

	public string Code { get; private set; }
	public int DecimalPlaces { get; private set; }
	public string Description { get; private set; }
	
	public override string ToString()
	{
		return Code;
	}

// ... emitted rest of implementation
}

First we have the concept of a Currency this may seem like a pointless object however even just doing this step alone would change our signatures for the functions above so that no longer do they just accept a magic string for currency code but instead a properly defined Currency object that already has its information encapsulated in once place.

There a few items to note when creating Value Objects:

  • They are immutable
  • Identity is based off their properties (I have not shown this here but you can see in the full source)
public class Money : IEquatable<Money>
{	
	public Money(decimal amount, Currency currency)
	{
		Amount = amount;
		Currency = currency;
	}

	public decimal Amount { get; private set; }
	public Currency Currency { get; private set; }

// ... emitted rest of implementation

	public override string ToString()
	{
		return string.Format("{0} {1}", Currency, FormatAmount());
	}

	private string FormatAmount()
	{
		decimal val = Amount * (decimal)Math.Pow(10, Currency.DecimalPlaces);
		val = Math.Truncate(val);
		val = val / (decimal)Math.Pow(10, Currency.DecimalPlaces);
		return string.Format("{0:N" + Math.Abs(Currency.DecimalPlaces) + "}", val);
	}
}

We can see that the Money object has to be supplied a Currency at creation and that it now has the smarts to know how to format correctly using the details of its Currency, if you wanted more control over the formatting you could provide an overload to take in a formatter object.

With the changes made above we have nearly made the MoneyUtil class redundant however there is the operation of converting existing Money to Money in another Currency (in this case GBP) this operation is prime example were a Domain Service is a good fit and captures the concept of converting Money from one Currency to another Currency.

First we can define an interface to represent the operation.

public interface ICurrencyConverter
{
    Money ConvertTo(Money from);
}

This captures the operation using the language of the domain and were already getting the benefit of our Money and Currency objects by eliminating primitive obsession were no longer passing strings and decimals but instead fully realised domain concepts.

Next we can implement a CurrencyConverter to convert to a specific Currency.

public class ToCurrencyConverter : ICurrencyConverter
{
	private readonly Currency _toCurrency;
	private readonly IDictionary<Currency, decimal> _rates;

	public ToCurrencyConverter(Currency toCurrency, IDictionary<Currency, decimal> rates)
	{
		_toCurrency = toCurrency;
		_rates = rates;
	}

	public Money ConvertTo(Money from)
	{
		if (!_rates.ContainsKey(from.Currency))
		{
			throw new InvalidOperationException(string.Format("Could not find rate from currency: {0} to: {1}", from.Currency, _toCurrency));
		}

		decimal rate = _rates[from.Currency];
		return new Money(from.Amount * rate, _toCurrency);
	}
}

On creation we provide the converter the Currency we want to convert to and also the rates that should be used, these would typically be populated daily and then cached for faster access although these are concerns way outside of the domain model and it does not concern itself with how these are provided.

With this final piece of the puzzle complete we can now take joy in removing our MoneyUtil class and instead having a richer domain model.

I have published code to go along with this post that can be found here https://github.com/stavinski/joasd-domainconcepts

Advertisements