Imagine this scenario: You are working with your team who has been tasked with “stabilizing” a legacy web application that is core to both the operation of the company as well as revenue. Your team is told that it is a priority. When you ask why it is a priority at this time, it is explained to you that there are some defects that are impacting the business’s ability to operate and “workarounds” that once were sufficient enough to maintain the business process, no longer are. The team lead might say, “We can’t add new features because we end up breaking things even though we think we have tested the application end-to-end. Our time to market is abysmal. The business units are getting fed up.”
Sound all too familiar? If so, let’s talk about microservices – what they are and what are various strategies for determining where they might best fit in a stabilizing role.
Microservices are leading the charge in many modernization projects and for good reason. Microservices are small, autonomous services that work together. They allow developers to build in a more practical manner. We often try to employ the Single Responsibility Principle (SRP) when building software. The Single Responsibility Principle essentially says that functions, modules, or classes should have responsibility for a single piece of functionality that a software system needs and that responsibility should be encapsulated by the class. Microservices offer a great way to enforce the SRP when stabilizing a legacy application. Since microservice implementation is encapsulated and the interface between the microservices and legacy application is RESTful, the microservice can be written in any language that supports building RESTful APIs. Each microservice is individually testable and deployable.
All microservices are:
- Small
- Autonomous
- Focused on one responsibility
- Technology Agnostic
- Scalable
- Composable
- Decoupled
- Individually Deployable
Experience has demonstrated that a substantial amount of legacy systems are monolithic in nature and that there is a repeatable, recognizable pattern that happens in many companies. This pattern is seen in all sizes of companies – large and small, startups and Fortune 100. The initial production release left some amount of technical debt that was never addressed, and years of enhancements, bug fixes, band-aids, “code workarounds”, and the like have left a legacy system that is unpredictable, difficult to fix, enhance, or extend without introducing breaking changes. Easy defects are resolved, harder ones see additional code introduced – not to fix the defect, but to simply correct the outcome of the defect after it has happened.
In time it is realized that this method of managing the application is not sustainable. The scenario in the first paragraph occurs. I would suggest that undertaking a modernizing/stabilizing project should be an opportunity to end some pain and bring value (although initially, you will see some people holding their head in their hands, some on the verge of tears. A few might even utter some unrepeatable words). Take heart: building and integrating microservices can be very rewarding if done correctly.
Where does one start with this methodology? First, have a meeting with the folks who use the legacy application and get a list of all the pain points they experience. After getting this list, work on prioritizing the top five to ten items. Take a deep dive into the legacy code and study the code that has the responsibility for implementing the functionality that is, unfortunately, causing the pain.
The goal of this exercise is twofold:
- Try to understand the intent of the code as written and;
- Determine where the integration of the microservice should occur.
Document both items very well and in detail. If the intent is unclear (that might be part of the root problem), it’s time to meet with the end users/product owner to establish, in clear requirements, what needs to happen. Translate your findings to each microservice, building your needed functionality within your microservice. Build unit, integration, and performance tests for each of your microservices. Then, define an integration strategy. On an iterative basis, build, test, and integrate. Follow this pattern until the identified priority pain points have been mitigated by microservice integration. Meet and assign the next set of pain points to be addressed and start the process all over again. Make sure to allow for a longer than normal QA and UAT cycle. After the first successful integrated deployment to production, things will start looking up!
There are other avenues to use when ascertaining where to introduce microservices. As a development team, review the code together, looking for any “code smells” that may indicate areas of functionality that may benefit from refactoring to a microservice. Also, look for any areas of code that calls external web services, SOAP-based or RESTful, and consider implementing a microservice wrapper for that. This will allow any changes the external service makes to be handled by a microservice and allow great testing as well as individual deployments.
Once you have a few production releases out of the way, consider a broader strategy of replacing legacy functionality with microservices. Why? So that all the business users who need new features, extended functionality can enjoy the benefits that microservices offer. Additionally, if you aren’t using a container to host the microservices you are building and deploying, please do so.
Using these tips, you’ll be on your way to successfully pivoting your projects and enterprise to a microservices mentality and enjoy the many benefits that come with this pattern of development.