During late 2011 to early 2012, JustGiving re-evaluated our approach to internationalisation. We shifted focus from having several systems sharing the same brand, too consolidating around our first, and biggest system, based out of London. As we started to consolidate our technology, we began maturing our software to support both internationalisation and multi-tenancy, making the decision that “one platform to rule them all” was a more appropriate design choice that endlessly porting features between different regional installations.
As part of this process, we had to evaluate the software and frameworks that we used while building new international functionality. One of the cornerstones involved enhancing our existing, fairly simplistic payment processing facilities, and enhancing them to support multiple currencies, multiple operating accounts, in different regions, through different payment service providers. We knew that we couldn’t rely on our sole existing payment provider (well, and PayPal) if we were to accept and settle transactions in more than twenty currencies, and we realised that just sticking with one provider was both risky from a business continuity perspective, and would end up costing us over the odds as we scaled out.
At the time our payment processing facility consisted of a Windows service, and an MSMQ queue installed on each of our web nodes. Users would make donations in their browser, data would be encrypted and stored in the database, and a message to start processing would be popped onto a queue. And… well, and we’d run some SQL and verify that everything had worked. When we were processing UK transactions, straight through, with no complexity, this was just about sufficient. We had debug logs, which were OK, and we could run a query or two to verify the number of transactions processed over a time window. The implementation was similarly straight-forward; a bunch of threads running .NET2.0 style MSMQ listeners, that would block until a message was received, and then call our payment service provider, persisting the result. Our payment service was simple, but it was also simplistic.
Then we sat down to think about what we’d want from an international payment service. We wanted rules to route payments between more than one payment provider, we wanted semi-automatic retrying and resilience, and we wanted to support new types or payments – pledges, things that required different types of processing. But most importantly, with this increased scope of complexity, we needed the kind of visibility we’d never really had with our invisible services before.
Just as we were starting to wrangle with the fact that we needed to completely re-work lots of our payment infrastructure, a framework called Nancy (or #NancyFx) started making ripples in the open source .NET community. There were a couple of frameworks at the time claiming to be .NET implementations of Sinatra (the popular ruby web framework), and we evaluated both Nancy and a competing framework called Nina. At the time, Nina was “feature complete” (and minimalist in its feature set) and Nancy was still under very active development, but there appeared to be some considerable hustle behind Nancy, with an obvious roadmap, and support, or planned support for popular IoC containers, view engines and other useful web stuff. This middle line between being an ultra-lightweight framework while supporting things that our development teams used and understood was compelling, especially when coupled with Nancy’s permissive hosting model – you could use it in IIS, you could use it hosted in WCF, it could host itself. We spiked up a quick sample app in an afternoon and immediately saw how we could iteratively introduce Nancy into our payment services as part of its re-working to give us some of the visibility we needed.
We started our “gentle” introduction of Nancy (with caution), by using its self hosting assembly, and put it side by side into our existing Windows service implementation. We hooked up Ninject, and started using Nancy to produce a simple status page hosted from inside our service. As the iterations progressed and we re-worked the internals of our payment services, we started making more extensive use of Nancy, maturing it from a read only status page into a fully featured dashboard and configuration portal.
As we extended our new payment processing agent to embed a rules engine, we used the dashboard to message the rules that were in play. As we added multiple payment service providers, we provided UI for our devops guys to enable and disable each payment service provider, and as we made our error handling more robust, we provided an interactive retry queue, right from the payment processing agent itself.
We started to use singleton objects shared by both our payment transacting code, and the Nancy modules, to recorded real-time statistics and surface them in graphs from within the application – giving our devops guys the visibility and confidence they needed when introducing new rules, making changes, and monitoring the performance of payment providers. Payment service providers are notoriously flaky, and the kind of statistics we were able to gather (average request times, specific errors, and interactive graphs on the dashboard home page) on several occasions allowed us to be the first client to respond to any outages, notably, before the payment service providers themselves even knew.
Nancy was perfect at being an enabler, while keeping out of the way of our regular development process. It provided us with low friction infrastructure, support for technology we knew and used from ASP.NET MVC, and played along with our other open source components (ninject, nhibernate, automapper). The introduction was painless due to its myriad of hosting options, and it supported a test-first TDD workflow from the start. It just worked, and it worked well enough that we trusted it with millions of pounds worth of transactions each day.
We went on to use Nancy in several other high profile internal projects on the back of our experience with it in the most sensitive area of our system – it made its way into payment settlement and reconciliation, PCI Level 1 compliancy code, and deployment tools. If you’re building test driven modern web apps, APIs or dashboards, I wouldn’t hesitate in recommending it as a solid technology choice.