Modernization Through Refactoring
Deliver Modernization through Refactoring
Refactoring applications requires modifying the code used to run the applications. When refactoring applications there is an opportunity to make sweeping changes to the code to reduce technical debt, increase performance, distribute components, or even change the coding language. The primary goal of refactoring is not to add new functionality to an application, but rather make it more efficient and easier to maintain. Reducing technical debt and making it more extensible can improve the speed at which new features are added in the future and lower the cost to maintain the application.
Deconstruct the Monolith
Monolithic applications are difficult for multiple teams to work on together. Code changes made by one team are committed to the central code base and can often interfere with other teams working on the same application. Monoliths also don’t scale very well. If parts of the application require additional physical resources, the entire monolith must be scaled to accommodate the needs for the single service within the app. Because of these reasons, application architects often break down the monolithic application into pieces so that they can be worked on individually and can scale independently from each other.
The Twelve Factor App is a modern application methodology for building software specifically designed for cloud platforms and is a helpful guide when refactoring. As the monolith is deconstructed into smaller components, consider the following from the 12 Factor App Framework.
Backing services consist of any service the application consumes over the network for normal operations. This could be a third-party API service, an email system for notifications, a file store, or a database. Backing services often store state for a distributed application. Carefully plan which backing services should be used with your modernized application. Since backing services store state data, they are more difficult to move to different clouds. Consider using backing services that are portable across cloud platforms to provide multi-cloud flexibility. For example, using a traditional cloud managed database offering might not be portable to a different vendor cloud’s database offering.
An application is executed as a series of one-to-many processes that share nothing and are loosely coupled. Stateful data should not be stored within these processes, including session state. All state should be stored within a backing service to ensure portability and scaling capabilities won’t affect the application.
As processes are separated, they should support concurrency to allow for horizontal scaling. In contrast to monoliths which generally scale vertically by increasing CPU or memory, twelve-factor apps can scale horizontally for just the processes needing more resources. Monoliths require more resources granted to the entire application, whereas a twelve-factor app can scale part of the application without affecting the rest of it. Concurrency is critical to a distributed systems’ reliability. The ability to horizontally scale also provides availability since each process can be deployed in pairs to protect against outages.
Processes in a twelve-factor app can be started and stopped at any time. Disposability of our processes means that these modern applications should be built with an expectation of the processes failing. Disposable processes should be optimized to reduce startup time which provides the ability to quickly scale more services as the system needs.
Distributed systems may need log correlation to determine issues. Event streams make it possible for each individual component to log messages in a similar method and later aggregated and correlated with a log management solution.
Application dependencies should never be implicitly inherited from the system they run on. Each application should explicitly define the dependencies and versions used to build the application. This removes the tight coupling that may have been needed in legacy apps where they host system must have the right dependencies installed prior to the application be deployed.
When decomposing a monolithic application, containers are often used instead of a virtual machine to reduce the resource overhead. Containers also are easily restarted, aiding with disposability, and are easily scalable.
On the extreme end of the modernization continuum are microservices. Microservices are a architectural approach where the applications are composed of independent services separated by a contract such as an API. Each microservice has a specialized function and may adhere to the twelve-factor application framework including having their own backing services.
Microservices bring additional challenges to the operations of an application including orchestrating the deployment of many components across fault domains and providing service discovery between components. Kubernetes provides a container orchestration solution capable of managing containers across hosts and providing a cloud native platform with service discovery. VMware provides Tanzu Kubernetes Grid to all VMware Cloud customers to run their container-based workloads in a vSphere environment. Tanzu Kubernetes Grid can also be used across native cloud environments to keep a consistent Kubernetes runtime across clouds.