Migrating your legacy code, monolith, or other existing app to a microservices architecture is a choice that’s highly dependent on the details and context of your organization's needs. With this guide, we hope to give you the background and tools necessary to decide for yourself if it’s the right move.
Let’s define the common terms before we dive in.
What is legacy code?
In software development, legacy code is usually source code that is inherited from another developer, team, or an older version of the software that’s no longer supported. Legacy applications may be written in a different language than the current version and they might not have any tests to help engineers understand what it does and what’s safe to change:
“Legacy Code is the code you need to change and you struggle to understand.”— Nicolas Carlo, What is Legacy Code? Is it code without tests?
What is a monolithic application?
A monolithic app is composed of one centralized system containing all the code, business logic, and the datastore for the application. Typically, the monolith comprises layers dedicated to data access, logic, and the user interface. It’s common for there to be one codebase or code repository, but in some monolithic applications these may be split into multiple repositories. Services performing business functions within a monolith are referred to as "tightly coupled."
Tightly coupled vs loosely coupled: When software services are tightly coupled, their classes and objects are dependent on one another. In a loosely coupled design, single responsibility and separation of concerns are prioritized so that services do not share classes and objects. Loosely coupled services are easier to develop and scale independently.
What are the benefits of monolithic applications?
- Simpler implementation: It’s easier to start building a product using a monolithic architecture.
- Better visibility → faster debugging: With a monolith, you know what you’re dealing with. There might be a lot of code but it’s all in one place and dependencies are known.
- Speed: Monoliths are big, but that doesn’t mean they’re slow. With all the code, data, and business logic in one place, services can communicate quickly.
Testing is simpler: You can run tests on all your code at once, in the same place.
What are the disadvantages of monolithic applications?
- Scaling is hard: Due to tight coupling, you can’t isolate services to scale up or down as needed.
- Complexity: Tightly coupled code makes it difficult to change or refactor without affecting other services.
- Low fault tolerance: An issue affecting one service within a monolithic application can’t be isolated, so the entire application may be brought down.
Summary: Monoliths pros and cons
Pros | Cons |
---|---|
Fast, as all the data is in one place | Code complexity is higher |
May be easier to debug | Low fault tolerance |
Traditional testing methods work best on monoliths | Harder to scale |
| Devs less empowered to make changes |
Monoliths and legacy code
Because monolithic code architecture was the default for many years and microservices are a newer form of application architecture, legacy code is often monolithic and the terms are sometimes used interchangeably. Legacy code isn’t synonymous with monolithic architecture though: not all legacy code is monolithic and not all monoliths are legacy code.
Can microservices be used for legacy software modernization? Absolutely, but it’s not the only way to modernize legacy code. We’ll explore microservices for modernization of legacy systems and the alternatives below.
What are microservices?
In a microservice architecture, the product is split into multiple applications hosted on different servers instead of all code, business logic, and data living in the same system. In the distributed system, each microservice application serves a different business need and hosts all of its own data for functionality. These multiple smaller services that make up the overall application communicate with one another via APIs.
Application programming interfaces (APIs): A type of software interface that enables two or more computer programs or services to communicate with one another.
What are the benefits of microservices?
- Scalability: Microservices enable horizontal scalability, as each new service can be scaled independently as needed.
- Autonomy: Having a microservices architecture empowers teams to own their microservice, build them with the languages and frameworks that suit them, and change them without worrying about knock-on effects.
- Fault tolerance: Since issues affecting one microservice can be isolated, the application is more resilient.
- Quicker onboarding: New developers can ramp up faster on a microservice vs the whole codebase.
- Move faster: Because you don’t have to update the entire application at once, you can make changes to a single microservice to push out new features and functionality more quickly.
Vertical vs. horizontal scaling: If demand for your application surges, you usually have two options: upgrade your servers for more capacity (vertical scaling, or “scaling up”) or add additional servers to supplement your existing ones (horizontal scaling, or “scaling out”). Horizontal scaling is more cost effective, particularly if you make use of automated scaling, since you can spin up and spin down your additional servers to meet demand.
What are the disadvantages of microservices?
- Complexity: With microservices, each separate service may be simpler on its own, but the services still need to work together and communicate with one another. Each microservice needs to account for cross-cutting concerns and compatibility, and include code accordingly. You are also looking at multiple deployments of separate microservices in parallel. In a sense, your architecture will determine whether you have to manage the complexity up front (as in a monolith) or later—you can’t avoid it altogether.
- Debugging is harder: Developers may run into an issue caused by a downstream service they depend on but don’t necessarily understand, and they may not even have access to the logs needed to identify the problem.
- Testing is harder: Each new microservice creates API endpoints to test. The traditional, monolithic approach to testing doesn’t translate to distributed architecture, as services may not all be available in the right state to test at the same time.
Latency: Requests can take longer to fulfill due to the latency created by API chaining between services.
Monzo’s network of microservices. Each service is represented by a dot and the lines are the rules that allow communication between the services.
Summary: Microservices pros and cons
Pros | Cons |
---|---|
Code is easier and simpler to understand | Higher latency due to API gateways |
Higher fault tolerance | Harder to debug |
Quicker onboarding | Slower testing |
Greater scalability | Hidden dependencies |
They enable cloud-native development | Each microservice must still include code for cross-cutting concerns |
Are microservices worth it?
Microservices are going through similar growing pains to DevOps, in that the initial wave of enthusiasm has passed and the tide of discourse has turned against them. Just look at the titles of some of the most popular discussions on Hacker News about microservices:
- “Goodbye Microservices: From 100s of problem children to 1 superstar”
- “Don't start with microservices – monoliths are your friend”
- “Enough with the microservices”
- “Microservices: Why Are We Doing This?”
- “I've been merging microservices back into the monolith”
- “The End of Microservices”
- You get the idea.
In keeping with DevOps’ journey, microservices has passed Gartner’s Peak of Inflated Expectations and as of 2022 are making the climb towards the Scope of Enlightenment.
As with most debates in software engineering, the answer is neither to go all in on the new shiny technology just for the sake of it nor write it off as a meaningless buzzword.
How to evaluate whether you need microservices or monolithic architecture
The first question you should be asking about migrating to microservices is not how to do it, but if you should.
If you’re considering migrating your legacy code or monolithic app to microservices, one Redditor offers some useful criteria for consideration:
“My rule of thumb when deciding whether it should be microservice is:
- Will it be created or maintained by another team?
- Will it be a different programming language than the one you've created stuff with so far?
- Will it need to stay up if other functionality in your system goes down?
- Will it need to be scaled independently of other parts of your system?
If you answered yes to any of these questions, go ahead and make it a microservice. If you answered no to all of them, don't.— mattwelke, Reddit
Let’s explore these criteria.
Will your service be created or maintained by another team?
If autonomy and empowerment are your goals, microservices can be a great way to enable your teams to own their projects and product areas without limiting their ability to make changes and improvements.
Will your service be in a different programming language?
Isolating your new service as a microservice allows you to build with the tools and technology that make the most sense for the team and the desired functionality.
Will your service need to stay up if other functionality in your system goes down?
Put another way, how important is uptime to you?
“Splitting applications into individual, independent deployable processes aids us in upping the overall robustness of our applications. While it may feel like you're exposing more of your architecture by adopting a loosely-coupled design, opening up your application to increased risks, you're conversely shoring up its ability to handle service outages and network partitions.” — Jessica Cregg, Microservices: How to know if you’re ready
Will your service need to be scaled independently of other parts of your system?
Microservices make horizontal scaling easier, since you can isolate the service experiencing more demand and add extra capacity to it, instead of scaling up your whole application.
Modules vs. microservices: Some argue that you can achieve many of the benefits of microservices with well thought-out modules instead. Your modules can be worked on independently by different teams, but will ultimately be deployed as a single unit. While this approach doesn’t allow you to build modules on different tech stacks, it does enable a microservices effect without all the associated complexity. Some programming languages, like Java, have native support for modules included.
How do you convert monolithic systems to microservices?
There are a number of migration strategies you can employ when looking to decompose your monolith. Below are two of the most common approaches:
Migration strategy #1: The strangler pattern
Design strategies such as the strangler pattern allow for gradual decomposition of your monolith into microservices, without touching the original code in the monolith until you’ve stood up a successful microservice performing the same function.
Stage 1: Create a duplicate service as a microservice.
Stage 2: Validate that the new microservice works without interrupting service.
Stage 3: Decommission the monolith-based service and switch all traffic over to the microservice.
Stage 4: Repeat until your monolith is decomposed entirely into microservices.
The name is inspired by the Australian strangler fig plant, which seeds itself at the top of a tree, working its way down to the roots and eventually strangling and killing the host tree. The concept may remind you of blue green deployment, which works by creating a new copy of your production environment to test before retiring the original and switching over completely to the new environment.
Migration strategy #2: Domain-driven design
Domain-driven design is used to build software that is loosely coupled and focused more on the business needs that the software fulfills rather than on the technology itself. This strategy can be applied to decomposing an existing application into microservices, by selecting functionality within your monolithic application to isolate and rebuild as a microservice until the entire app is made up of separate services.
Stage 1: Stop adding new functionality to your monolith.
Stage 2: Separate the front end from the back end of your monolith.
Stage 3: Identify and spin out monolith-based services into microservices (shrink your monolith).
Stage 4: Retire the monolith when all services have been converted.
Along with these high-level stages, there are some underlying principles to domain-driven design.
Learn more:
How to get the most out of microservices architecture
We're glad you asked...
Implement continuous integration and continuous delivery
Continuous integration and continuous delivery processes (CI/CD) are critical to automation of your software delivery pipeline, and help you launch your new microservices seamlessly. Continuous integration enables you to integrate and test new code automatically, while continuous delivery processes ensure that the new code is always in a state that’s ready for deployment.
These practices speed up your microservices migration without disrupting other services, since you can catch bugs and breaking changes before merging and deployment.
Learn more:
Should you build microservices from the outset?
“A hill I'm thinking lately I'm willing to die on is that a startup where it's just a few people working on something should just make a monolith.” — mattwelke, Reddit
We know that the migration process can be painful. It’s natural to think it would be easier to start with microservices so you never have to refactor. If you are in the position to think about the software architecture of your application (i.e. you’re planning to build rather than working with a legacy, monolithic, or existing application) it’s likely that your product will undergo a number of iterations before you reach the scale at which microservices become beneficial. How do you even go about planning your microservices and the interactions between them when you don’t even know what all your product features will be?
“A microservices based architecture is something you might grow into over time as you come across real problems working with your code base.” — Nick Janetakis, Microservices Are Something You Grow into, Not Begin With
It’s very difficult to forecast exactly what services you’ll need, so trying to start with a microservices architecture is in most cases a premature optimization (and probably a waste of valuable time that could be spent on product development!)