Moving from monolith to microservices
Many large companies have little appetite for risk and innovation and as a result are hamstrung by their old, monolithic systems which slow down their ability to innovate from a software perspective. This is one of the key reasons why we are seeing so much innovation on the software development front from small start-up companies who don’t have any of these legacy systems and have the flexibility of utilizing cloud infrastructure to get their modern software architectures up quickly. What makes Allan Gray so unique is that we have been able to break the trend of bigger financial services companies and migrate ourselves away from a monolithic system into a modern microservices architecture.
This journey to a fast and highly flexible software architecture has not been without its challenges though as moving away from a legacy system is not just a matter of making the time to do the rework, but also about answering a couple of key architectural questions along the way. Hopefully our journey to answering these key questions can also serve as an inspiration for others to take the plunge:
• How do we actually write a micro-service frontend, while maintaining the fluidity of a SPA?
While legacy systems are clunky, their scale at least lends itself to a fluid UI experience. Having multiple teams working on different systems independently of each other puts this at risk. We have worked hard on blending our design philosophies and architectural footprint across teams to ensure that a unified vision is not lost in our independence.
• The monolithic database - what do we do with it?
Changing a system is one thing, moving yourself off of a massive database with many years and millions of customer’s worth of information, something else entirely and in truth, not something which is entirely feasible. To solve this we have instead created a series of smaller databases within our microservices with stored procedures which allows to sync with our bigger DB ensuring the data is always in sync
• Trunk-based development - do we have to? (yes) Ok, but how? And what about versioning?
The more different code is out there, the more important it is to control your source code effectively. With the need to deploy to production rapidly, trunk based development was non-negotiable, but we needed a versioning platform which allows us to do this at scale across many teams and code bases. This flexibility has led us towards Gitlab for our source control and tools like New Relic which allow us to monitor each API, its stability and the versions that are currently in our different environments.
• How do we test microservices against each other?
Perhaps the biggest challenge of microservices development is not the way it’s built, but the way it is tested. Separating the development into smaller sections makes sense from a development perspective, but not from a testing perspective. While no solution is perfect, we have figured out an approach that allows us to scale fast while ensure that our software meets our high quality standards.
• What if someone makes a breaking change and doesn’t tell me?
Inevitably, with teams working independently and speaking to each other through their different APIs, the time will come when a team needs to make changes that will break other teams’ functionality. While there is no easy solution around this, we have taken the approach of versioning our APIs to ensure that new changes do not compromise existing functionality and allows teams affected by breaking changes the opportunity to work on updating their software with less risk.
Teams have also needed to build in their run-time dependencies into the code base, so that their software will only deploy if the relevant versioning is in place. This further prevents unsupported code from making its way into production and forces teams to deal with their dependencies.
• How do we persuade developers to stop sharing code and creating hard build-time dependencies?
While code sharing and object orientation are foundations of good software development, if you have multiple microservices running together, it becomes messy to share everything. We have taken the approach where each team needs to design their systems in a manner that are independent of each other and while there are always inevitable dependencies on other systems, through our test process, we can mock these out and ensure they still work in sync with each other.
• How do we deploy these things consistently?
Moving from one big deployment cycle to each team being able to deploy multiple times a week is a big change for any organization. Change control mechanisms and process just don’t scale to the level that is needed. We needed a system that can manage the deployments and allow us to automate certain mechanisms and build processes so that these things can happen automatically and at scale. We have achieved this through our Docker and Kubernetes platform which allows every one of our teams to deploy into production as often as they need without risking the quality of the software.
• How do we know when something isn’t working properly, and what do we do about it?
Monitoring plays a big part to managing a big ecosystem. We have had to build effective monitoring systems that allow us to easily detect when different services and APIs are not working as expected, so that we can quickly respond and address the issues at hand. Thanks to our rigorous testing procedures though, this is unlikely to happen in production and more a measure of allowing us to catch this in our internal environments before we deploy into production.
Answering these questions has enabled us to move towards the path of continuous delivery and allow each of our different teams to delivery fast and reliable code into production as quickly as possible, putting us in the position where we can not only innovate faster, but also better meet the needs of our clients by delivering functionality that benefits them more timeously.