Ruby-Based Legacy System Modernization Case Studies
Ruby on Rails has recently given developers reasons to consider migration to other tools, but with some modernization, it is still a valid and preferable tool.
Join the DZone community and get the full member experience.Join For Free
Even with new web technologies appearing, in 2020, Ruby was still loved by 42% of developers. Why? Because it was built with the key thought in mind that it could “make developers’ lives easier”. Development with Ruby was fun, interactive, and fast. Nowadays, it is hard to imagine a development task for which there is no existing Ruby library.
Over the past year, several clients addressed us with the request for updating Ruby-based enterprise products. And yes — modernization is possible; however, having an outdated Ruby product doesn’t mean it should be rebuilt from scratch with different technology. We believe that in most cases it is possible to work with existing legacy code, and we’d like to share our approach to modernizing legacy Ruby on Rails applications. Read on if you endeavor to reanimate your Ruby-based project.
When You Need to Modernize Ruby/Rails App
1. Your Application Needs Updating
Especially if you have any of the following issues:
- Outdated and non-interactive UI, problems with UX
- Web app feels like it’s from the 2000s
- The app makes the full page reload on each user click
- UI has no immediate response to actions
- Some interactions are impossible to implement
Why is this a risk for legacy systems? Improving the user experience (UX) is usually considered a designer task. It is important to know that modern UI/UX tools require both frontend and backend enhancements.
2. Slow Application Performance
- Web pages are loading slowly, so users need to wait a lot
- The application is unresponsive and annoying
Why does it happen? Application architecture and database schema design are complex and time-consuming engineering tasks. In the early stages, serious investment is perceived as high risk because the product may not succeed. Subsequently, code refactoring becomes a challenge at the later stages.
3. Unable to Track Key Performance Indicators (KPI)
- No clear view of data flow in the app
- No vision of how users behave
- Lack of understanding of how to formulate KPI in real-time
What is missing? A data warehouse module! To support your marketing and business team, you need a solid engineering solution to gather data about your business and transform it into human-readable numbers and graphs.
4. Application Crashes During Rush Hour
- The application cannot handle a big amount of users
- Different parts of the system produce a different load, but cannot be scaled separately
What does this mean? The system has a bottleneck, which influences the whole application. Bad news: a short-term solution for fixing the bottleneck is not enough in this case. In the long term, you should consider creating a mechanism to individually boost (scale) your key features.
5. Product Is Really a Set of Multiple Apps
You can clearly see the way to move forward with service-oriented architecture but do not know how to split the existing monolith while keeping business running.
What is wrong? Keeping all the apps in a single codebase is like keeping all animals in the zoo in the same cage. It creates a lot of mess and unexpected bugs, slowing down development.
6. Increased Infrastructure Costs
Your web app needs a lot of cloud resources and expensive servers to operate due to non-optimal implementation.
What to do? Consult with an experienced solution architect and come up with a technical modernization plan for the next phase of the project.
7. Engineers Started to Work Slowly
- Implementation of the new feature takes a lot of time
- The development is slow due to high code coupling
- The deployment is hard and fragile due to huge amounts of bugs, testing, and fixing which requires additional efforts
Is my current team bad? Not necessarily, but it requires professional help to identify what slows them down and come up with a plan for how to implement modernizations.
8. Each Release Brings a Huge Pile of New Bugs
- New features break existing ones
- A lot of regression testing is needed by QAs
- No possibility to make frequent releases
How to stop it? It’s time to perform code modernization.
9. Unable to Find Engineers for the Project’s Tech Stack
- Project parts and services are established on different technologies
- You need to hire multiple engineers for each service or look for a super engineer, who knows everything
How to proceed? It is painful, but sometimes rewriting parts of the app to use the same technology stack might be a good option.
10. Worried About Security
The first version was developed in a hurry with lots of bugs and minimal testing. Now that a lot of users have started using the app, I am worried that I will be sued if there is a data leak.
What to do to sleep well again? Update all libraries, encrypt user data at rest and in transit, and implement end-to-end SSL. Or hire a team, which will perform the audit and carry out the required changes.
11. Mobile App Integration Is Hard to Implement
The project does not have an API, or it requires duplicating a lot of code and bugs.
Why is it so hard? MVP or the first version of the project was developed without taking into account the possibility of a mobile version. It is hard to reuse existing code in the mobile app.
How to Handle Ruby on Rails Legacy App Upgrade
Ruby on Rails (RoR) upholds requirements for startups and MVP projects, due to its strong points, such as a fast time-to-market and a large community of developers.
Using RoR, a team of any level could start a project and develop it at the initial stages. This becomes a problem when your business grows — performance, scalability, security, and tracking come into play. At this stage, you need a more experienced team that is able to investigate, develop, and consult. Upgrades may be necessary either in this situation or when the RoR application becomes obsolete.
The growth of an RoR-based project can be handled in the following way:
- Assign a tech expert
- Perform the full project code and infrastructure analysis
- Identify and prioritize the critical issues to be fixed
- Come up with a timeline and strategy for the project modernization. It could be a gradual evolution together with further features development
- Provide the team to do the modernization
What to Estimate in the Process of Modernization
- How will modernization influence the development of new features? Should all the team be involved in modernization or is it better to have dedicated developers? When is it time to do the code freeze?
- Is the code ready for modernization? The features are covered with tests, documentation is in place, backups and disaster recovery are functioning properly.
- What are the goals of modernization? The team should set understandable and trackable performance indicators of modernization and regularly assess how the process is moving.
- Is the risk of modernization warranted? Modernization is an improvement but everything has its cost. Modernization plans should cover the increased risks of developer error (automation is required), rising complexity (more documentation to onboard new developers needed), etc.
Ruby on Rails Legacy Application Update Case Studies
Here we will review three case studies, where outdated Ruby and Ruby on Rails applications were modernized, and one case where Ruby became a solution for modernization of a legacy software system initially built with another programming language. Three of the four case studies are accompanied by comments from developers involved in the project.
Case Study #1: Refactoring Health Legacy Application
The project was inherited from another development team after five years of development. So, it was a legacy code. It had outdated versions of Ruby and Ruby on Rails, and the code was written using many different architecture approaches mixed in a single codebase. There was no related documentation whatsoever.
- Upgrade Ruby and Ruby on Rails versions
- Define the set of patterns and abstractions to be used during the refactoring stage
- Make sure the new version of code is developed in an extendable way
- Provide a high level of security and personal health information (PHI) protection to comply with HIPAA
- Setup performant and easily scalable infrastructure
- Document the architecture design
- Develop a new set of features and launch in half a year
Upgrade to Ruby 2.7.1 and Rails 6.0.1: The first task performed by the team was to ensure that upgrades wouldn’t break the existing functionality. We developed and refactored 4488 automated RSpec tests, which covered most of the existing application features. This step made it possible to upgrade Ruby on Rails to the newest versions safely. After the upgrade, the RSpec tests showed us all the cases, where new libraries caused problems, and we managed to fix them.
Modular (component) Monolith Rails architecture design: Although the Rails application had a single repository, by its true nature the application consisted of many different services. It was a real challenge to develop new features because of the way the code of all these services was mixed together.
After the brainstorming phase, the team decided to use the Modular Monolith design for Ruby on Rails API. The benefits of this choice were that all services were decoupled into separate Rails engines (admin portal, CMS, Common API, etc.), it was easy to navigate the project and implement new features, and the design had a clear and easy-to-implement set of guidelines.
Building new infrastructure: Microsoft Azure was a great candidate for a new cloud enterprise solution. At that moment, it had a suitable bunch of tools to set up new infrastructure, taking into account security, scalability, durability, performance, and recoverability.
For security reasons, it was decided to implement an “end-to-end SSL” approach, which ensured that at any phase of communication between services the traffic was encrypted and safe. Azure Kubernetes Service, also called AKS, enabled achieving required scalability, durability, and performance for our Ruby on Rails API.
Last but not least, the whole setup was performed using the Helm tool and Azure DevOps CI/CD.
Case Study #2: Upgrading Sports Tournaments Management System
This project required more features to be developed in the same budget, the number of bugs to be reduced, and extensibility to be increased. Additional tasks included simplifying the onboarding of new team members and improving overall security.
- Move the code to service and query objects plus decorators by introducing a domain-driven design (DDD) approach
- Unify the frontend part
- Make onboarding for new team members and developing of new features seamless and fast
It was decided to:
- Move logic from controllers to service objects
- Move query functions from models to query objects
- Move helper functions from models to decorators
- Upgrade Rails version
- Develop all frontend features using React
- Unify code style using Robocop and Rails upgrade
Applying DDD allowed the developers to simplify overly complicated models and controllers of the legacy Ruby on Rails application and make sure it was easy to extend the code for new features.
The Rails app had 2 different ways to develop UI components: pure jQuery with Ajax and EJS with remote links. To unify the frontend part, we employed migrating to React.js as an appropriate UI approach.
Case Study #3: Modernizing Bank Management System
Multi-tenant Ruby on Rails startup development had been running smoothly until the team faced several risks: slow response time under load, increased development time for new features, a single point of failure, and a lack of security mechanisms.
- Provide migrating from AWS to Azure
- Improve legacy codebase
- Find out how to solve other tasks related to risks encountered by the team
After investigation, the following problems were found:
- A lot of load from periodic computations
- At the initial stages, quality was sacrificed for speed
- Hard to extend and maintain codebase
- Slow tests (harder to develop and release)
- Hard dependencies between system components
Migrating From AWS to Azure
The original first version of the system was built using AWS cloud in an outdated manner.
All developers had direct access to servers and databases, which is unsafe because of human errors and the high possibility of troubles that could happen from time to time. Also, servers were targets to crash because of periods of high load. The Ruby on Rails web app generated a lot of jobs and made the compute server (Sidekiq) get “stuck.” At these moments the whole system became unavailable due to the hard coupling of the components.
It was decided to perform a decisive technology modernization and also migrate to Azure.
Additional security mechanisms were implemented around authentication and authorization, applying a single sign-on approach (SSO) and Azure Active Directory (AAD). They were seamlessly integrated into the existing Ruby on Rails solution and Active Admin.
All the public traffic was routed through a gateway and firewall for high protection of sensitive data. Key Vault allowed storing Ruby on Rails credentials and secret keys.
As the core of the system, the AKS (Kubernetes) cluster was set up to provide the required value of performance and stability of the system, eliminating the single point of failure and providing a fault-tolerant experience for users.
Improving Legacy Codebase
After the internal analysis of the codebase, it became clear that to improve time-to-market for new features and reduce the number of bugs, we had to get rid of monolith architecture and the server-side rendering approach.
The split to services and separating the frontend part drastically assisted Ruby on Rails developers to speed up automated tests and increase the extendability of the legacy monolithic Ruby code.
Case Study #4: WordPress-Based Legacy Application Modernized With Ruby on Rails
RoR is still considered the best platform for startup product development, so sometimes it can become a solution for the modernization of a legacy software system built with another programming language.
The project was initially a WordPress-based startup social network for teams interested in the growth of their businesses. Soon an idea of a special growth management tool came into play, and it could be built using the Ruby on Rails framework.
The team quickly realized that the project was a set of multiple services, and it was difficult to keep engineers for different technology stacks (WordPress and RoR). In addition to the startup-based architecture design, it was hard to develop new features and the development slowed down.
- Unify the tech stack
- Isolate multiple apps from each other
- Reduce release risks
It was decided to:
- Rewrite the WordPress part of the project using RoR for unifying the tech stack
- Introduce Microservices architecture to keep multiple apps isolated from each other and make it possible to share the data between them (like users). This increased overall stability and performance
- Introduce CI/CD automation for reducing release risks
Why Ruby on Rails Is Still Our Choice in 2021
In summary, the increasing demand in web systems development raises the question of which tool is best suited for these needs. For us, Ruby on Rails is still a valid and preferable tool for the following reasons:
- It is a “Swiss Army Knife” of web development that allows us to create prototypes, proof of concepts and MVPs quickly, and test the market with a minimum budget.
- Ruby on Rails framework has built-in tools out-of-the-box to fulfill 80% of common web systems feature requests.
- For developers with the right skills, it is still a tool for infinite product growth in terms of features and code.
- Ruby on Rails makes both businesses and engineers happy. It was built with the philosophy “to make developers’ lives easier” in mind, which simplifies delivering new features and meeting product owners’ expectations.
It is undeniable that Ruby on Rails has some drawbacks. One of them is the high price for each mistake. Errors originating from the planning stage or the first development phase are difficult to correct in the future. As a result, this turns into broken interactions between separate parts of the app. To avoid mistakes, you need to find an experienced team that is able to deliver the product with the required customization and functionality, devoting attention to previous research.
Published at DZone with permission of Serge Koba. See the original article here.
Opinions expressed by DZone contributors are their own.