How Evolutionary Architecture Transforms Monoliths into Agile Microservices
This article examines evolutionary architecture for microservices, outlines popular patterns such as CI/CD and the Strangler approach, explains layered and bounded‑context designs, and provides a detailed case study showing how a legacy monolithic system can be incrementally refactored into a flexible, cloud‑native microservice ecosystem.
This article explores evolutionary architecture for microservices, introduces popular patterns to consider when establishing a microservice strategy, and briefly presents case studies on how to implement these strategies.
Microservices: Nature of Evolutionary Architecture
Architectures are usually created according to predefined principles that help reflect an enterprise’s business strategy goals. Business models change rarely, but today enterprises want highly scalable businesses to handle more transactions and serve more customers. They need flexible operational processes to quickly enter new markets and increase innovation in existing markets. Some immutable common goals give rise to a set of architectural principles that encourage enterprises to build, design, implement, and run IT systems in an evolutionary way. One of the most important principles of evolutionary architecture is the ability to support continuous and incremental change across multiple dimensions of technology and business.
Traditional software architecture elements can be hard to change. Once decisions are made and components are designed and implemented, altering the system’s architecture becomes difficult. As layered architectures and the patterns that implement them have emerged, the way systems are designed has changed. Figure 1 shows a typical layered architecture that drives system evolution.
Figure 1 A Layered Architecture Example
With a layered architecture, changes can be made in each layer without affecting other parts of the system. For example, changing a component in the persistence layer theoretically has little impact on the business and database layers and no impact on the presentation layer. This represents a degree of evolution; the system has four continuously evolving opportunities—each of the four layers can evolve. However, this evolution occurs only in one dimension: the technical aspect of the architecture.
When a business‑domain perspective is added to the architecture, that perspective lacks an evolutionary dimension. Changes in the business domain can be difficult because they often require touching multiple modules or layers. For example, it is usually hard to change the customer‑related information and flow within a service because parts of the customer reside in the business layer while other parts are scattered across the data, presentation, and other layers.
Microservice architecture is, by nature, an evolutionary architecture. One of its typical features is that each service forms its own bounded context and operates independently from all other services in the system. Technically, each service fully encapsulates its own concerns. You can easily remove a service from the system and replace it with another without affecting other services, thanks to the high decoupling principles of this style. This makes microservice architecture more flexible, giving each service the ability to evolve.
Figure 2 shows a typical microservice architecture overview. Note that an API gateway is a mandatory component of microservice architecture. By routing API requests to the appropriate microservice, the gateway can handle different requests effectively. When using an API gateway, design and implementation must be careful to avoid coupling between services. Each microservice can contain multiple micro‑components that may be distributed across different layers. Figure 1 shows microservice types.
Figure 2 More Evolutionary Dimensions in Microservice Architecture
Evolutionary Practices and Patterns
Having an evolutionary architecture is important for building more flexible business models. This section introduces practices and patterns that can drive evolutionary architecture.
Continuous Integration, Continuous Delivery
Continuous Integration (CI) is a software development practice where developers frequently (often daily) integrate their work, leading to multiple integrations per day. The idea is to isolate potential integration problems early, allowing them to be resolved promptly. CI is supported by an automated pipeline that builds and tests each change, quickly validating each build and detecting integration errors. Early isolation reduces integration issues during development, ultimately speeding product delivery.
CI is usually combined with Continuous Delivery (CD), which continuously deploys each tested build to production. By adopting CI and CD, developers spend less time fixing bugs and more time developing new features or enhancing existing ones, delivering higher business value.
Both CI and CD rely on automation throughout every step of the development lifecycle, including infrastructure provisioning. With the rise of cloud technologies and mature CI/CD tools, automation has evolved and plays a critical role in the evolution of architecture.
Strangler Application
Martin Fowler introduced the term Strangler Application in his 2004 article. In this pattern, a new system intercepts calls to a legacy application, routes them to new functions, and gradually replaces the legacy system. Compared with a cut‑over rewrite, the Strangler approach reduces risk by allowing incremental refactoring of a monolithic application into microservices. Over time, the legacy system’s functionality diminishes until it disappears or becomes part of the new microservices. Figure 3 illustrates the transformation process.
Figure 3 Incrementally Replacing an Application
The Strangler architecture still contains the original monolith and the new services. It also includes two main components:
Scheduler: Acts as a request router, directing requests for new functionality to new services and routing older requests to the existing monolith.
Integration Component (IC): Located in the new service, the monolith, or both, it integrates the monolith with newly created services. Parts of the monolith can be wrapped as a record system exposed via a set of APIs.
An evolutionary architecture principle is to be prepared for the unknown, meaning it is forward‑looking rather than predictive. Under natural evolution, features you build today may become obsolete and be replaced in the future, leading to the gradual disappearance of the legacy application.
Evolutionary Database Refactoring and Deployment
Databases (more precisely, the data within them) are often the most valuable assets in an IT system. As the system evolves, the database must also change to stay in sync with application code changes. Database changes include schema modifications and data migration from old structures to new ones, and may even involve moving from a relational DBMS to a NoSQL model. Traditional database update tools and processes are not innovative enough to keep pace with rapid application development, potentially hindering fast releases.
A new, automation‑supported database change management and deployment method is needed to perform continuous and incremental changes in an evolutionary architecture such as microservices. This section discusses techniques that align database changes with application code changes. Scott Ambler and Pramod Sadalage, in "Refactoring Databases: Evolutionary Database Design," present 13 lessons that form three steps: break large changes into small ones, version control the changes, and automate database deployment.
Break Large Changes into Small Changes
When performing a large database change, split it into smaller, individually testable changes. Each change typically combines a schema modification, data migration, and related access‑code changes. This allows rapid isolation of failures within each fragment and maps database changes to the application functionality that uses them, enabling incremental deployment within the same agile pipeline.
Version Control the Changes
Organize database change scripts in the same repository as application source code, mirroring the directory structure and naming conventions. This aligns database changes with the specific application version that requires them, simplifying coordination across teams.
Automate Database Deployment
Automation is key to managing and deploying evolutionary database changes. By breaking changes to an appropriate granularity, reorganizing scripts, and applying version control, you obtain an automation‑supported, evolution‑friendly deployment method. CI/CD pipelines can then automate database deployments similarly to application code.
Evolutionary Architecture in Practice
This section demonstrates how to apply evolutionary methods to build a more flexible architecture using a fictional Company A case study. The technical architecture is driven by business strategy requirements. Part 1 "Microservices" outlines the business needs that help you start building an evolutionary strategy for a monolithic application.
Basic steps for the transformation guide:
Start with product data: Migrate product data to Elasticsearch to enable powerful search, creating a new microservice on the cloud that uses a NoSQL model and new indexes.
Enhance the account service: Combine existing customer data with social data to personalize content, using a JSON‑based data model.
Migrate part of the customer data to the cloud using a hybrid integration pattern to synchronize old and new data sources.
Optimize the UI layer: Begin with mobile support, then incrementally update the UI using microservice‑based data models.
Order processing is the final step: Wrap the existing order component with an API to improve flexibility and decoupling, preparing for future steps.
Stepwise Transition to Catalog Service
Product information is core to Company A’s business. Providing customers with easy, interactive access to this data is critical. The catalog component becomes a new microservice running in the cloud, handling both logic and data migration, eliminating latency when the service accesses the database.
Technical implementation may retain the same technology stack as the original Java monolith to avoid large‑scale changes, but later steps can adopt newer stacks. The new catalog service can initially store data in a relational model before moving to a modern, lightweight NoSQL/JSON model to improve search.
Replacing Customer and Order Components
Business continuity is vital, so the transition must avoid service interruption. The Strangler technique mitigates downtime by running the new microservice alongside the legacy component and gradually shifting traffic.
Customer‑related business components and data are broken down and converted to an evolutionary model, while the old components continue to run in parallel. Traffic is gradually moved to the new services.
Transforming the UI into a UI Microservice
Company A needs to extend the system to support mobile customers. The main steps are:
Modify the CustomerServiceDojo module to support mobile users and move toward responsive design.
Adopt responsive client libraries such as Bootstrap and Angular to improve user experience and make the UI more modular.
Migrate the UI layer to the cloud for faster development, deployment, and adaptability.
Next, we will identify monolithic application candidates suitable for microservice adoption and discuss redesign and refactoring of new components.
Identifying Candidate Functions
A monolithic application is a good candidate for evolution when it exhibits any of the following:
Long time‑to‑market for new services due to difficulty maintaining, modifying, or quickly delivering new features.
Inability to deploy a single component independently; changes require rebuilding the entire .ear or .war.
Only one technology choice, preventing adoption of newer libraries or techniques.
Large system characteristics such as massive in‑memory data, high CPU usage, inability to scale parts independently, difficult maintenance, and complex code dependencies.
Complexity that makes onboarding new developers hard.
Considerations When Moving to Microservices
Platform flexibility – each service can use its own language and data store.
Design by capability – each microservice owns a specific business domain.
Ease of rewriting a whole service – small, well‑encapsulated services are easier to replace.
Flexible change execution – decisions can be postponed until truly needed.
Business value – faster response to market demands and quicker feedback loops.
Scalable expansion – individual services can be scaled independently.
Security partitioning – services can be isolated behind firewalls and encrypted channels.
Team organization – teams can own services end‑to‑end, reducing cross‑team coordination.
Decomposing a Monolith into Microservices
This section covers technical steps for breaking a monolithic application into microservices.
Designing Microservices
Microservice architecture allows free choice of technology stack and service size, driven by clear user‑experience understanding.
Use design thinking to bound and identify microservices
Design thinking is a process that envisions the overall user experience. Instead of focusing on a single feature, microservices focus on the experience a user has. Design thinking helps limit work scope to releasable functional units, aiding decomposition and identification of microservice opportunities. Concepts include Hills, Hills Playback, Solutions, User Cases, Epics, Sponsor Users, and Identifying Microservice Opportunities.
Hills
A Hill expresses a business goal for a release schedule. It defines who uses which resources to achieve the goal. Teams typically set three Hills and a technical foundation. Hills convey leadership intent, allowing teams to decide how to implement a smooth user experience. Hills must be user‑focused and measurable.
Hills Playback
Hills Playback summarizes the goals for a specific time window. Playback Zero marks the moment the team is formed and presents the intended outcomes to business sponsors. Cross‑functional participants meet weekly to execute the Playback.
Solution
A Solution is a workflow that implements an experience, defining the cases and context used in Hills Playback. Large solutions can be broken into scenarios and user cases.
User Case
A User Case is an independent, codable requirement that can be developed in one or two days, expressed from a user‑experience perspective.
Epic
Epics group related cases for reuse across the solution, avoiding duplication.
Sponsor User
A Sponsor User participates throughout the project, representing the target user, and may lead Playback sessions.
Identifying Microservice Opportunities
For each design, look for potential reuse of services in other designs. The example Hill targets an iOS solution on Bluemix.
Deploy to Bluemix
Deploy as a standalone service on Bluemix
Deploy to the Bluemix microservice team
Choosing an Implementation Stack
Microservices run as separate processes, so any technology that supports the required communication (HTTP/REST, MQTT, AMQP, etc.) can be used. Consider whether the service is I/O‑bound or CPU‑bound. Node.js is suitable for I/O‑bound services, while Java, Go, or C are better for CPU‑intensive work. Cloud platforms such as IBM Bluemix provide options for containerized deployment.
Determining Microservice Size
Deciding the number and size of microservices is challenging. Practices include evaluating file count, avoiding too many responsibilities per service, and ensuring each service does one thing (e.g., authentication, providing REST endpoints, serving web pages). Over‑splitting leads to a microservice anti‑pattern; start larger and split later if needed.
Refactoring
Refactoring modernizes applications, adding new structure and platform benefits while preserving functionality. The migration from monolith to microservices follows similar routes.
Repackaging a Monolith (Figure 5)
Steps:
Split the EAR into independent WAR files.
Deploy each WAR in its own container (e.g., Docker or Bluemix runtime).
Manage each WAR independently via automated DevOps pipelines.
Refactoring Code (Figure 6)
Identify opportunities such as:
Front‑end: Convert simple servlets/JSP to RESTful services and modern UI frameworks (Bootstrap, Angular) or native mobile apps.
HTTP session state: Move session data to a database.
SOAP/EJB services: Map to RESTful interfaces, possibly converting XML to JSON.
REST/JMS services: Separate each service into its own WAR and package shared JARs.
Refactoring Data (Figure 7)
Rules include:
Data islands: Isolate tables that are independent or form a small cluster, then separate them.
Choose the appropriate database based on query patterns (key‑value or document stores for simple primary‑key queries, SQL for complex joins).
Batch data updates: For limited relationships, periodic batch loads may suffice.
Denormalization: When relationships are many, denormalize tables to improve query performance.
Example of Identifying and Creating a New Architecture
Based on the fictional Company A business problem, this section defines a new architecture, explains the rationale for each new service, and outlines the benefits.
Current Architecture
The existing application is a typical Java monolith packaged in an EAR and running on an application server. It uses JSF and Dojo for the front‑end, EJB for business components, and JPA for persistence (DB2 and SQL). Figure 9 shows the monolithic architecture.
Target Architecture
The new architecture is based on a Platform‑as‑a‑Service (PaaS) cloud environment, leveraging services to improve DevOps control, and using cloud resources such as databases, runtimes, security, and application servers for better independent scalability.
Using a gradual migration, the Catalog and Account components move to microservices, while the Order component remains in the monolith (Figure 10).
Key components:
PaaS platform – provides a cloud environment with all resources needed for the full lifecycle of web‑based applications.
Security gateway – protects internal networks from unsafe traffic and ensures compliance; integrates with the monolith’s SQL database.
Five applications:
UI application using jQuery, Bootstrap, and AngularJS, consuming REST APIs from microservices.
Catalog microservice with a new search service and ETL updates from the monolith.
Order microservice that retrieves order data from the monolith via a gateway.
Account microservice with added analytics, a NoSQL database, and social integration.
Enterprise data center – partial migration; Order remains monolithic, but a new API provides order access.
Summary
This article examined evolutionary architecture for microservices and described how to identify monolithic applications suitable for evolution into a microservice architecture. The next part will discuss important database topics when evolving monoliths to microservices. Happy learning, see you next time!
Source: https://www.ibm.com/developerworks/cn/java/j-cn-java-and-microservice-5/index.html?ca=drs-
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITFLY8 Architecture Home
ITFLY8 Architecture Home - focused on architecture knowledge sharing and exchange, covering project management and product design. Includes large-scale distributed website architecture (high performance, high availability, caching, message queues...), design patterns, architecture patterns, big data, project management (SCRUM, PMP, Prince2), product design, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
