Minimising Technical Debt

Technical Debt (1)

At Moneythor, we are committed to delivering a technical solution that can be implemented with components that are easy to maintain, cost efficient and fully aligned with industry standards in order to ensure longevity and scalability of our software.

The software industry has always been and will continue to be susceptible to rapid developments with new architecture being implemented and tools deprecated quickly, but also highly competitive as new initiatives are developed to establish key differentiators for solutions. This is even more true today, with cloud providers investing greatly in software, hardware and infrastructure.

In this constantly changing environment, let’s review what we take into account when we carefully make our technical architecture choices and what do we pay attention to when building the Moneythor solution.

What is technical debt?

There are several interpretations of technical debt, but the most commonly accepted definition is any decision taken with the purpose of achieving rapid gains despite knowing it implies refactoring or revisiting as a later stage. In our case, this could for example be to implement a perfectly working piece of code not aligned with the long-term strategy of the product, or edge cases voluntary left on the side in the absence of functional requirements needing them at that time.

Why is this important to us?

Like the vast majority of enterprise software solutions, the Moneythor platform is built on top of a fair amount of Open Source libraries. Open Source software has historically been developed by individuals or funded by industry leaders. Today, large organisations are increasingly contributing to the development of many Open Source software projects to help fulfil their own needs. Think about the now inevitable Apache Kafka (from LinkedIn), Kubernetes (from Google), React (from Facebook/Meta); the list grows longer every day.

Given the complexity of the Moneythor solution, it would be unimaginable, extremely time consuming and expensive to build every aspect of the solution ourselves, not mentioning the risk of reinventing the wheel without ever reaching the same level of quality of these third-party projects sponsored by renowned organisations and intensively tested in multiple production environments.

Having said that, security is a huge concern. No one likes to hear how a service has suffered a data breach, but it is almost impossible to avoid vulnerabilities like the ones regularly reported on Common Vulnerabilities and Exposures and often requiring an immediate action to remedy them.

As such, we carefully test and select all third-party libraries, always considering alternatives and how difficult it would be to understand their source code and fix any security issues ourselves. Having done that, we integrate it in our solution with the necessary layers to ensure it can be easily upgraded or even replaced.

Be aware of your technical debt

Rewriting source code or switching from one technology to another often incurs a high cost, and it is a difficult thing to justify with stakeholders. For that reason, we take technical debt into account from inception. The way we design our solution plays an important role here and sometimes, we do not hesitate to spend an initial extra effort to prepare the solution for an additional piece of unconfirmed functionality.

This is also the opportunity for us to record from the start what will be considered final implementations of new features and items where we had to take some shortcuts to produce a working solution on time, still knowing some parts could be revisited later.

It’s worth noting that functional requirements evolve and we have to revisit the existing code. In that case, we are not addressing a technical debt item. It’s simply the natural lifecycle of a constantly enhanced solution.

Keep technical debt under control

Having said that, it is impossible to guess the future and risky to bet on an upcoming solution or buzzword. For example, the Moneythor solution leverages the foundations of the Java Enterprise application suite, a very mature and respectable platform that has supported the development of millions of proven enterprise software projects in the last decades. One of the Java platform’s features is the venerable blocking Servlet API, a proven model different from the asynchronous non-blocking stream processing approach promoted by the newer reactive programming initiative.

If the latter becomes the default approach in the future once the community and many dependencies adopt this new programming paradigm, it could create a technical debt where there would be a need to invest time to bring a solution to a new standard with the same level of functionality as before.

In this case, the role of the engineering team is once again to design the solution in such a way that we can minimise the effort to rewrite and adapt the existing features without having to spend time on adapting the existing business logic.

Data storage systems easily create technical debt

As is well known, the world is producing and consuming more and more data each year at an exponential rate. Processing and analysing a huge amount of data has always been a main challenge for organisations across sectors including the financial services industry. It is no surprise that the software industry is constantly working on and building new tools aiming to solve these ever evolving data challenges.

While relational databases were the default in the past and their cloud managed versions are very scalable, there are now completely different approaches on how to persist and query data. Switching from a traditional relational database to something like a document store is not as straightforward as switching between two different relational database vendors. In many cases, the existing solution relies on principles and contracts that do not exist anymore in the new data store.

Technical debt has consequences

Our experience in this industry over the last decades and multiple heterogeneous projects show that there is a threshold above which technical debt becomes a black hole and this threshold gets lower and lower over time, especially when no action is taken.

What it means is this: when the limit is met, it might be impossible to catch up with your technical debt, and ultimately it will cost less to rewrite everything from scratch, which can be a significant and demoralising effort. When that happens, all the experience and knowledge you acquire with one technology has to be rebuilt.

For example, switching from the popular PostgreSQL relational database to the also popular Mongo DB document store is not only about making your software compatible with the two persistent layers. It is also about understanding the implications it has on the flow of data, the transactional process, the pros and cons of both solutions, what you get and what you lose. This can easily be overlooked leading to delays and extra costs, failures in some cases.

Our approach to manage technical debt

As mentioned previously, it would be impossible to predict developments, nor can we reasonably implement everything all at once. What we can do is to try to protect ourselves against unnecessary hurdles. Well-known and proven architecture and design patterns exist for this exact purpose.

Firstly, the solution is built on top of the Spring Framework. The most important point to note here is that we chose Spring not only because it is extremely popular, well maintained and provides a rich set of sub-projects to help address many aspects of enterprise-grade software solutions, but really because at its core, it’s a dependency injection framework that allows us to decouple our code and greatly eases the implementation of the separation of concerns principle.

As much as possible, we decouple everything from third-party libraries to our own code so that implementation is easily replaceable. Next, once we decide on implementing a new feature, we begin by decoupling the concrete implementation of the business logic with the framework. We also take extra time to design a generic intermediary layer that sits between the technical framework powered by Spring or third-party libraries and the implementation of the business requirements. The purpose of this intermediary layer is to provide an abstraction of what the feature is about so that we can later easily customise it and protect it from the impact of upgrading or replacing underlying technical components.

The result is that the Moneythor solution becomes itself a framework with a concrete implementation of the functional logic, and not only that implementation. This provides us a great level of flexibility, which is extremely valuable in our case. It also gives us the ability to maintain the same product for all clients. Of course, we adapt our solution to different regions, target environments and client-specific features but this is achieved thanks to only one version of the source code with zero fork.

Today, we can measure the benefits of this approach. We started developing this application suite nearly 10 years ago, and yet we do not carry over any vulnerable libraries. Instead, we use the latest version of Spring and other third-party components enabling us to add support for new technologies within budget, and without the need to refactor large part of the software. Last but not least, this keeps our development team motivated to work on a modern architecture with recent technologies and tools. 

Essentially, it is about finding the right balance between complex design & engineering and acceptable cost of rework, and with the Moneythor solution we have been able to strike that balance and shall continue to do so.

digital banking newsletter