Background
I have already written about NMoneys in this article, but I will do a tiny recap for those in a rush.
NMoneys is a "Money Value Object" implementation for the .NET platform that supports the ISO 4217 standard.
That definition means that with that library, we are able to represent and operate with monetary quantities in a variety of currencies.
The informal scope of the project establishes that NMoneys does not provide any support for exchanging monetary quantities in different currencies. In fact, all the operations are defined in terms of quantities with the same currency.
Surprisingly enough (or maybe not), most of the feedback I received was on the line of adding those capabilities. Reluctant as I was at first, I could not but listen, shrug and give it a shot. And this is what I came up with.
Disclaimer
Do not get false impressions: IT IS NOT a currency exchange service. It is just a mean to allow exchange operations to happen between the monetary quantities. One still needs to feed and/or retrieve real, current financial data from a reliable third party in order for the operations to be accurate.
Using the Code
All code for snippets (and more that is not published for brevity) will be included in the demo project (Visual Studio 2010, .NET 4.0).
The code for the demo project can also be browsed from the horse's mouth in the project web site. Likewise, the latest version of the code for NMoneys
and NMoneys.Exchange
libraries can be easily accessed here and here.
Extend and Conquer
Independence
One thing I was determined from the very beginning: whichever features were to be added, they would be added to another project and would not "pollute" the simplicity and focus of the original project.
After some thinking, I came to the conclusion that extension methods on the Money
class should become the entry API for the new features and a new library would host them. That model would allow different release cycles for each project and would allow clients that do not need the new capabilities remain "bloat"-free.
Simplicity
After independence, simplicity is the theme to guide the design of the library.
One of the reasons exchange operations were not even considered for the library in the first place was their complexity. Operations such as conversions on fractional quantities are not trivial; there are roundings, losses and all sorts of pitfalls that, when it comes to money, one cannot obviate. I do not posses the knowledge of the rules that command such operations and yet I am decided to implement them. How dare! Well, not much. Since amounts are modeled as System.Decimal
quantities, the simplest that can possibly work was used as a "safe" default: use the existing product and division operations.
Extensibility
I am well aware that the simplest, default operations might not be suitable for everyone (they might even be incorrect). What has been done to protect correctness and integrity is provide multiple extensibility points that would allow those "in the know" to do the right thing. It would be equally awesome that those enlightened ones contributed with the rest of the world those extensions. ;-p
Conversions
Once the .NET project references the NMoneys.dll and the NMoneys.Exchange.dll assemblies, conversion operations can be brought to the code by just using the NMoneys.Exchange
namespace. That action would enable the .Convert()
extension method on Money
instances.
Meaningless Defaults
The simplest (and probably the most useless) of conversions one can perform is a direct conversion, meaning that one monetary quantity, let's say 3 Euros, would be converted to 3 US dollars or 3 pounds if all defaults were used. Clearly, a pretty little mess.
[Test]
public void Default_Conversions_DoNotBlowUpButAreNotTerriblyUseful()
{
var tenEuro = new Money(10m, CurrencyIsoCode.EUR);
var tenDollars = tenEuro.Convert().To(CurrencyIsoCode.USD);
Assert.That(tenDollars.Amount, Is.EqualTo(10m));
var tenPounds = tenEuro.Convert().To(Currency.Gbp);
Assert.That(tenPounds.Amount, Is.EqualTo(10m));
}
Defaults must be changed. One way would have been providing a decimal rate conversion somewhere in the method call chain. Simple, ugly in some many levels, but certainly doable given the multiple extension points of the framework. Passing a hardcoded rate (volatile as they are) is not the way to go; forcing developers to create some sort of provider of values, so why not embed it in the framework?
X will Provide
A straightforward provider model is used to override the default rates. An implementation of IExchangeRateProvider
that allows more correct conversions needs to be configured. That is done by setting a delegate into the property ExchangeRateProvider.Factory
. A completely "from scratch" implementation that consults an on-line provider is welcome and so is the already tagged as useless ExchangeRateProvider.Default
.
[Test]
public void Configuring_Provider()
{
var customProvider = new TabulatedExchangeRateProvider();
customProvider.Add(CurrencyIsoCode.EUR, CurrencyIsoCode.USD, 0);
ExchangeRateProvider.Factory = () => customProvider;
var tenEuro = new Money(10m, CurrencyIsoCode.EUR);
var zeroDollars = tenEuro.Convert().To(CurrencyIsoCode.USD);
ExchangeRateProvider.Factory = ExchangeRateProvider.Default;
}
From the example, one can peek another implementation of the IExchangeRateProvider
, the TabulatedExchangeRateProvider
. This provider eases the creation of "static
" exchange rates tables, which might be useful in some domains, mostly in terms of caching calls. Use your favorite Inversion of Control container, complex ad-hoc type creation policies and one can do pretty clever things to save request to real-time providers. A more complete set of capabilities of the class is showcased in its unit tests, such as the ability to add rates and calculate their inverse rates.
Highly Rated
Useless default implementation is left behind, fresh new rates can be fed into the system by the use of a custom provider and yet the default calculations performed by ExchangeRate
are unsuitable for your purposes. Should you give up? Absolutely not. Having the ability to use a custom provider enables that custom provider to return inheritors of ExchangeRate
that use custom logic to perform calculations.
- First, come up with a
ExchangeRate
inheritor that performs the operations in the desired fashion:
public class CustomRateArithmetic : ExchangeRate
{
public CustomRateArithmetic(CurrencyIsoCode from,
CurrencyIsoCode to, decimal rate) : base(from, to, rate) { }
public override Money Apply(Money from)
{
return new Money(0m, To);
}
}
- Then create the
IExchangeRateProvider
implementation that makes use of this custom rate applying logic:
public class CustomArithmeticProvider : IExchangeRateProvider
{
public ExchangeRate Get(CurrencyIsoCode from, CurrencyIsoCode to)
{
return new CustomRateArithmetic(from, to, 1m);
}
public bool TryGet(CurrencyIsoCode from,
CurrencyIsoCode to, out ExchangeRate rate)
{
rate = new CustomRateArithmetic(from, to, 1m);
return true;
}
}
- And last, for not least, make the framework aware that your calculations are performed by the provider just implemented, using the technique shown before: setting the
ExchangeRateProvider.Factory
delegate.
[Test]
public void Use_CustomArithmeticProvider()
{
var customProvider = new CustomArithmeticProvider();
ExchangeRateProvider.Factory = () => customProvider;
var zeroDollars = 10m.Eur().Convert().To(CurrencyIsoCode.USD);
Assert.That(zeroDollars, Is.EqualTo(0m.Usd()));
ExchangeRateProvider.Factory = ExchangeRateProvider.Default;
}
Redefine the API
One can go as far as redefining how the API looks. We mentioned that providing a fixed number as a rate might not be the most clever of the ideas, but one is able to do it, nonetheless.
- Extend the
IExchangeConversion
entry point to return a custom type.
public static UsingImplementor Using
(this IExchangeConversion conversion, decimal rate)
{
return new UsingImplementor(conversion.From, rate);
}
- Implement the custom type.
public class UsingImplementor
{
private readonly Money _from;
private readonly decimal _rate;
public UsingImplementor(Money from, decimal rate)
{
_from = from;
_rate = rate;
}
public Money To(CurrencyIsoCode to)
{
var rateCalculator = new ExchangeRate(_from.CurrencyCode, to, _rate);
return rateCalculator.Apply(_from);
}
}
- Use your newly shaped API to paint yourself into an ugly corner. ;-)
[Test]
public void Creating_New_ConversionOperations()
{
var hundredDollars = new Money(100m, CurrencyIsoCode.USD);
var twoHundredEuros = hundredDollars.Convert().Using(2m).To
(CurrencyIsoCode.EUR);
Assert.That(twoHundredEuros, Is.EqualTo(200m.Eur()));
}
Of course, the extensibility of the API can be used to solve some other problems, such as providing different conversions for buy/sell currencies or many other smart scenarios that I cannot fabricate.
Wrapping It Up
I had several goals with this article.
Make a point that NMoneys library is alive and kicking.
Secondly, that feedback was very welcome. And, as a result, NMoneys.Exchange was implemented.
And last, but not least, take the chance to show how an API can be extensible and non-intrusive in order not to bloat the original project.
Write code, share it and be merry.
History
- 25-Sep-2011 - Initial version