The goal of this article series is to share our experience of building a new system for processing small-scale transactions within the Y Soft product suite. This article will discuss the main system requirements and high-level architecture decisions we made. Following articles will describe how we used the chosen technologies and the challenges we faced.
Back in 2011, YSoft SafeQ (the main Y Soft product dealing with print management) contained a component that limited users’ consumption based on their account balance and printing costs. However, it was tightly coupled with YSoft SafeQ and its maintenance; further development and customizations were too expensive to continue in this manner. So in 2012, we made the decision to create a separate system for payment transaction processing. At that time, Y Soft opened a new development branch in Prague and hired a new team to build this green field project.
The main requirements for Payment System were the following:
- Compatibility – it had to be able to do what the old solution did, obviously 🙂
- Strong data consistency (among all cluster nodes) – we would be dealing with money and many of our customers would not like it if their users continued printing for free once they hit 0 on their account or if any money transactions had been lost.
- Deployment in the customer environment – we do not host our products ourselves rather our products run in the customer’s environment. This means that the environments vary from big data centers with plenty of resources (think big banks and universities) to every day workstations (small businesses).
- Extensibility – some of our customers have their own payment processing systems that manage monetary accounts. There are also plenty of different payment gateways in various countries for money deposits. We had to be able to integrate Payment System with all of them at a minimal cost (ideally offloading the cost to a 3rd party).
- YSoft Payment Machines – Y Soft also develops its own hardware for depositing money – the YSoft Payment Machine so we had to support those as well.
We also imposed some requirements of our own:
- Fast development – the project must be simple to build and simple to run with as few steps as possible to start developing. New developers should not have to spend several days in order to build and run the solution. Nobody should be made to memorize 30-step manuals to build or run anything!
- Simple design – everybody hates to write (and read) documentation, therefore, we wanted to design the system according to KISS principles so that we could concentrate on programming and not on explaining complex design to our co-workers.
- Flexible UI – we have seen our share of Java web frameworks and we cannot say it has ever been a pleasant experience. Especially if something does not work as it is supposed to, or if you need just some little extra that the framework does not provide out of the box. Therefore, we wanted the UI to be simple but flexible.
How We Addressed the Requirements
One of the challenges was to find out what the old solution really did since there was not much documentation (hardly any at all :-)). We gathered requirements from various sources, mainly company board members and QA analysts (since we did not have product management at that time) and prepared a sizable business analysis. That way we were quite confident that we understood the requirements and that all of the participants had the same notion of the system to be delivered.
The main requirements boiled down to a system that manages monetary accounts and provides the means to create 2-step and multi-step transactions on those accounts (which map to the print and copy processes of YSoft SafeQ). The system should also provide means to manipulate account balances by a cash desk operator.
We wanted to create a more general purpose payment transaction processing system so we analyzed existing systems like PayPal, Google Wallet and Amazon Payments, to avoid reinventing the wheel, regarding naming and the functionality of different operations. The result is a system that is being deployed in production and tested as standalone without YSofr SafeQ, sometimes in surprising setups, e.g., machines for selling candy in Switzerland :-).
Since we needed to keep the data consistent, we chose a straightforward solution – one shared database and multiple stateless application servers. There were also other options, like a fully distributed solution or a single application. But we wanted failover, so a single application was not an option. On the other hand, a fully distributed solution would be a lot more complex and would have many necessary limitations during network partition (there are plans for a different system that will be fully distributed, but for different use cases, and we will achieve consistency in a different way).
NoSQL databases are currently very popular, but they are mostly targeted at cloud provider infrastructure with reliable networks. Environments where partitions happen on a daily basis (e.g., nodes are online only when the owner of the machine is at work) are not ideal for them.
Therefore, we chose a traditional SQL database as the database solution. YSoft SafeQ supports PostgreSQL and MSSQL, so that was the minimal set of supported DBs for us as well. We delegate database failover to the chosen database installation.
Support Multiple Database Vendors
We need to support multiple databases and of course we do not want to duplicate code for each vendor. Besides PostgreSQL and MSSQL we use H2 for development since it does not need complex installation, it can be easily bundled with the application or used in tests and it is very flexible (in memory, persistent and server mode).
We chose tools that grant us database portability. For database structure and minimal data set insertion, we use Liquibase (more details will follow in a separate article). For querying, we use Hibernate and we have successfully avoided the need for stored procedures so far (yes, there are times when they are handy, but they come at a cost, especially when you need to support multiple DB vendors).
Deployment in the Customer Environment
The sizes and needs of our customers vary dramatically, so we provide several different deployment options. They range from a single application server with a schema on existing database installation to a clustered solution with multiple application servers behind a load balancer.
We achieved this by implementing the main transaction processing logic as stateless. So you can start a transaction using one node and finish it using another.
Many customers have security policies that do not allow us very good access to the installed solution and descriptive logs are a must. We have cooperated with our colleagues from the Customer Support Services department (CSS), who act as the first line of support, on reviews of log messages. The results are simple and clear log messages (usually one descriptive message per business operation), so CSS consultants can deal with many issues without our assistance.
There are four kinds of extension points in our system:
- REST API – We chose REST mainly because it is well supported in most languages and platforms and because it is simpler to use than alternatives (like traditional web services). This way, anyone can easily integrate with us. You can even create Payment System transactions from the command line if you like. This API comes in 2 flavors:
- Merchant API, which is used by merchants to create payment transactions.
- Administration API, which is used for account and system management.
- Java API for external payment processing systems – this is an extension point where either developers or CSS consultants write adapters for existing customer-specific payment processing systems. In this case, Payment System acts as a proxy for SafeQ, so SafeQ does not have to deal with the specifics of customer specific systems. Implementations have to be in Java (or another JVM language), but of course we count on providing Java adapters for payment processing systems that provide, for example, REST interfaces.
- REST “API” for payment gateways – we have defined a REST contract that has to be implemented by an integration application. This decoupling made the internal Payment System’s design much simpler, since we do not have to care about all of the different workflows that different payment gateways use. The workflow is separated in the integration application, which is HTTP based, since most of the payment gateways we support have either HTTP POST or REST based APIs. Multiple payment gateway integrations can be configured at the same time.
- User management API – this is an API for retrieving information about users. Currently, we rely on SafeQ as the provider of user-related information in our deployments. This is, however, impractical during development, so we have isolated this functionality in an API and have a simple DB-backed development version.
YSoft Payment Machines
YSoft Payment Machines (our own hardware for money deposits) have limited hardware – e.g., 64MB of memory for the operating system and device firmware. This was a big limitation for choosing a messaging solution, so a simple and straightforward solution like (RESTful) web services was out of reach.
We have ended up with Google Protocol Buffers on top of TCP (we use Netty on the server side). Google Protocol Buffers have a C implementation – nanopb, as well as a Java implementation and its memory footprint in C was low enough that we could use them on our devices (we could not even use a C++ version because there was not enough spare memory for C++ runtime).
We use Maven as our build system. We were happy using Maven on previous projects and it is fully sufficient for our needs on this project as well. The main benefit of using Maven is that developers familiar with it can build and modify the build configuration of our project easily. They do not have to learn the build specifics when switching from one project to another, which reduces the risk of making an error.
Our deployables are war files bundled with the default configuration so you can run the system just by dropping them in a web container, and you can then access Payment System right away. The defaults, which make use of the H2 database, can be changed along with other configuration options in a separate configuration file, which contains overrides.
We also provide a pre-configured version of Tomcat that we use as our production web container. We also have a Maven Cargo plugin configured so that you can start Payment System without a container installed (more on that in a separate article).
The design is the usual View, Controller, Service and DAO. Everybody is familiar with it and there are no surprises there. The main point here is consistency so developers do not have to think too hard about where to put the code, and they can concentrate on solving business problems. The knowledge transfer to new co-workers about the database schema, Liquibase usage, business logic and REST APIs took less than 1 hour.
To sum it all up – by using battle-tested technologies (Hibernate, Spring, Maven, Tomcat, Liquibase, Netty) cleverly, having a simple straight-forward design and data model, we have achieved our goals and satisfied all of the requirements. We have a working and extensible system, proven in several installations, and more installations will come soon. But, of course, everything was not always easy and simple, so stay tuned…