02-06-2023 - Shai Zambrovski
In today’s rapidly evolving world of software development, building scalable and resilient applications has become a necessity.
Microservices architecture offers a solution by decomposing applications into smaller, independent services that can be developed, deployed, and scaled individually.
Two key components in the microservices landscape are Service Registry\Discovery
and API Gateway
.
Spring Cloud is a framework within the Spring ecosystem that provides a set of tools and libraries to simplify the development of distributed systems and microservices-based applications.
It aims to address common challenges in distributed computing, such as service discovery, configuration management, load balancing, and more.
Spring Cloud leverages popular technologies like Netflix OSS components (e.g., Eureka, Ribbon, Hystrix) and integrates them with Spring Boot to enable developers to easily build scalable, resilient, and cloud-native applications.
It promotes the use of microservices architecture and provides a consistent and opinionated approach to building distributed systems using Spring’s programming model.
An API gateway is a server or service that acts as an entry point for all client requests to a microservices architecture.
It provides a centralized point of control and management for APIs, allowing for features like authentication, rate limiting, request/response transformation, and routing to multiple services.
The API gateway acts as a single interface for clients, abstracting away the complexities of the underlying microservices and providing a unified and consistent API for clients to interact with.
Spring Cloud Gateway is a lightweight and highly customizable API gateway built on top of Spring Boot.
It provides a way to route and filter HTTP requests to backend services based on various criteria.
The gateway acts as a single entry point for all client requests, enabling centralized control and management of API traffic.
It supports dynamic routing, load balancing, and other advanced features to ensure reliability, scalability, and security of the microservices architecture.
Spring Cloud Gateway integrates seamlessly with other components of the Spring Cloud ecosystem, such as service discovery and configuration management, making it an ideal choice for building robust and resilient API gateways in distributed systems.
In a distributed system composed of numerous services, the ability to locate and communicate with these services is vital. Service registry and discovery play a crucial role in managing the dynamic nature of these distributed architectures.
A service registry is a central repository where services can register their location and metadata, allowing other services to discover and interact with them. It serves as a directory or catalog of available services within the system.
Service discovery, on the other hand, is the mechanism through which services can locate and connect to other services dynamically. By leveraging the service registry, services can query and retrieve the necessary information to establish communication with their dependencies.
Service registry and discovery provide scalability, fault tolerance, and flexibility in distributed systems by enabling seamless service-to-service communication without hard-coded dependencies. They facilitate dynamic updates and scaling of services, making it easier to add, remove, or replace services in the system.
Client service registry refers to the capability of a service or application to dynamically register itself with a service registry and provide its availability and metadata information for other services to discover and interact with.
Server service registry refers to the centralized repository or server that maintains the registry of available services, allowing other services to discover and communicate with them dynamically through a load balancer that has a well-known location.
Client-Side Service Discovery | Server-Side Service Discovery |
---|---|
Pros Client services have control over service discovery and can make dynamic routing decisions. Less dependence on external components or infrastructure. Client services can easily adapt to changes in service availability. |
Cons Centralized service registry provides a single source of truth for service information. Improved scalability and performance by offloading service discovery to dedicated components. Easy integration with other infrastructure components like load balancers or API gateways. |
Cons Increased complexity in client services due to the need for service discovery logic. Potential network overhead from querying the service registry for service information. Requires additional effort for handling service registry failures or inconsistencies. |
Cons Dependency on the availability and reliability of the central service registry. Possible performance impact due to the additional network hops to the service registry. May introduce a single point of failure if the service registry becomes unavailable. |
Spring Cloud Eureka is a service registry and discovery server that is part of the Spring Cloud framework. It is inspired by Netflix Eureka, which is an open-source project developed by Netflix as part of their Netflix OSS (Open Source Software) platform.
Spring Cloud Eureka implements client-side service discovery by providing a built-in client library that applications can use to register themselves with the Eureka server.
In this approach, each service instance (client) is responsible for registering itself with the Eureka server upon startup. The client sends heartbeats to the server to keep its registration information up to date.
Other services (clients) that need to consume a particular service can use the Eureka client library to discover the available instances of that service. The Eureka client library provides a simple API to query the Eureka server for service instances based on their registered service ID.
We will introducing the power of Spring Cloud API Gateway and Eureka using simple e-commerce web application built on-top of Docker Compose.
Our application is built with a microservices architecture
Spring Cloud Eureka server: Provides a service registry and discovery mechanism for seamless communication between microservices.
This is the pom.xml file for the Eureka server.
We use dependency management of spring-cloud and use only the eureka-server
package to acts as a service registry.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.8-SNAPSHOT</version>
<relativePath/>
</parent>
<groupId>io.shaikezam</groupId>
<artifactId>service-discovery</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service-discovery</name>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<finalName>service-discovery</finalName>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<id>netflix-candidates</id>
<name>Netflix Candidates</name>
<url>https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-candidates</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>
The provided YAML file configures the server port, eureka client host and application name:
spring:
application:
name: service-discovery
server:
port: 8001
eureka:
client:
service-url:
defaultZone: http://service-discovery:8001/eureka
We will set in our main function the@EnableEurekaServer
Spring annotation that enable the service to act as a Eureka server, allowing it to register other services and provide service discovery capabilities within a distributed system.
package io.shaikezam.servicediscovery;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class ServiceDiscoveryApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceDiscoveryApplication.class, args);
}
}
Spring Cloud API Gateway: Serves as a centralized entry point, enabling advanced routing, filtering, and rate limiting for improved security and performance and serve our static resources.
This is the pom.xml
file the APi Gateway.
We use dependency management of spring-cloud and use only the spring-cloud-starter-gateway
package to acts as a api-gateway and the spring-cloud-eureka-client
to register and discover other services.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.8-SNAPSHOT</version>
<relativePath/>
</parent>
<groupId>io.shaikezam.</groupId>
<artifactId>application</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>application</name>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<finalName>task-tracker-api-gateway</finalName>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<id>netflix-candidates</id>
<name>Netflix Candidates</name>
<url>https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-candidates</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>
The provided YAML file configures the server port, eureka server host and application name.
server:
port: 8000
spring:
application:
name: api-gateway
eureka:
client:
service-url:
defaultZone: http://service-discovery:8001/eureka
This Application class sets up a Spring Boot application with service discovery capabilities and configures routes for the Spring Cloud Gateway to forward requests to backend services based on defined path patterns:
@EnableDiscoveryClient
annotation enables the service to participate as a discovery client, allowing it to register with a service registry and discover other services.
The customRouteLocator
method is a bean definition that configures the routes for the Spring Cloud Gateway. It uses the RouteLocatorBuilder
to define routes based on incoming paths and rewrite them to backend service URLs using load-balanced (lb) URIs.
The example shows two routes: "product-service"
and "order-service"
, where the incoming paths "/webapi/products/"
and "/webapi/orders/"
are rewritten to "/api/v1/<API SUFFIX>"
and forwarded to the respective backend services.
For example /webapi/products/foo
will redirect to /api/v1/products/foo
of product-service
package io.shaikezam.application;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("product-service", r -> r.path("/webapi/products/**")
.filters(f->f.rewritePath("/webapi/(?<segment>.*)","/api/v1/${segment}"))
.uri("lb://product-service"))
.route("order-service", r -> r.path("/webapi/orders/**")
.filters(f->f.rewritePath("/webapi/(?<segment>.*)","/api/v1/${segment}"))
.uri("lb://order-service"))
.build();
}
}
Products Service: Handles operations related to products, such as retrieval, creation, and management.
Orders Service: Manages order-related functionalities, including order placement, processing, and tracking.
Both product-service
and order-service
are Spring boot
application that written with MVC pattern that expose API in the controller, a service layer for BL and repository that implemented by Spring JdbcTemplate
Also, each service has it’s own applicatiom.yml file that contains the service port, application name and eureka server host.
Also, each service has it’s own applicatiom.yml
file that contains the service port, application name and eureka server host.
MariaDB Service: Ensures persistent data storage for the application, maintaining the integrity and reliability of the data.
phpMyAdmin Service: Offers a user-friendly interface to manage and monitor the application’s MariaDB database for easy debugging and data inspection.
This is the docker-compsoe.yml
file (we open the ports for all service for debug purpose).
Feel free to change password (I choose to use Admin@Admin for DB & phpMyAdmin).
version: "3.9"
services:
api-gateway:
container_name: api-gateway
build: api-gateway
ports:
- "8000:8000"
networks:
- product-network
service-discovery:
container_name: service-discovery
build: service-discovery
ports:
- "8001:8001"
networks:
- product-network
product-service:
container_name: product-service
build: product-service
ports:
- "8002:8002"
networks:
- product-network
depends_on:
- db
order-service:
container_name: order-service
build: order-service
ports:
- "8003:8003"
depends_on:
- db
networks:
- product-network
db:
container_name: db
build: db
restart: always
environment:
MARIADB_ROOT_PASSWORD: admin
MARIADB_USER: admin
MARIADB_PASSWORD: admin
MARIADB_DATABASE: application
ports:
- "3306:3306"
networks:
- product-network
phpmyadmin:
image: phpmyadmin/phpmyadmin
container_name: phpmyadmin
restart: always
ports:
- "8004:80"
environment:
PMA_HOST: db
PMA_PORT: 3306
MARIADB_ROOT_PASSWORD: admin
networks:
- product-network
networks:
product-network:
external: false
name: product-network
The above demo can be run after you clone my repository, just execute build.sh file and navigate to localhost:8000/index.htm
You can also navigate to eureka dashboard via localhost:8001
.