The Software Architectural Evolution of Quickteller (Part 1)
Here’s what comes to the minds of users when they think of Quickteller; seamless payment of bills, services and transfer of funds. In simpler words, a one-stop platform for digital transactions, ranging from funds transfers to airtime top-ups to bills payment letting you perform all your transactions at your own convenience.
In this article and it’s upcoming parts, we’ll help you take a peek behind the scenes of the gloriousness that is Quickteller. Let us take you through the software architecture and design journey of Quickteller, including the very reason behind our building and continually re-iterating one of Africa’s largest online payment platforms. We’ll look at some of the technologies and design principles that has helped us process millions of transactions daily for billers and service providers across Africa.
To sum it up, we’ll take you on a journey of how we evolved from this:
To this:
Quickteller started out as a Business to Business (B2B) platform called PAYDirect rendering services to billers and service providers registered on the platform. Banks in turn rendered these services to their customers, allowing them to pay bills at the various bank branches, saving time and human resource.
Quickteller soon found its way into ATMs and merchants’ POS terminals, facilitating Business to Business to Consumer (B2B2C) services like funds transfer, airtime top-up and bills payment. Overtime, it evolved into offering these services directly to consumers as a Business to Consumer (B2C) platform.
With over 3,000 active billers currently registered on the platform, Quickteller facilitates 4 major types of transactions:
- Funds transfers
- Bill payments
- Airtime top up
- Remittances
To allow for room to scale, we made some resolutions when starting out on some architectural principles guarding our software architectural designs to enable effective build, allow room for simplicity and promote great user experience.
Architectural design principles
These principles as listed below (alongside the reasons that necessitated each one) have continually helped to achieve scale and improve the iterations on the Quickteller platform over the years. They include:
1. Service-oriented architecture (SOA)
At inception, the very first version of QuickTeller, originally called BillsOnline was built using Microsoft’s ASP.NET Forms with most transactions focused on bills payment. In a bid to connect to more service providers like cable networks, Telcos and DISCOs while also wanting the same services to be available on multiple channels like the ATM, POS, Mobile and eventually USSD, Interactive Voice Response (IVR), agent networks and 3rd party reseller platforms, we knew we had to do things differently. In 2009, we embraced SOA and rebuilt the BillsOnline platform using Microsoft Windows Communication Foundation (WCF) as the service layer where all the business logics were concentrated.
Integrating with Quickteller at this period was easy and also hard, here’s why:
- It was easy because all one had to do was prepare an XML and send over HTTP in a SOAP message. That architecture allowed us package Quickteller as an API which processed and accounted for over 35 million transactions monthly from both our products and 3rd party re-sellers!
- It was hard because there were several tools in several programming languages that had poor support for SOAP web services and even though WSDL (Web Service Description Language) was supposed to be a standard, we found out some proxy generation tools still fell short. This made it a bit difficult and slow supporting and on-boarding new customers with their programming language of choice.
SOA was not common at the period due to the popularity of ASP.Net Forms and Java server pages (JSP), where frontends were tightly coupled with their backends. We chose to decouple Quickteller to allow partners work with us easily, doing this meant making sure all channels interact with a central web service. Exposing this central service allowed us launch Quickteller on banks ATMs seamlessly in 2010 without breaking a sweat.
Our SOA continued to serve us fine but we wanted more, we wanted to be able to improve our service offering without breaking the SOAP contracts already implemented by our channels. To do this, we took a leap into the future of SOA by implementing a simple XML string contract over WCF SOAP service. This simple idea is what’s now popularly known as the REST web services.
In due time, Microsoft introduced its REST framework tagged “WebAPI” to which we refactored and migrated our codebase. At that period, it was quite easy scaling horizontally across our multiple services and handling more volume as a result of the liberty we had taken to start managing our sessions centrally in Redis.
Fun fact: The Quickteller’s REST over SOAP API still exists!
2. No direct database connections from channels
As mentioned earlier, Quickteller was designed with multiple channels in mind, starting out with channels as the ATM, POS, Web, mobile and more recently, IoT, messaging frameworks, bots etc. As a result of the initial architectural requirement, another design principle we followed from the onset was to never create a direct connection to database by any channel.
It made sense to completely encapsulate all our database queries and business logic from all channels into a service layer to avoid issues of service availability on one channel and unavailability on another channel. As simple as this may sound, it took serious discipline to help us maintain this rule as we had a service layer that was sitting on a single database to which we soon realized that we had to do something about very quickly;
3. Moving from object relational mapping (ORM) tools to stored procedures
We realized we were pulling a lot of data as a result of performing millions of transactions daily and writing Language Integrated Queries (LINQ) without paying attention to the SQL code generated, soon became a massive issue. This led us to re-engineer our queries using stored procedures that we could easily tweak and improve on.
To make coding fun (or perhaps less painful) with stored procedures, we created code generators and wrote packages that could generate code stubs “deals” for us. This helped us in improving our code performance and helped to meet the reporting needs of service providers and billers requiring transaction reports.
4. Building Reusable code Libraries
Another design principle we followed was to keep all Quickteller’s business logics in reusable libraries. With this, it was easy for Quickteller to adopt and experiment with new web frameworks in the .NET ecosystem with little or no hassle within a short period of time.
This helped us to constantly improve our codebase while maintaining backward compatibility for services running on older frameworks. As Microsoft improved their web technologies over the years, we also evolved flexibly from building with ASMX -> WCF -> ASP.NET Web API-> ASP.NET Core with each framework interface pointing back to the same libraries.
Doing this meant we could expose an API via SOAP, and later have it automatically pulled up in the REST API with relative ease, this allowed us to create several gateways through the controllers into the libraries built.
In 2016 we began re-exposing our code base using the ASP.Net Core (now running on Linux) while also breaking our monolith service into microservices, all to enable us scale vertically for some library components in need of more resources than others, while each microservice was connected by separate handlers that pulled out necessary information.
Note: Handlers help group our services and billers with common needs together to help us avoid re-writing code for each.
5. Introduction of Enterprise Service Bus (ESB)
To accommodate the growing number of service providers and billers on Quickteller with each service provider having different integration requirements, we introduced the ESB into the mix as an API gateway manager between Quickteller and the service providers.
The ESB supports two integration patterns:
- Generic: This accommodates service providers and billers with no special integration and fully get to comply with Quickteller’s API integration
- Service provider specific: This accommodated billers and service providers with unique modes of integration. In order to bridge these specific requirements, we use the ESB to create a bundle that adapts Quickteller generic to service provider’s specifics.
Introducing the ESB has helped (and still helps) keep the codebase insanely simple while making integration for any service provider flexible enough.
This part of the article only gives an overview of our software architectural design and structure but there is a whole world of more exciting things to find when explored in-depth. This gives you a reason to anticipate the subsequent and concluding part of this article which focuses on our payment processes and technologies, design iterations and implementations currently happening on the Quickteller platform, and our future engineering plans for this platform.
We know!! We are as excited about this as you are.