Spring cloud micro-service architecture with Api Discovery and Api Gateway using Netflix Eureka

Mayank Yaduvanshi
8 min readApr 30, 2023

--

Microservice architecture is a software development approach that structures applications as a collection of small, independent services that communicate through APIs. Each microservice performs a specific business function and can be deployed and updated independently of the other services. This approach allows for greater flexibility, scalability, and resilience in complex applications.

image source — General Spring Cloud architecture [10]. | Download Scientific Diagram (researchgate.net)

In this tutorial we will create following services -

  1. Discovery Service — to register our feature services, so that locating and communicating with them becomes easy.
  2. Gateway service — entry point of all the feature services
  3. User service — a sample feature service

We will start by Creating an Wrapper or Parent maven module and will add each service module later one-by-one.

> NOTE: I am using Intellij IDEA IDE in this demo, you can use any IDE that supports Spring boot framework.

1. Create an empty Maven project

Go to **File -> New -> Project -> New Project**

Select language as **Java** and Build system as **Maven**.

Delete the src directory, as this is just a wrapper module, we will create new spring modules inside it for each micro-service.

1.1 Adding Spring boot dependency

Under the <Project> tag, add the spring-boot-starter-parent dependency as parent in pom.xml. As of writing this article latest available version of spring-boot-starter-parent is 3.0.2.

<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.7.6</version>
<relativePath/>
</parent>

2. Discovery Service

In a microservice architecture, services are often created and destroyed dynamically. This can make it difficult for a client service to locate other services it needs to interact with. A discovery service solves this problem by acting as a registry, allowing services to register themselves and providing a way for client services to discover and communicate with them. The discovery service can also provide load balancing and failover capabilities. In this tutorial, we will create a discovery service using Spring Cloud Netflix Eureka.

Create a new spring module from File → New → Module → Spring initializer

Location: path of parent module

Type: Maven

Packaging: War

After successful module creation, add discovery_service as child module in parent pom.xml

<modules>
<module>discovery_service</module>
</modules>

2.1 Remove unnecessary dependencies

When Creating module with Spring initializer it will add some dependencies that we don’t need, remove them from pom.xml

  • spring-boot-starter-web
  • spring-boot-starter-tomcat

2.2 Add Spring cloud dependencies

Add following properties in <properties> tag, we are using spring cloud version 2021.0.7 as latest available at time of writing this article.

<properties>
<java.version>17</java.version>
<spring-cloud.version>2021.0.7</spring-cloud.version>
</properties>

Now add the Eureka server dependency in <dependencies> section.

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

We also need to add Spring cloud dependency manager. Add following in <dependencyManagement> section.

<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>

2.3 Add server properties

We need add some properties in application.yml

server:
port: 8080

eureka:
client:
register-with-eureka: false
fetch-registry: false

Note: you can rename [application.properties](<http://application.properties>) to application.yml, spring will auto-detect the new file. .yml is more readable and looks clean.

add following code in application.yml file.

server:
port: 8080

eureka:
client:
register-with-eureka: false
fetch-registry: false

Port 8080 is added to test service locally. To test each service on local we need to give different port to each.

2.4 Enable eureka server

Add annotation @EnableEurekaServer to the application class.

...
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServiceApplication {
...
}

This is all we need to do to create a discovery service. Next we will create a Gateway service.

3. Gateway Service

In a microservice architecture, a gateway service acts as the entry point for all external client requests to the system. It routes requests to appropriate microservices and can handle tasks such as authentication and rate limiting. The gateway service can also provide additional functionality such as caching and load balancing.

Gateway service will be the only entry point, we only can access other service through it. We will configure routes and load balancing in it.

3.1 Create gateway service with spring initializer

Create a new module in parent module with spring initialzer

add module in parents pom.xml

<modules>
<module>discovery_service</module>
<module>gateway_service</module>
</modules>

Note: Follow the Steps 2.1 and 2.2 to remove unused deps and add spring cloud dependencies. Just do not add netflix-eureka-server instead in this we use netflix-eureka-client

3.2 Add gateway dependencies

Add the following deps in pom.xml

<!--Gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!--Eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

3.3 Add properties in application.yml

server:
port: 8081

spring:
application:
name: api-gateway
cloud:
gateway:
discovery:
locator:
enabled: true

eureka:
client:
register-with-eureka: false
fetch-registry: true
service-url:
defaultZone: http://localhost:8080/eureka
instance:
hostname: localhost
prefer-ip-address: true

Port: 8081

Gateway discovery : enabled

Register with eureka: false (we do not want to register gateway service to discovery, as it is not a feature service)

Default zone: this tells the gateway where is discovery service running

3.4 Enable discovery client

Add annotations to the main application class

...
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.reactive.config.EnableWebFlux;

@SpringBootApplication
@EnableDiscoveryClient
@EnableWebFlux
public class GatewayServiceApplication {
...
}

3.5 Creating Routes

Although currently we do not have any feature module/service, still in this step we create some routes for the service we will create in next steps.

Routes are basically a way of forwarding api requests to specific service using a path matching strategy.

Create new class GatewayConfig and add the below code -

...
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;

@Configuration
public class GatewayConfig {
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("user-service", r -> r.path("/api/users/**")
.uri("lb://user-service"))
.build();
}
}

This configuration matches the api request path that starts with /api/users and forward them to UserService that we will create in next section. We can add as many routes as required just by calling .route method, for example -

builder.routes()
.route("user-service", r -> r.path("/api/users/**")
.uri("lb://user-service"))
.route("admin-service", r -> r.path("/api/admin/**")
.uri("lb://admin-service"))
.build();

NOTE: Here route name user-service is the spring application name that defined in application properties file.

NOTE: If you get build errors like jakarta.servlet.ServletException cannot be resolved, then it can be resolved by adding servlet-api dependency in pom.xml for that service.

<!--Servlet api-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>compile</scope>
</dependency>

That’s all we need to do to create Gateway, in next section we will create a feature service, register it in discovery and access it through gateway.

4. Feature service (User service)

In micro-service architecture we create a separate service for each feature/module of our app, for example service A, service B and so on.

We register each feature service in Discovery and access them through the Gateway.

4.1 Create spring module with spring initializer

4.2 Remove unnecessary dependencies

from pom.xml remove the following dependencies

  • spring-boot-starter-tomcat

4.3 Add spring cloud dependencies

add following dependencies to pom.xml of user-service

<properties>
...
<spring-cloud.version>2021.0.7</spring-cloud.version>
</properties>

<dependencies>
...
<!--Web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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>

4.4 Adding properties to application.yml

server:
port: 9001
servlet:
context-path: /api/users

spring:
application:
name: user-service
mvc:
pathmatch:
matching-strategy: ant_path_matcher
eureka:
client:
service-url:
defaultZone: <http://localhost:8080/eureka>
instance:
hostname: localhost
prefer-ip-address: true

NOTE: Notice the context-path and application name, these are same as we used to add in Gateway routes in step 3.5

This is all the configuration we need to register this service with Discovery and make accessible through the gateway.

Next we will create an API in user service and check if we can call it through the gateway, so lets create an controller -

@RestController
public class UserController {

@GetMapping
public ResponseEntity<String> getUsers() {
return ResponseEntity.ok("These are all the users");
}
}

5. Running the services

Now its time to run all the services and see if all works as expected.

If you are using Intellij Idea, you can go to services tab and start each service one by one -

Make sure there are no errors in console of each service -

Once all the service started, we are ready to send the request.

6. Verifying the services

After running all services, we can check their statuses by opening the endpoint [<http://localhost:8080>](<http://localhost:8080>) in the browser -

as see in the picture our user-service is registered with Eureka and its status is UP. Now you can just stop the user-service and its status becomes DOWN or it will get un-registered.

Next — we will send requests to user-service, but through the api-gateway

Examine this Postman request, we have sent a GET request to [<http://localhost:8081/api/users>](<http://localhost:8081/api/users>) and it returned the 200 OK response.

Here Port 8081 is where the Gateway service is running and path /api/users is the path defined in user-service. Hence we can say that our request is passing through the api gateway.

Congratulations! On completing this tutorial.

you can find the full source code here— mayank042/spring_microservice_sample: Sample application demonstrating the spring boot micro-service architecture (github.com)

What’s Next -

In the future articles we will take this further and add following features -

  • Gateway filter — with filter we can validate in-coming requests and take actions accordingly.
  • Authentication — we can secure our all feature services with single authentication service and using gateway filter.
  • Inter service communication — we can call services internally in a clean micro-service way.

Stay up-to-date with me for upcoming exciting articles.

--

--

Mayank Yaduvanshi

Full Stack Software Developer, Experienced in Angular, Flutter, Spring boot, NodeJs, MySQL, MongoDB, AWS, Google Cloud, Docker, Git, JIRA.