Click here to Skip to main content
15,885,216 members
Articles / Programming Languages / C#

NMoneys.Exchange, Companion for NMoneys

Rate me:
Please Sign up or sign in to vote.
4.43/5 (4 votes)
29 Sep 2011CPOL6 min read 16.6K   145   10  
NMoneys.Exchange extends NMoneys (Money Value Object implementation) to support exchange of currencies
NMoneys_long.png

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.

C#
[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.

C#
[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);

	// go back to default
	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.

  1. First, come up with a ExchangeRate inheritor that performs the operations in the desired fashion:
    C#
    public class CustomRateArithmetic : ExchangeRate
    {
        public CustomRateArithmetic(CurrencyIsoCode from, 
    	CurrencyIsoCode to, decimal rate) : base(from, to, rate) { }
    
        public override Money Apply(Money from)
        {
            // instead of this useless "return 0" policy one can 
            // implement rounding policies, for instance
            return new Money(0m, To);
        }
    }
  2. Then create the IExchangeRateProvider implementation that makes use of this custom rate applying logic:
    C#
    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;
        }
    }
  3. 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.
    C#
    [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()));
        // go back to default
        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.

  1. Extend the IExchangeConversion entry point to return a custom type.
    C#
    public static UsingImplementor Using
    	(this IExchangeConversion conversion, decimal rate)
    {
        return new UsingImplementor(conversion.From, rate);
    }
  2. Implement the custom type.
    C#
    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);    
        }
    }
  3. Use your newly shaped API to paint yourself into an ugly corner. ;-)
    C#
    [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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader
Denmark Denmark
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --