Cloud Native 17 min read

From Monolith to Microservices: A Small Team’s CI/CD and Cloud‑Native Journey

This article recounts how a two‑person SaaS team evolved its architecture from a monolithic Java application to a suite of over ten Spring Cloud microservices, detailing RESTful API design, CI/CD pipelines with Jenkins, Docker containerization, Kubernetes integration, link tracing, and operational monitoring.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
From Monolith to Microservices: A Small Team’s CI/CD and Cloud‑Native Journey

Microservices suitability for small teams is subjective.

As business complexity grows, a monolithic application becomes increasingly large, resembling a class with ever‑growing lines of code; dividing it into smaller applications follows the divide‑and‑conquer principle.

Microservice architecture should not be a concern for a small team at the outset; it evolves gradually, and over‑design must be avoided.

The company provides SaaS services, custom development, and private deployments. In less than two years the technical architecture progressed from a monolith to microservices and finally to containerization.

Monolith Era

Early development involved only two people, making microservices unnecessary. Influenced by a previous company, the team adopted front‑back separation as a SPA, though server‑side rendering remains possible (e.g., using PHP or Thymeleaf).

Deployment used Nginx to proxy front‑end HTML and reverse‑proxy requests to the server on port 8080.

API Definition

APIs follow standard RESTful conventions:

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

Resources are plural, e.g., /api/contacts, and can be nested like /api/groups/1/contacts/100.

URLs avoid verbs; naming consistency is enforced during code review.

Supported HTTP methods: POST / PUT / DELETE / GET. Note that PUT performs a full update while PATCH does a partial update; the team treats PUT as a partial update by ignoring empty or missing fields, which can cause issues for true null‑value operations.

Swagger generates documentation for front‑end consumption.

Continuous Integration (CI)

Team members with experience in large teams required robust quality control, so an integration‑testing framework was introduced early, targeting API test cases that include database reads/writes and MQ operations, effectively replacing JMeter at the Java level.

Because integration tests involve external resources, data preparation and cleanup become more complex, especially when ensuring test data isolation for parallel tasks.

Jenkins automates the pipeline: code submission triggers compilation, integration testing, and report generation; successful tests lead to reviewer code review. This CI setup sufficed for the monolith era.

Service Splitting Principles

From a data perspective, services are split where database tables have minimal relationships, often starting with user‑management modules. In DDD terms, a service groups related domain models, possibly with slight data redundancy to define clear boundaries.

Within a service, domain services coordinate multiple domain objects, though implementing a rich (charging) model can be challenging for many developers.

Service decomposition is a large effort requiring collaboration among those most familiar with the business and data, and must consider team structure to avoid circular and bidirectional dependencies.

Framework Choice

The original monolith used Spring Boot, so the natural microservice framework was Spring Cloud. While the framework should not dictate language, both Dubbo and Spring Cloud introduce invasiveness; integrating Node.js services into Spring Cloud revealed many issues, suggesting future service‑mesh solutions.

Typical Spring Cloud components used:

Zuul as gateway to route client requests to specific services.

Eureka as service registry for discovery and registration.

Hystrix in each service for rate limiting and circuit breaking.

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

Zuul’s performance is limited by its synchronous model and lack of dynamic routing; Spring Cloud Gateway (asynchronous) or Kong (NGINX‑based) are considered alternatives.

Architecture Refactoring

After six months of refactoring and new requirements, the monolith was split into more than ten microservices, and Spark was introduced for BI. The system now comprises an OLTP online business layer and an OLAP analytics layer, with data sources expanding from MySQL to Elasticsearch and Hive.

Automated Deployment

Compared to CI, continuous delivery (CD) is more complex; the team currently performs automated deployment only. Production deployment uses a jump host: Jenkins builds JARs, transfers them to the jump host, and Ansible deploys them to the cluster.

This straightforward approach works for a small team, provided thorough manual and automated testing precedes deployment.

Link Tracing

Open‑source tracing solutions like Spring Cloud Sleuth + Zipkin or Meituan’s CAT assign a fixed identifier to a request to trace its path across services, enabling latency analysis and troubleshooting.

The team implements lightweight tracing by logging RequestId (unique per request) and TraceId (secondary path identifier). RequestId is generated by the gateway; TraceId starts identical but diverges when entering thread pools or message queues.

When a request publishes to MQ, each consumer generates its own TraceId. The context is propagated via HTTP headers for service‑to‑service calls or via message headers for MQ, using ThreadLocal to store APIRequestContext.

Log aggregation allows quick issue location by filtering on RequestId or TraceId.

Operations Monitoring

Before containerization, the stack used telegraf + influxdb + grafana to collect JVM, system, and MySQL metrics, with Grafana visualizing the data. spring‑boot‑actuator combined with jolokia exposed JVM endpoints, requiring no code changes.

Containerization Era

Since microservice planning included containerization, each service now has a Dockerfile to build its image.

Spring Cloud and Kubernetes Integration

The team runs on Red Hat OpenShift (enterprise Kubernetes). A Kubernetes Service groups multiple pods, providing built‑in load balancing; services call each other using the service name, similar to Spring Cloud Feign + Ribbon.

Because heterogeneous language services (e.g., Node.js BFFs) required custom registration, discovery, load balancing, and health checks, the team replaced Eureka with Kubernetes native service discovery.

During local development and integration testing, Eureka remains enabled; in production it is disabled via configuration.

eureka.client.enabled=false
ribbon.eureka.enabled=false
# Example for service foo
foo.ribbon.listofservers=http://foo:8080

CI Refactoring

CI now builds Docker images and pushes them to Harbor; deployment pulls images directly from Harbor.

The database migration tool, previously Flyway, caused startup delays when multiple service instances upgraded concurrently. The team split migration tasks into per‑service containers, running them as one‑off Docker commands, optionally targeting specific versions for cross‑version upgrades.

Jenkins pipelines also enforce deployment order based on service dependencies (e.g., config, Eureka).

Conclusion

Microservice evolution touches development, testing, and operations, demanding close collaboration among diverse roles. The team applied divide‑and‑conquer without blindly chasing trends, using service‑oriented architecture to address scaling challenges while recognizing the higher demands on people and processes.

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/cdKubernetesSpring CloudService Splitting
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

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.