alex_aldo - Fotolia
Software teams know the benefits of breaking a monolithic application into independent, loosely coupled microservices, but many teams fail in their efforts to migrate existing applications.
If you start with a monolithic architecture with well-segmented domains, minimal dependencies and an automation-guided release process, you probably don't need help migrating to microservices. In fact, you should probably buy a lottery ticket because you're lucky. Most monolithic applications were built up over time, with diverse naming structures, components reconfigured to work in new domains and tightly coupled integrations -- by developers who didn't document their code and have since left the company.
Follow these tips from two experts, both of whom presented at the 2020 O'Reilly Software Architecture Conference in New York, to break up messy monoliths into tidy microservices -- or discover where to leave the monolith in place.
Step 1: Establish an application architecture baseline
To break apart a monolith, you first have to know what's in it. At the start of any microservices project, segment the existing monolith's architecture by logical components, such as the namespaces.
Components, in this context, are any sets of classes that do one thing, said Mark Richards, an independent consultant. "You don't have to move individual classes," he said. "If you start cherry-picking and say these classes will be a good microservice, these other classes will come too and ... it will never end." Understand the broad pieces of the monolithic application first.
This discovery requires involvement by both IT and business teams. For example, the software team can create a list of APIs, but it still needs input from business stakeholders on what they actually use in the application and how important those APIs are. By investigating the current state of the application and reconciling it with actual usage, a company can cut cost on zombie instances and improve performance, said Mike Amundsen, a development and architecture consultant. It's possible that all you need is an improved monolith, light-years away from a microservices architecture.
"What do you actually have? What's actually being used, and how is it being used? That's the first step in stability," Amundsen said.
Step 2: Refactor into a domain-driven design
To progress toward microservices, assess the size of the namespaces identified as future services. If any single component in the architecture contains more than 10% of the overall application, break it down into smaller pieces, Richards said. He bases this on statements, rather than lines of code, since code volume varies from one developer to another.
Now that you have segmented namespaces of roughly equal size, look for what Richards calls hills, which are stacks of increasingly specific namespaces. Flatten these hills by reassigning classes to lower levels to get rid of extraneous namespaces, and create new groupings as needed. When in doubt, leave larger groupings together -- you can always break them apart later.
Microservices adoption is on the rise
In 2018, O'Reilly Media reported about 50% of software projects use microservices, after surveying more than 800 software architecture practitioners. A year later, 84% of respondents said they've adopted microservices, according to a smaller study conducted by technology market research firm Vanson Bourne on behalf of API and microservices management provider Kong Inc. However, only 13% of organizations reported running 500 or more microservices in production, with 60% of microservices deployments containing 100 services or fewer.
Eventually, the monolith is logically translated to services, with namespaces organized by domains and subdomains. It's modularized. Richards recommended no fewer than four -- and no more than 12 -- domains per application.
Next, check the dependencies between these components. Fewer dependencies mean less work. If certain components are tightly coupled, leave them out of the initial microservices migration. If most or all of the components are highly interdependent, consider simply rewriting existing code rather than refactoring the entire application.
Dependency visualization is also crucial to set priorities within the microservices project. "With this, you'll have absolutely no idea what you are in for," Richards said. The lack of a dependency map is why so many migrations from monolith to microservices fail.
As you refactor code, Richards recommended renaming any namespace in a domain that is structured differently than the rest. For example, every namespace in the shipping domain of the application should be named shipping.X.
Before you refactor this modularized monolith code into microservices, take steps to shield users from disruption, and ensure that no important business applications go offline during the process. Ensure that every change is reversible without data issues. Both Richards and Amundsen recommended that software teams rely on several techniques:
- Decouple the presentation logic into a remote UI with API access.
- Install proxies between API consumers and services.
- Set up facades.
- Release new code with toggles and dark launches rather than cutovers.
Once the UI is separated from the app's functions and code is grouped into easy-to-understand domains and dependencies, the monolith is no longer uncharted territory. It's navigable and modular, what Richards calls clean, service-based architecture. Many organizations can comfortably stop here and still enjoy increased agility, deployment rates and testability.
Also, most software teams don't need to dive as aggressively into a microservices architecture as webscale companies tend to, Amundsen said. "That top level, with runtime scalability and agility, where Netflix is, that's super super complicated, super super abstract and super super expensive." And, while a service-based modular architecture is difficult to accomplish, it doesn't require heavy database changes or organizational restructuring since you haven't broken the monolith into microservices yet.
Step 3: Build microservices
Microservices are pieces of single-purpose code that do one thing well. From tens of business or functional services, an application architecture can split into hundreds or thousands of microservices.
Start your work at points where the application will benefit the most from microservices -- ideally, where automation, DevOps and containerization are already in place. If possible, tackle a high-change but low-risk component, Richards said, such as nonfinancial, customer-facing functionality. Using the strategies described above, like dark launches, teams increase the chance that they can refactor without negative consequences on the user base.
To create microservices, identify functional blocks of code that should stand alone, map the associated data and identify relevant interfaces. Developers and architects must find the right places to split or consolidate services based on all three considerations, Richards said.
Decide how you will manage microservices, and prioritize either reusability or specificity. "The more you make it specific to someone's needs, the less reusable it becomes," Amundsen said. Some organizations are careful about repetition in microservices development, while others tolerate a huge volume of copies and duplicates, so long as they interface consistently.
Microservices architects should revisit and refine the application composition continually. Microservices refactoring is not a one-and-done initiative. "This continues until you die or the projects gets cancelled," Richards joked.
Will it work?
To be successful, don't promise too much all at once. Engage with business stakeholders because microservices might require new hardware, software, IT tools and people. Effective microservices development and deployment demand cross-functional teams that collaborate freely and implement a strong CI/CD pipeline.
To get to microservices, Richards advised that software teams work from the domain-driven modular design and build competencies judiciously. For example, set up deployment automation before it's required. Collaborate across job titles: Testers can guide developers to write more production-ready code with checklists.
You don't need to have the same architecture and deployment approach across the whole company, Amundsen pointed out. For example, shop-floor workers' handheld apps have different requirements than mainframe-based transaction processing software. "Balancing speed and safety at scale ... is a big part of what the migration is going to be about."
Amundsen also stressed that a lot of businesses want a big change, as opposed to incremental steps that take months and years to accomplish. "I'm going to tell you to take everything a bit at a time. That doesn't sell really well when you're trying to get [a project] funded," he said. To mitigate this problem, he advised that organizations pick the best targets for modernization -- low-dependency, high-change features -- and assert business-relevant reasons to migrate them.