March 04, 2011

Exchanging currencies with Zend_Currency_CurrencyInterface

As many companies offering online services do, we handle multiple currencies. This often involves querying external services to know the real value of the currency exchange. Previously, this logic was scattered all over the code, even copy-pasted in multiple files, and is pretty hard to mantain, let alone to test.

So, in my work detecting and extracting patterns from old spaghetti code, I came with the following solution: A Service layer.

Even spaghetti code talks by itself (maybe not too loud), and this code was talking about three things: select a currency, ask the webservice for the exchange rate, and cache the response. For the sake of testing and ease of use, I decided to use a Decorator pattern.

Test first

First I made a test, to show how the final API should look like:

$amount    = 19.95;
$rate      = 2.0;
$currency  = new Currency($amount, Currency::EUR);
$converter = new Exchange\ExchangeMockup;
$converter->setRate(Currency::EUR, Currency::USD, $rate); // Only for testing purposes
$dollars   = $currency->exchangeTo(Currency::USD, $converter);

assert($dollars instanceof Currency);
assert($dollars->getCurrency() === Currency::USD);
assert($dollars->getAmount() === $amount * $rate);

Currency

As well as existing libraries worked, like Zend_Currency, they do too much for me, for now. I only want to pack amount and currency in a single class, so Currency came to life.

/**
* @filename Payment/Currency.php
*/
namespace Payment;

class Currency
{
    const EUR = 'EUR';
    const USD = 'USD';
    const GBP = 'GBP';

    public function __construct($amount = 0, $currency = self::EUR)
    {
        $this->setAmount($amount)
             ->setCurrency($currency);
    }

    private $amount;

    public function getAmount()
    {
        return $this->amount;
    }

    public function setAmount($amount)
    {
        $this->amount = $amount;
        return $this;
    }

    private $currency;

    public function getCurrency()
    {
        return $this->currency;
    }

    public function setCurrency($currency)
    {
        $this->currency = $currency;
        return $this;
    }

    public function convertTo($newCurrency, $service)
    {
        // Not implemented yet. See below
    }
}

Ok, this is a no-brainer, quite simple POD. It's only work is to pack amount and currency in the same class. Is in cases like this that I miss my C++ enums so much. Using class constants is as far we can go. Note the use of fluent interface in the setters: It's a habit I have, and I like it. :P

ExchangeService interface

In order to allow decoration of our classes, it's a good practice to declare a common interface for all of them. That's where ExchangeService enters the scene:

/**
* @filename ExchangeService.php
*/
namespace Payment;

interface ExchangeService
{
    /**
     * Current exchange rate between $from and $to
     *
     * @param string $from Short name of the base currency
     * @param string $to   Short name of the currency to exchange to
     */
    public function getRate($from, $to);
}

In fact this is a copy from Zend_Currency_CurrencyInterface, so the file contents could become this:

/**
* @filename ExchangeService.php
*/
namespace Payment;

interface ExchangeService extends \Zend_Currency_CurrencyInterface
{
}

Or even inherit directly from Zend_Currency_CurrencyInterface.

Now it's time to go with the real implementation of the service.

Webservicex.NET Exchange Service

The old implementation made calls to this webservice, and, since I could not find any other easy and free webservices, I stick with it. Its usage is quite simple, just make a GET request to this url: http://www.webservicex.net/CurrencyConvertor.asmx/ConversionRate?FromCurrency=GBP&toCurrency=EUR and you recieve an XML with the result. Easy as pie, show now the code:

/**
* Payment/Exchange/WebserviceX.php
*/
namespace Payment\Exchange;

use \Zend_Http_Client                   as HttpClient;
use \Zend_Http_Client_Adapter_Interface as HttpAdapter;
use \Payment\ExchangeService            as ExchangeService;

class WebserviceX implements ExchangeService
{
    const URI = 'http://www.webservicex.net/CurrencyConvertor.asmx/ConversionRate';

    private $adapter;

    public function __construct(HttpAdapter $adapter)
    {
        $this->adapter = $adapter;
    }

    public function getRate($from, $to)
    {
        $adapter = $this->adapter;
        $client = new HttpClient(self::URI, compact('adapter'));
        $client->setParameterGet(array(
            'FromCurrency' => $from,
            'toCurrency'   => $to,
        ));
        $response = $client->request();
        $body = $response->getBody();
        $xml = new \SimpleXMLElement($body);
        return (string) $xml;
    }
}

I'm using Zend_HTTP_Client to make the request and SimpleXML to parse the response. The constructor expects a Zend_HTTP_Client_Adapter in order to inject a mock adapter like Zend_HTTP_Client_Adapter_Test for testing purposes.

This class works all alone, but it makes a request to the webservice every time. I thought of adding a cache in the class, but… what's the point? If I ever want to change to another webservice, I should add the cache there too. So, I'm gonna decorate this class. With a cache.

ArrayCacheDecorator

To allow this object to replace WebserviceX, we must implement the same ExchangeService interface. Then, redirect to the underlying object only if the request is not in the array cache.

/**
* @filename ArrayCacheDecorator
*/
namespace Payment\Exchange;

use Payment\ExchangeService;

class ArrayCacheDecorator implements ExchangeService
{
    public function __construct(ExchangeService $service)
    {
        $this->setService($service);
    }

    private $service;

    public function getService()
    {
        return $this->service;
    }

    public function setService(ExchangeService $service)
    {
        $this->service = $service;
        return $this;
    }

    private $rates = array();

    public function getRate($from, $to)
    {
        $rate = &$this->rates["{$from}_{$to}"];
        if (isset($rate)) {
            return $rate;
        }

        $inversedRate = &$this->rates["{$to}_{$from}"];
        if (isset($inversedRate)) {
            return 1.0 / $rate;
        }

        $rate = $this->service->getRate($from, $to);
        return $rate;
    }
}

This layer is quite simple too. It stores a rates array with every conversion rate requested. If the rate exists, it's returned. If not, ask the underlying service and save the result in the array. I added a simple inverse rate to speed requests of B->A when A->B is in the cache, making the assumption that rate_AtoB = 1.0 / rate_BtoA

Now we can stack our services, decorate one with the other, like this:

$adapter = new \Zend_Http_Client_Adapter_Curl;
$service = new Payment\Exchange\WebserviceX($adapter);
$service = new Payment\Exchange\ArrayCacheDecorator($service);

// Makes a request to WebserviceX
$rate1   = $service->getRate(Currency::USD, Currency::EUR);

// Avoids WebserviceX by ArrayCacheDecorator
$rate2   = $service->getRate(Currency::USD, Currency::EUR);

// Avoids WebserviceX by ArrayCacheDecorator inverse rate feature
$inverse = $service->getRate(Currency::EUR, Currency::USD);

Good! That's a real step towards speeding the process! But there's still something wrong: What if the webservice is down? What if there's a timeout? We need a fallback approach, another level of cache, either disk, database, memcache or whatever…

ZendCacheDecorator

I created then a decorator to add the greatness of Zend_Cache into the system.

/**
* @filename Paymeny/Exchange/ZendCacheDecorator.php
*/
namespace Payment\Exchange;

use \Zend_Cache_Core         as Cache;
use \Payment\ExchangeService as ExchangeService;

class ZendCacheDecorator implements ExchangeService
{
    public function __construct(ExchangeService $service, Cache $cache)
    {
        $this->setService($service)
             ->setCache($cache);
    }

    private $service;

    public function setService(ExchangeService $service)
    {
        $this->service = $service;
        return $this;
    }

    public function getService()
    {
        return $this->service;
    }

    private $cache;

    public function setCache(Cache $cache)
    {
        $this->cache = $cache;
        return $this;
    }

    public function getCache()
    {
        return $this->cache;
    }

    public function getRate($from, $to)
    {
        $id     = "{$from}_{$to}";
        $cache  = $this->getCache();
        $result = $cache->load($id);

        if ($result === false) {
            try {
                $result = $this->getService()
                               ->getRate($from, $to);

            } catch(\Exception $e) {
                // Result is false yet, so do nothing
            }

            if ($result === false) {
                $result = $cache->test($id) ? $cache->load($id, false) : false;
            } else {
                $cache->save($result);
            }
        }

        return $result;
    }
}

This decorator is in no way harder than the last one. It requires a Zend_Cache_Core instance that does The Hard Work™. First checks for a valid cache entry and, if it is found, it's returned. Otherwise, it gets the rate from the underlying layer. When no exceptions occur and the next layer returns a valid value, it is saved in the cache for further use.

In case of error, the result is the latest value stored on the cache, whether it's stale or not, providing a fallback for operations that, otherwise, will fail (timeout).

exchangeTo($currency, $service)

Now it's time to implement Currency::exchangeTo. Note the use of ExchangeService, decoupling Currency class from real implementations of the interface. Also, a new Currency object is returned on conversion.

/**
* @filename Payment/Currency.php
*/
class Currency
{
    ...

    public function exchangeTo($newCurrency, ExchangeService $service)
    {
        $currentCurrency = $this->getCurrency();

        // No need to convert between same currency
        if ($newCurrency === $currentCurrency) {
            return $this;
        }

        $rate = $service->getRate($currentCurrency, $newCurrency);
        $amount = $this->getAmount() * $rate;
        return new Currency($amount, $newCurrency);
    }

    ...
}

ExchangeMockup

In order to avoid calls to the real exchange services when testing our code using Currency, we can create a mockup class. This class replaces the real call to the service with preconfigured results specified by the user. Something like this:

$service = new Payment\Exchange\ExchangeMockup;
$service->setRate(Currency::EUR, Currency::USD, 2.0);
$service->setRate(Currency::EUR, Currency::GBP, 0.5);

assert($service->getRate(Currency::EUR, Currency::USD) === 2.0);
assert($service->getRate(Currency::EUR, Currency::GBP) === 0.5);

The class is pretty straightforward and is left as an exercise for the reader.

Bonus track

As a side bonus, this services can be used with Zend_Currency::setService too, and with your own currency classes, if you want.

No comments:

Post a Comment