Sitemap
Habtamu Tesfie

Habtamu Tesfie

Implementing a Robust Microservices Solution with Spring Cloud Gateway

Brokers, circuit breakers, service discovery, tracing and monitoring

18 min readOct 25, 2026

--

Press enter or click to view image in full size

In today’s dynamic and interconnected digital landscape, building resilient and scalable microservices architecture is not just a trend but a necessity. As businesses strive for agility and efficiency, the implementation of robust frameworks like Spring Cloud Gateway becomes pivotal. Coupled with sophisticated components such as brokers, circuit breakers, service discovery, and distributed tracing systems, this approach promises to revolutionize the way applications are developed, deployed, and maintained.

In this article, we delve into the intricacies of implementing such a comprehensive architecture. From harnessing the power of Spring Cloud Gateway for efficient API management to leveraging robust brokers for asynchronous communication, we explore how each component contributes to the overall resilience and scalability of your microservices ecosystem. Furthermore, we uncover the pivotal role of circuit breakers in maintaining system integrity under duress and the transformative impact of distributed tracing systems on debugging and performance optimization.

Join us on a journey through the evolving landscape of microservices architecture, where cutting-edge technologies converge to elevate your applications to new heights of reliability and efficiency. By understanding and implementing these components effectively, you pave the way for a future-ready infrastructure that not only meets but exceeds the demands of modern digital enterprise.

Prerequisites

Before you begin, ensure you have the following:

  • Java 17: Installed on your development machine.
  • Maven 3.8.5: Dependency management tool for Java projects.
  • Basic Understanding of Spring Boot: Knowledge of how to create Spring Boot applications, define dependencies, and configure application properties.

Creating the Product Service

Step 1: Setting Up the Project

1. Initialize a Spring Boot Project: You can use Spring Initializr (https://start.spring.io/) or your IDE to create a new Spring Boot project with the following dependencies:

  • Spring Web
  • Spring Data MongoDB
  • Lombok (for automatic generation of boilerplate code)

2. Configure MongoDB: Ensure MongoDB is installed and running locally. Update your application.properties with MongoDB configuration:

Press enter or click to view image in full size

Step 2: Implementing the Product Service

The Product Service is responsible for managing product information, including creating and retrieving products. This service is implemented using the following components:

1. Controller:

  • The ProductController is the entry point for handling HTTP requests related to product operations.
  • It provides endpoints to create a new product and to retrieve all products.

2. Service:

  • The ProductService contains the business logic for managing products.
  • It interacts with the database via the ProductRepository to save new products and fetch all existing products.

3. Repository:

  • The ProductRepository is an interface that extends MongoRepository, providing methods for CRUD operations on the product collection in MongoDB.

Please refer to the GitHub link provided for the implementation of the product service: Github

Creating the Inventory Service

Step 1: Setting Up the Project

1) Initialize a Spring Boot Project: Start by creating a new Spring Boot project using Spring Initializr (https://start.spring.io/) or your IDE. Include the following dependencies in your pom.xml:

  • Spring Web: Enables building web applications using Spring MVC.
  • Spring Data JPA: Provides support for working with relational databases using Java Persistence API (JPA).
  • MySQL Driver: JDBC driver for MySQL to connect Spring Boot application with MySQL database.
  • Lombok: Simplifies Java code by reducing boilerplate code (optional, but recommended).

2) Configure MySQL Database: Ensure MySQL is installed and running locally. Update your application.properties with MySQL configuration:

Step 2: Implementing the Inventory Service

The Inventory Service is responsible for managing inventory information, including checking stock availability for products. This service is implemented using the following components:

1. Controller:

  • The InventoryController is the entry point for handling HTTP requests related to inventory operations.
  • It provides an endpoint to check the stock availability of products by SKU codes.

2. Service:

  • The InventoryService contains the business logic for managing inventory.
  • It interacts with the database via the InventoryRepository to fetch inventory details and check stock availability.

3. Repository:

  • The InventoryRepository is an interface that extends JpaRepository, providing methods for CRUD operations on the inventory table in the MySQL database.
  • It includes a method to find inventory items by their SKU codes.

Please refer to the GitHub link provided for the implementation of the Inventory service: Github

Creating the Order Service

Step 1: Setting Up the Project

1. Initialize a Spring Boot Project: use Spring Initializr (https://start.spring.io/) or your preferred IDE to create a new Spring Boot project. Include the following dependencies in your pom.xml:

  • Spring Web: To build web applications using Spring MVC.
  • Spring Data JPA: To interact with relational databases using JPA.
  • MySQL Driver: To connect the Spring Boot application with a MySQL database.
  • Lombok: To reduce boilerplate code by using annotations for generating getters, setters, and constructors automatically.

2. Configure MySQL:

  • Ensure MySQL is installed and running locally.

Update your application.properties file with the MySQL configuration details to establish a connection.

Step 2: Implementing the Order Service

The Order Service is responsible for managing orders, including placing new orders and verifying stock availability through the Inventory Service. This service is implemented using the following components:

1. Controller:

  • The OrderController is the entry point for handling HTTP requests related to order operations.
  • It provides an endpoint to place a new order.
  • It includes Resilience4j annotations for circuit breaker, retry, and time limiter to enhance the service’s resilience.

2. Service:

  • The OrderService contains the business logic for managing orders.
  • It interacts with the database via the OrderRepository to save new orders.
  • It communicates with the Inventory Service to verify stock availability before placing an order.

Please refer to the GitHub link provided for the implementation of the order service: Github

Enhancing Microservices with Service Discovery, API Gateway, Fault Tolerance, Message Broker, and Distributed Tracing

1) Service Discovery

Service discovery provides a mechanism for microservices to locate each other dynamically within a microservices architecture. In real-world scenarios, the port number or IP address of a microservice instance might change, which can cause service calls between microservices to fail if they rely on static addresses. To overcome this problem, a service discovery component is essential. Spring Cloud offers Eureka Server and Eureka Clients to address this challenge.

Setting Up Eureka Server for Service Discovery

To implement service discovery, we need to set up a Eureka Server. This server will maintain a registry of all the microservices and their instances, enabling dynamic discovery and interaction.

Step 1: Adding Dependencies

First, add the necessary dependencies for the Eureka Server in your pom.xml file:

Step 2: Adding Configuration Files

Application.properties

Step 3: Main Application Class

Create the main application class for the Eureka Server and annotate it with @EnableEurekaServer.

Press enter or click to view image in full size

Step 4: Security Configuration

To ensure that Eureka’s endpoints are secured while allowing access to the Eureka dashboard, configure security settings as follows:

1. Add spring security dependency:

2. Configure credentials on the application.properties file

Press enter or click to view image in full size

Step 5: Disable Security Configuration for Eureka Server

The SecurityConfig class configures security settings for the Eureka Server. It ensures that CSRF protection is disabled for requests to the Eureka endpoints, allowing the Eureka dashboard and client registrations to function properly without interference from CSRF security measures.

Press enter or click to view image in full size

Modifying Microservices for Service Discovery Integration

To integrate each microservice (Product, Inventory and order services) with Service Discovery (Eureka Server), follow these steps to modify their configuration:

1. Update pom.xml Dependencies:

Add the following dependencies to product, order and inventory service’s pom.xml file to enable Eureka Client integration:

2. Update application.properties:

Setting the server port to 0 for each service allows the service to run on any available port.

  • server.port=0

Setting the service name for Eureka registration like the following for each service.

  • spring.application.name=product-service
  • spring.application.name=order-service
  • spring.application.name=inventory-service

Setting the service discovery (Eureka) server url for each service’s configuration:

Press enter or click to view image in full size

Please check the implementations of service discovery from the github link: Github

2) API Gateway

It serves as a single entry point for all client requests to interact with the microservices architecture. It provides a unified interface, enhances security, and facilitates dynamic routing and filtering of requests.

Spring Cloud Gateway is a powerful and flexible API Gateway built on top of the Spring Framework. It provides essential features for managing and routing HTTP requests within a microservices architecture. Here’s a detailed explanation of its components and functionalities:

Key Components and Features

1) Routing:

Spring Cloud Gateway allows you to define routes that determine how incoming requests are forwarded to backend services. Routes can be defined based on URI path, headers, HTTP methods, and other request attributes.

Press enter or click to view image in full size

id: Unique identifier for the route.

uri: URI of the backend service or load-balanced service.

predicates: Conditions that must be met for the route to be matched.

2) Filters:

Filters are applied to requests and responses as they pass through the gateway. They can modify requests and responses, apply rate limiting, add headers, authenticate requests, and more.

Press enter or click to view image in full size

name: Name of the filter (e.g., RequestRateLimiter, AddRequestHeader)

args: Arguments specific to the filter configuration.

Integration with Service Discovery (Eureka)

Spring Cloud Gateway integrates seamlessly with Eureka Server (spring-cloud-starter-netflix-eureka-client). This allows it to dynamically discover and route requests to registered microservices without hardcoding their addresses.

Press enter or click to view image in full size

Service Discovery: Routes can directly reference services registered with Eureka using logical names (lb://service-name).

Load Balancing Strategy

Load Balancing with lb://

The lb:// prefix in Spring Cloud Gateway routes indicates that requests should be load-balanced across multiple instances of a service registered with a service registry (such as Eureka). This approach ensures high availability, scalability, and fault tolerance by distributing incoming traffic across multiple instances of a service.

When a request matches a route configured with lb://service-name, Spring Cloud Gateway queries the service registry to retrieve available instances of service-name. It employs a load balancing strategy (e.g., Round Robin) to distribute incoming requests among the identified instances.

Load Balancing with WebClient:

In addition to configuring routes in Spring Cloud Gateway, you can also enable client-side load balancing in your services using WebClient with the @LoadBalanced annotation.

Press enter or click to view image in full size

Check out the link: GitHub Link

Dynamic Updates: The service registry continuously monitors the health and availability of service instances. Spring Cloud Gateway automatically updates its list of available instances based on changes in the service registry.

Security

Spring Cloud Gateway integrates with Spring Security (spring-boot-starter-security) to enforce security policies, authenticate requests, and manage access to routes and services.

# application.properties
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/realms/spring-boot-microservices-realm

Rate Limiter with Redis

It is a technique used to control the rate of requests sent or received by a server. In a microservices architecture, rate limiting is essential to prevent individual services from being overwhelmed by too many requests, thereby ensuring stability and reliability.

Press enter or click to view image in full size

To effectively run the API Gateway, ensure the following servers are configured on your local environment:

1. Redis Server: Redis should be configured and running on port 6379. Redis is used as a backend to manage rate limiting configurations and counters, crucial for controlling request rates across microservices.

2. Keycloak Server: Keycloak server should be configured and running on port 8080. Keycloak is utilized for OAuth2-based authentication and authorization, ensuring secure access to the microservices via the API Gateway.

For a comprehensive implementation of the API Gateway, including detailed configuration and setup, refer to the GitHub repository: GitHub Link

3) Fault Tolerance (Circuit Breaker)

A microservices application anticipates failures as it scales. As the application expands, the likelihood of service failures increases, necessitating robust fault tolerance mechanisms. The circuit breaker pattern is crucial for detecting and handling unresponsive services, thereby preventing cascading failures throughout the application. Resilience4j, a modern resilience library, replaces Netflix Hystrix in contemporary microservices architectures.

It offers comprehensive fault tolerance features including circuit breakers, fallback strategies, and failure isolation. These capabilities ensure that even when services fail, the application can gracefully degrade functionality, maintaining overall stability and reliability.

Press enter or click to view image in full size

Implementation Explanation

In the provided implementation, Resilience4j is integrated into the OrderController to ensure fault tolerance for the placeOrder method, which interacts with other microservices like the inventory service.

1. Dependency: To use Resilience4j in your Spring Boot application, include the following dependency in your pom.xml:

Press enter or click to view image in full size

2. Circuit Breaker: The @CircuitBreaker annotation applied to the placeOrder method specifies the circuit breaker pattern. It monitors the number of failures and opens the circuit after reaching a threshold, preventing further calls until the service is deemed healthy again.

Press enter or click to view image in full size

3. Fallback Method: The fallbackMethod is defined to provide an alternative response if the circuit breaker opens, preventing a total failure.

In the placeOrder method, @CircuitBreaker(name = "inventory", fallbackMethod = "fallbackMethod") specifies that if the inventory circuit breaker triggers, the fallbackMethod should be executed.

4. Time Limiter: The @TimeLimiter annotation ensures that calls to the placeOrder method are completed within a specified time frame, failing fast if the time limit is exceeded.

Configuration:

Press enter or click to view image in full size

5. Retry: The @Retry annotation allows retrying the operation a specified number of times before considering it as failed.

Configuration:

Press enter or click to view image in full size

These properties configure the retry mechanism to attempt the operation up to 3 times with a 5-second wait between attempts.

For a comprehensive implementation of the Fault Tolerance, including detailed configuration and setup, refer to the GitHub repository: Github link

4) Broker Integration

In a microservices architecture, it’s crucial to have efficient and reliable communication between services. One common approach to achieve this is by using a message broker. In this implementation, Kafka is used to facilitate communication between the Order Service and the Notification Service.

Order Service

The Order Service is responsible for placing orders and publishing events when an order is successfully placed.

Get Habtamu Tesfie’s stories in your inbox

Join Medium for free to get updates from this writer.

Dependencies:

Press enter or click to view image in full size

Implementation (OrderService)

Placing an Order: The placeOrder method handles the logic of placing an order, including checking inventory and saving the order to the repository.

Publishing Events: When an order is successfully placed, an OrderPlacedEvent is published using applicationEventPublisher.

Complete implementation link: link

Notification Service

The Notification Service is responsible for processing the events published by the Order Service. When an order is placed, the Notification Service listens for the OrderPlacedEvent and sends a notification email.

Dependencies:

Press enter or click to view image in full size

Configuration (application.properties):

Press enter or click to view image in full size

Implementation:

The Notification Service consists of three components:

  • Main Application Class: This class starts the Notification Service and listens to Kafka messages.
  • Email Notification Service: This service handles the logic of processing the notification and sending an email.
  • Email Service: This service is responsible for sending the actual email.

Main Application Class (NotificationServiceApplication):

Listening to Kafka Messages: The handleNotification method is annotated with @KafkaListener to listen for messages on the notificationTopic. When a message is received, it is passed to the EmailNotificationService for processing.

Email Notification Service (EmailNotificationService):

Processing Notifications: The handleNotification method processes the received OrderPlacedEvent and sends an email notification using the EmailService.

In this setup:

  • Order Service: publishes OrderPlacedEvent to a Kafka topic when an order is placed.
  • Notification Service: listens to the Kafka topic for OrderPlacedEvent messages and processes the event by sending a notification email.

This integration ensures decoupled and scalable communication between the Order Service and the Notification Service using Kafka as the message broker.

For a comprehensive implementation of the order service, including detailed configuration and setup, refer to the GitHub repository: Github link

For a comprehensive implementation of the notification service, including detailed configuration and setup, refer to the GitHub repository: Github link

5) Distributed Tracing System Integration

Distributed tracing is a critical component in modern microservices architectures. It allows you to trace the path of a request as it traverses through various services, providing visibility into the interaction and performance of each service. This helps in identifying bottlenecks, debugging issues, and ensuring that all services in the architecture are working harmoniously.

Key benefits of distributed tracing include:

  • Performance Monitoring: Identify slow services and performance bottlenecks.
  • Error Tracking: Detect where errors occur within the service chain.
  • Dependency Analysis: Understand service dependencies and interactions.
  • Root Cause Analysis: Quickly pinpoint the root cause of issues in a distributed environment.

Zipkin is an open-source distributed tracing system that helps gather timing data needed to troubleshoot latency problems in service architectures. It manages both the collection and lookup of this data. Check out more about Zipkin on link

Local Setup of Zipkin: check the link zipkin

Adding Dependencies and Configuration

To integrate distributed tracing into all of the microservices, including the API Gateway and service discovery, you need to add the necessary dependencies and configuration properties. This example uses Micrometer and Brave for tracing and Zipkin for tracing data collection and visualization.

Dependencies:

Add the following dependencies to your pom.xml file in each microservice:

Press enter or click to view image in full size

Configuration:

Add the following properties to the application.properties file in each microservice:

Press enter or click to view image in full size

These properties configure the tracing system to report to Zipkin and ensure that all traces are sampled (for development and debugging purposes, a sampling probability of 1 means 100% of traces are collected).

Verifying Tracing Setup

After setting up tracing in all microservices, service discovery and the API Gateway:

  • Deploy and Start Services: Ensure all services are running.
  • Generate Traffic: Interact with your application to generate some traffic.
  • Check Zipkin: Open Zipkin UI at http://localhost:9411 to visualize traces.

You should see traces representing requests flowing through your microservices. Each trace will show spans that correspond to individual services and their respective operations.

6) Constant Monitoring and Alerting

Constant monitoring and alerting are essential components of managing a microservices architecture. They provide real-time insights into the state of your services, allowing you to detect and address issues promptly. A robust monitoring system helps track resource usage (such as disk utilization, memory consumption, and CPU utilization) and alerts you to any anomalies. Tools like Prometheus and Grafana, are commonly used for monitoring and visualization in microservices environments.

Adding Dependencies and Properties

To enable monitoring and alerting across all services, including the API Gateway and service discovery, you need to add specific dependencies and configure properties.

Dependencies:

Add the following dependencies to pom.xml file in each microservice:

Press enter or click to view image in full size

Configuration:

Add the following properties to the application.properties file in each microservice:

Press enter or click to view image in full size

These configurations expose the Prometheus endpoint for each microservice, allowing Prometheus to scrape metrics from your applications.

Setting Up Prometheus and Grafana Using Docker: check out the link

Configure Grafana to Use Prometheus as a Data Source:

  • Open your browser and navigate to http://localhost:3000.
  • Log in with the default credentials (username: admin, password: admin).
  • Add a new data source:
  • Go to Configuration > Data Sources > Add data source.
  • Select Prometheus.
  • Set the URL to http://host.docker.internal:9090 and click Save & Test.

7) Containerization

Containerization provides an independent, isolated environment for microservices, bundling all dependencies needed to run each service. Containers run on a unified OS kernel, isolating themselves from the host environment and each other. This isolation offers significant flexibility when deploying applications across different computing environments, such as on-premises servers or managed cloud services like AWS or Azure.

Using containers ensures consistent environments from development to production, making applications more portable, scalable, and easier to manage.

Jib for Image Creation

Jib is a Maven and Gradle plugin designed for building optimized Docker and OCI images for Java applications. Unlike traditional methods that require Dockerfiles, Jib directly creates images during the build process, simplifying containerization.

Benefits of Jib:

  • No Docker Daemon Required: Jib does not require Docker to be installed locally, making it easier to use in environments without Docker.
  • Reproducible Builds: Jib produces the same image every time it is run, ensuring consistency across environments.
  • Optimized Layers: Jib automatically optimizes the image layers, which speeds up the build and push process by reusing layers when possible.
  • Integrated with Build Tools: As a plugin for Maven and Gradle, Jib integrates seamlessly into existing build pipelines.
  • Security: By avoiding Dockerfiles and the need for Docker, Jib reduces potential security risks associated with misconfigured Dockerfiles or privileged Docker daemons.

You can read more about Jib on link

Example Jib Configuration:

Here’s an example of how to configure Jib in your pom.xml:

Local Development and Testing Guide Using Docker Compose

Prerequisites:

Install Docker and Docker Compose: Ensure Docker and Docker Compose are installed on your system.

Steps to Run the Project

1. Clone the Docker Compose File: Clone the docker-compose.yml file from the repository to your local machine:

git clone https://github.com/HabtamuTesfie/microservices.git
cd microservices

2. Start Docker Compose: Run the following command to start all the services defined in the docker-compose.yml file:

docker-compose up -d

3. Verify Services are Running: Check the status of the containers to ensure they are running correctly:

docker-compose ps

4. Access Keycloak: Keycloak is configured to run on port 8080. Access it via:

http://localhost:8080

Default credentials are:

  • Username: admin
  • Password: admin

Next Steps

1. Create a Realm:

  • On the top left of the dashboard, hover over the drop-down labeled “Master”.
  • Click the “Add realm” button.
  • Name the realm “spring-boot-microservices-realm”.
  • Click the “Create” button.
Press enter or click to view image in full size

2. Create a Client:

  • Navigate to “Realm Settings” in the left panel under the “Configure” section.
  • Click on “Clients” under “Realm Settings”.
  • Click the “Create” button on the top right.
  • Enter “spring-cloud-client” as the Client ID.
Press enter or click to view image in full size

3. Configure the Client:

  • Change “Access Type” from “Public” to “Confidential”.
  • Disable “Standard Flow Enabled” and “Direct Access Grants Enabled”.
  • Enable “Service Accounts Enabled”.
  • Click “Save” at the bottom.
Press enter or click to view image in full size

4. Get Client Secret:

  • Go to the “Credentials” tab.
  • Copy the Secret and save it for future use in OAuth2 requests with Postman or frontend applications.
Press enter or click to view image in full size

Verify Service Endpoints

Tip: Before testing any endpoint ensure “keycloak” is added to your local hosts file by mapping 127.0.0.1 keycloak in /etc/hosts (Linux/Mac) or C:\Windows\System32\drivers\etc\hosts (Windows).

Press enter or click to view image in full size

Common Postman Configuration

Step 1: Open Postman and Set Up the Request

  • Open Postman and create a new request.
  • Set the request type (GET, POST, etc.) according to the endpoint.
  • Enter the request URL (adjust based on endpoint, e.g., http://localhost:8181/api/product).

Step 2: Configure Authorization

  • Click on the Authorization tab.
  • Select OAuth 2.0 from the Type dropdown.
  • Set the “Add authorization data to” field to “Request Headers”.

Step 3: Configure the OAuth 2.0 Token

1. Fill in the following details in the “Configure New Token” section:

Press enter or click to view image in full size

2. Click on “Get New Access Token” to retrieve the access token.

3. Then click on “Proceed”.

4. Once the token is retrieved, click on “Use Token”.

Step 4: Endpoint-Specific Configuration

1. Create Product:

Method: POST

Endpoint: http://localhost:8181/api/product

Request Body Example:

{
"name": "iphone_13",
"description": "iphone_13 description",
"price": 1000000
}

2. Get Product

Method: GET

Endpoint: http://localhost:8181/api/product

Response Example:

[
{
"id": "667bde399291d3755f7c923a",
"name": "iphone_13",
"description": "iphone_13 description",
"price": 1000000
}
]

3. Create Order

Method: POST

Endpoint: http://localhost:8181/api/order

Request Body Example:

{
"orderLineItemsDtoList": [
{
"skuCode": "iphone_13",
"price": 100000,
"quantity": 20
}
]
}

Please check out the Github link for the docker-compose.yml file.

You can clone and test the complete repository on your local.

Here is the Github link

Originally published at scalable-microservices-implementation.blogspot.com on June 27, 2024.

--

--