Cloud Native 14 min read

How a Small Team Evolved from Monolith to Cloud‑Native Microservices

Facing growing business complexity, a two‑person SaaS team transitioned their monolithic application to a microservice architecture, adopting Spring Cloud, Docker, Kubernetes, CI/CD pipelines with Jenkins, automated testing, service splitting principles, and lightweight tracing, ultimately achieving scalable, observable, and maintainable cloud‑native operations.

21CTO
21CTO
21CTO
How a Small Team Evolved from Monolith to Cloud‑Native Microservices

Monolith Era

In the early stage, the team consisted of only two developers. They adopted a front‑end/back‑end separation strategy, building a single‑page application (SPA) without SEO concerns. Deployment used Nginx to proxy static HTML resources and forward API requests to the backend server on port 8080.

Monolith Architecture Diagram
Monolith Architecture Diagram

Interface Definition

APIs follow standard RESTful conventions:

Versioning is placed after /api/, e.g., /api/v2.

Resources are expressed in plural, e.g., /api/contacts, with possible nesting like /api/groups/1/contacts/100.

Avoid verbs in URLs; naming consistency is enforced during code review.

Supported HTTP methods: POST, PUT, DELETE, GET. Note that PUT performs a full update, while PATCH is a partial update. In practice the team treats PUT as a partial update by ignoring empty or missing fields, which can cause special‑case handling for explicit nulls.

Swagger generates documentation for front‑end consumption.

Continuous Integration (CI)

The team introduced an integration‑testing framework early on, writing test cases directly against APIs, covering database reads/writes and message‑queue operations. Tests run automatically via Jenkins, generating coverage reports. After successful tests, code is reviewed in Gerrit before merging.

CI Pipeline Diagram
CI Pipeline Diagram

Microservice Era

Service splitting follows the principle of low data coupling; user‑management modules are often the easiest to separate. From a Domain‑Driven Design perspective, a service encapsulates one or more related domain models, possibly with limited data redundancy to define clear boundaries.

Splitting requires collaboration among domain experts and consideration of team structure to avoid circular or bidirectional dependencies.

Framework Choice

The existing monolith used Spring Boot, so the team naturally adopted Spring Cloud for microservices. While Spring Cloud and Dubbo are both intrusive, integrating heterogeneous languages (e.g., Node.js) proved challenging, suggesting a future shift toward Service Mesh.

Key components:

Zuul as API Gateway.

Eureka as service registry.

Hystrix provides rate‑limiting and circuit‑breaker features.

Feign and Ribbon handle inter‑service calls, with Feign abstracting Eureka interactions.

Zuul’s synchronous model can exhaust servlet threads under heavy I/O, leading to ~30% performance loss compared to direct service calls. Spring Cloud Gateway or Nginx‑based Kong are considered for better async handling.

Architecture Transformation

After six months of refactoring and new feature development, the monolith was split into more than ten microservices. A Spark cluster was added for BI, forming two major subsystems: an OLTP online business system and an OLAP analytics system. Data sources expanded from MySQL to include Elasticsearch and Hive.

Microservice Architecture Diagram
Microservice Architecture Diagram

Containerization Era

Each service has its own Dockerfile to build images. CI now includes a step to build Docker images and push them to Harbor. Database migration scripts were containerized and executed as one‑off Docker runs, avoiding concurrent Flyway upgrades.

In production, OpenShift (Red Hat’s enterprise Kubernetes) replaces Eureka with native Service objects for service discovery and load balancing. Configuration still uses Eureka locally, but production disables it via:

eureka.client.enabled=false
ribbon.eureka.enabled=false

Explicitly setting

foo.ribbon.listOfServers=http://foo:8080

CI Refactoring

The pipeline now builds Docker images, pushes them to Harbor, and runs containerized database upgrade tools. Jenkins pipelines also enforce deployment order for dependent services such as Config and Eureka.

Automated Deployment

Jenkins pipelines orchestrate deployments, handling service dependencies and ensuring correct rollout sequencing.

Link Tracing

Lightweight tracing uses RequestId (generated by the gateway) and TraceId (extended at each thread pool or message‑queue hop). Logs include these IDs, enabling rapid pinpointing of problematic services.

Tracing Flow Diagram
Tracing Flow Diagram

ThreadLocal stores an APIRequestContext per request; when crossing service boundaries, the context is serialized into HTTP headers (or message headers for MQ) and reconstructed downstream, preserving RequestId and TraceId.

Operations Monitoring

Before containerization, the team used Telegraf + InfluxDB + Grafana to collect JVM, system, and MySQL metrics. Spring Boot Actuator combined with Jolokia exposed JVM endpoints. The solution required only configuration, no code changes.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Dockerci/cdcloud-nativeMicroservicesKubernetesSpring Cloud
21CTO
Written by

21CTO

21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.