Microgateways with Docker and Spring Boot


Posted on Tue, Jul 4, 2023 architecture docker rest springboot kubernetes gateway devops java typescript

Microgateways are a new way to build and manage APIs in microservices architectures. They are lightweight, distributed proxies that can be deployed alongside each microservice.

Microgateways are a new way to build and manage APIs in microservices architectures. They are lightweight, distributed proxies that can be deployed alongside each microservice.

This allows for fine-grained control over API traffic and makes it easier to implement security, rate limiting, and other policies. In this guide, we will show you how to build a microgateway using Docker, Spring Boot, and Gateways.

This guide is for developers who are interested in learning how to build microgateways. It is assumed that you have some basic knowledge of microservices architectures and Docker.

What is a Microgateway?

In the realm of modern application development, microservices have emerged as a powerful approach to building scalable and flexible systems. However, managing and securing these distributed microservices can be a complex endeavor.

This is where microgateways come into play, serving as a crucial component in the microservices architecture. In this section, we will delve into the definition and purpose of microgateways, explore their key features and functionalities, and compare them to traditional API gateways.

Detailed Overview of Microgateway ๐Ÿ”

Microgateways are lightweight and specialized gateways that act as intermediaries between client applications and the microservices within an application.

Their primary purpose is to provide a unified entry point for accessing and managing microservices. By offering a range of functionalities, microgateways enable efficient communication and enhance the security and performance of microservices-based applications.

Key Features and Functionalities of Microgateways

1. Request Routing โ†•๏ธ

Microgateways intelligently route incoming requests to the appropriate microservice based on predefined rules or policies. This routing mechanism ensures that each request is directed to the correct microservice for processing.

It can be based on various factors such as the request URL, headers, or content. The rules or policies can be defined based on specific criteria like load balancing, geographic location, or user-specific attributes.

2. Protocol Translation ๐Ÿ“

Microgateways enable seamless communication between clients and microservices by translating protocols, ensuring compatibility between different components.

They act as intermediaries that  can handle different communication protocols used by clients and microservices. For example, a microgateway can receive an HTTP request from a client and translate it into a protocol that a specific microservice understands, such as gRPC or AMQP.

This protocol translation capability allows clients and microservices to interact with each other without worrying about protocol differences.

3. Security Enforcement ๐Ÿ”

Microgateways play a crucial role in enforcing security measures to protect microservices from unauthorized access. They handle authentication, authorization, and encryption protocols, ensuring that only authorized requests are processed.

Microgateways authenticate clients and validate their credentials, such as API keys or access tokens, before forwarding requests to microservices.

4. Caching โœ…

Microgateways can cache responses from microservices, improving response times and reducing the load on backend services. When a microgateway receives a request, it first checks if the response for that request is already cached.

If the response is present in the cache and still valid, the microgateway can directly serve the response to the client without forwarding the request to the corresponding microservice. Caching reduces the overall response time as it eliminates the need for unnecessary backend service calls. It also reduces the load on microservices, enabling them to handle higher traffic efficiently.

Comparison with Traditional API Gateways

While both microgateways and traditional API gateways serve as intermediaries between clients and services, they differ in their scope and focus.

1. Granularity ๐Ÿฅถ

Microgateways offer a finer level of granularity, allowing for dedicated gateways for individual microservices or groups of related microservices. In contrast, traditional API gateways tend to cater to a broader set of functionalities and services.

2. Resource Consumption ๐Ÿฆพ

Microgateways are lightweight, consuming fewer resources and providing better performance for microservices-based architectures. Traditional API gateways may be more resource-intensive due to their broader feature set.

3. Specialized Functionality ๐Ÿ˜‡

Microgateways are purpose-built for microservices, offering specialized functionalities such as protocol translation and fine-grained security enforcement. Traditional API gateways, on the other hand, cater to a wider range of services and may lack certain microservices-specific features.

Microgateways play a vital role in enabling efficient communication, security, and performance optimization within microservices-based architectures. With their specialized functionalities and lightweight nature, microgateways offer a tailored solution for managing and securing microservices.

Understanding the key features and functionalities of microgateways allows developers to harness their potential and build robust and scalable systems.

Building the Microgateway

In this section, we will guide you through the process of building a microgateway using Docker and Spring Boot. A microgateway acts as a lightweight intermediary between clients and microservices, providing essential features like routing, security, and monitoring. By following the steps outlined below, you'll be able to create a robust microgateway for your application.

Step 1 : Choose a Gateway Framework ๐Ÿ‘พ

Before diving into development, it's crucial to select a suitable gateway framework that aligns with your project requirements. In this guide, we will use Netflix Zuul as our gateway framework of choice. Zuul is a popular option known for its scalability, routing capabilities, and seamless integration with Spring Cloud.

Step 2 : Define API Contracts and Routes ๐Ÿ“

Identify the microservices that will be exposed through the gateway and define the API contracts and routes. These contracts specify the endpoints, request/response formats, and any required transformations. Properly defining the API contracts ensures consistent communication between the gateway and the microservices it interacts with.

Let's consider an example where we have two microservices: User Service and Product Service. The User Service has an endpoint /users/{userId} to fetch user details, while the Product Service has an endpoint /products/{productId} to retrieve product information. We want to expose these endpoints through the microgateway.

Step 3 : Create a Docker Container for the Microgateway ๐Ÿณ

To ensure portability and ease of deployment, we'll package our microgateway in a Docker container. Begin by creating a new directory for your project and navigate into it.

Next, create a file named Dockerfile (with no extension) in the project directory and add the following content :

FROM openjdk:20-jdk
WORKDIR /mygateway
COPY target/mygateway-0.0.1-SNAPSHOT.jar mygateway-0.0.1-SNAPSHOT.jar.original
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "mygateway-0.0.1-SNAPSHOT.jar.original"]

We specify the base image as OpenJDK 20, set the working directory to /mygateway, copy the compiled microgateway JAR file (mygateway-0.0.1-SNAPSHOT.jar) into the container as mygateway-0.0.1-SNAPSHOT.jar.original, expose port 8080, and define the entrypoint command to run the microgateway. Save the Dockerfile and proceed to the next step.

Step 4 : Configure the Microgateway using Spring Boot ๐ŸŒฑ

Now, let's configure the microgateway using Spring Boot and Eureka. Create a new Spring Boot project using your preferred IDE or by running the following command in your project directory :

$ spring init --name=mygateway --dependencies=web,eureka-client mygateway

This command initializes a new Spring Boot project named my-microgateway with dependencies on web and eureka.

Next, open the src/main/resources/application.yml file and configure the Eureka proxy routes :

server:
  port: 8080

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

spring:
  application:
    name: eureka-server

eureka:
  instance:
    hostname: localhost
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  server:
    enable-self-preservation: false

spring:
  profiles:
    active: default

info:
  app:
    name: Eureka Server

management:
  endpoints:
    web:
      exposure:
        include: info

We define two routes, user-service and product-service in this configuration file, mapping the respective paths (/users/** and /products/**) to the URLs of the actual microservices.

Step 5 : Integrate Gateway Components with Microservices ๐Ÿ‘จ๐Ÿผโ€๐Ÿ’ป

To integrate the gateway components with the microservices, we need to create appropriate controllers and define the necessary mappings. Start by creating a new package named com.example.mygateway.controller in the src/main/java directory.

Within the controller package, create a new Java class named UserController with the following content :

package com.example.mygateway.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    @GetMapping("/users/{userId}")
    public String getUserDetails(@PathVariable String userId) {
        // Invoke User Service API or perform necessary operations
        return "User details for userId: " + userId;
    }
}

Similarly, create another Java class named ProductController in the same package :

package com.example.mygateway.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductController {
    @GetMapping("/products/{productId}")
    public String getProductDetails(@PathVariable String productId) {
        // invoke Product Service API or perform necessary operations
        return "Product details for productId: " + productId;
    }
}

These controller classes define the endpoints (/users/{userId} and /products/{productId}) that will be handled by the microgateway.

Update your pom.xml file with the codes below to install needed dependencies :

<?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.1.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>eureka-server</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>eureka-server</name>
	<description>eureka-server</description>
	<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>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</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>
	</build>

</project>

Step 6 : Build and Run the Microgateway ๐Ÿงฑ

To build the microgateway project, navigate to the project directory in your terminal and execute the following command :

$ ./mvnw clean package -DskipTests

This command will compile the project and generate the JAR file in the target directory.

Now, run the microgateway by executing the following command :

$ docker build -t mygateway .
$ docker run -p 8080:8080 mygateway

These commands build the Docker image for the microgateway and start a container running the microgateway on port 8080.

Step 7 : Test the Microgateway ๐Ÿงช

You can now test the microgateway by making requests to the exposed endpoints. Open your preferred API testing tool (e.g., Postman) and send a GET request to :

http://localhost:8080/users/{userId} or http://localhost:8080/products/{productId}, replacing {userId} and {productId} with the desired values.

The microgateway will forward the request to the appropriate microservice based on the defined routes, and you will receive the corresponding responses.

You have successfully built a microgateway using Docker and Spring Boot. You can further enhance the microgateway by implementing additional features such as security, monitoring, and rate limiting to meet your application's requirements.

Implementing Security and Authentication

Securing microservices is a critical aspect of building a microgateway. In this section, we will guide you through implementing security and authentication measures to protect your microservices and ensure that only authorized requests are allowed.

By following the steps below, you can enhance the security of your microgateway.

Step 1 : Securing Microservices with Authentication and Authorization ๐Ÿ”‘

Before implementing security in the microgateway, it's essential to ensure that your microservices are properly secured with authentication and authorization mechanisms.

This can include techniques such as OAuth 2.0, JSON Web Tokens (JWT), or basic authentication.

Let's consider an example where we use JWT for authentication. Begin by adding the necessary dependencies to your pom.xml file :

<dependencies>
    <!-- Other dependencies -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.2</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.2</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.11.2</version>
        <scope>runtime</scope>
    </dependency>
    <!-- Other dependencies -->
</dependencies>

Step 2 : Configuring Security Features in the Microgateway ๐Ÿ“œ

To configure security features in the microgateway, we can leverage Spring Security, a powerful security framework provided by Spring Boot.

Start by creating a new configuration class named SecurityConfig in the com.example.mygateway.config package.

package com.example.mygateway.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .antMatchers("/api/public/**").permitAll() // Public endpoints
            .anyRequest().authenticated();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("admin")
            .password(passwordEncoder().encode("password"))
            .roles("ADMIN");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

In this configuration class, we disable CSRF protection since we're using stateless authentication. We also specify that sessions should not be created, as JWT authentication is stateless by nature.

The configureGlobal method configures an in-memory user with the username "admin" and a password encoded with BCrypt. You can modify this method to integrate with your own user authentication system.

Step 3 : Implementing API Key Management and Rate Limiting โšก๏ธ

To further enhance security, you can implement API key management and rate limiting in your microgateway. This ensures that only authorized clients can access your microservices, and it helps prevent abuse and excessive traffic.

One popular library for API key management and rate limiting is Spring Cloud Gateway. You can add the necessary dependencies to your pom.xml file :

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

Next, create a configuration class named ApiKeyConfig in the com.example.mygateway.config package :

package com.example.mygateway.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;

@Configuration
public class ApiKeyConfig {

    @Value("${api.key.url}")
    private String apiKeyUrl;

    @Bean
    public GlobalFilter apiKeyFilter() {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String apiKey = request.getHeaders().getFirst("X-API-Key");

            if (apiKey == null || !isValidApiKey(apiKey)) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }

            return chain.filter(exchange);
        };
    }

    private boolean isValidApiKey(String apiKey) {
        // Perform validation against the API Key management service
        WebClient webClient = WebClient.create();
        return webClient.get()
                .uri(apiKeyUrl + "?apiKey=" + apiKey)
                .retrieve()
                .bodyToMono(Boolean.class)
                .block();
    }
}

In this configuration class, we define a global filter (apiKeyFilter) that intercepts incoming requests and checks for the presence and validity of an API key. The isValidApiKey method sends a request to the API Key management service (specified by the apiKeyUrl property) to validate the API key. You should modify this method to integrate with your own API Key management system.

These steps ensure that security measures like authentication, authorization, API key management, and rate limiting are implemented in your microgateway, providing a secure layer of protection for your microservices.

Deploying the Microgateway

After building and securing your microgateway, the next crucial step is deploying it to a production environment.

In this section, we will guide you through the process of deploying the microgateway using Docker and Kubernetes. By following the steps below, you can ensure a smooth deployment of your microgateway.

Step 1 : Containerize the Microgateway with Docker ๐Ÿ“ฆ

To make the deployment process more manageable and scalable, we will package the microgateway into a Docker container. Docker provides a convenient way to package applications with their dependencies into portable containers.

Start by creating a Dockerfile in the root directory of your microgateway project. Open a text editor and add the following content to the Dockerfile :

FROM openjdk:20-jdk
WORKDIR /mygateway
COPY target/mygateway-0.0.1-SNAPSHOT.jar mygateway-0.0.1-SNAPSHOT.jar.original
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "mygateway-0.0.1-SNAPSHOT.jar.original"]

Step 2 : Build and Push the Docker Image ๐Ÿ”จ

Now that we have a Dockerfile, we can build the Docker image for the microgateway. Open a terminal or command prompt, navigate to the root directory of your microgateway project, and execute the following commands :

$ docker build -t mygateway .

This command builds the Docker image using the Dockerfile in the current directory. Replace my-microgateway with a suitable name for your microgateway image.

After the image is built, you can push it to a container registry of your choice (e.g., Docker Hub, Amazon ECR, or Google Container Registry). Assuming you have set up the necessary credentials for your container registry, run the following command to push the image :

$ docker push mygateway

Lastly, create a docker-compose.yaml file and paste the codes below into it to be able to pull the needed images and run the gateway:

services:
  eureka-server:
    container_name: eurekaserver
    build:
      dockerfile: ./docker/eurekaserver.Dockerfile
    environment:
      SPRING_PROFILES_ACTIVE: docker
    ports:
      - "8761:8761"
    networks:
      - spring

  mygateway:
    container_name: mygateway
    build:
      dockerfile: ./docker/mygateway.Dockerfile
    environment:
      SPRING_PROFILES_ACTIVE: docker
    depends_on:
      - eureka-server
    ports:
      - "8080:8080"
    networks:
      - spring

networks:
  spring:
    driver: bridge

Run docker-compose up -d to start up docker-compsse script.

Step 3 : Deploy the Microgateway to Kubernetes โ˜๏ธ

Kubernetes is a powerful container orchestration platform that simplifies the deployment and management of containerized applications. We will now deploy the microgateway to a Kubernetes cluster.

Ensure that you have a running Kubernetes cluster and the kubectl command-line tool configured to access the cluster. If you don't have a cluster set up, you can use local Kubernetes solutions like Minikube or Docker Desktop with Kubernetes enabled for development and testing purposes.

Create a file named microgateway-deployment.yaml and add the following content :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mygateway-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mygateway
  template:
    metadata:
      labels:
        app: mygateway
    spec:
      containers:
        - name: clever_heyrovsky
          image: mygateway
          ports:
            - containerPort: 8080
apiVersion: v1
kind: Service
metadata:
  name: mygateway-service
spec:
  selector:
    app: mygateway
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
  type: LoadBalancer

In this YAML file, we define a Deployment and a Service for the microgateway. The Deployment ensures that a single replica of the microgateway is running, while the Service exposes the microgateway as a LoadBalancer service on port 80. Save the microgateway-deployment.yaml file.

To deploy the microgateway to your Kubernetes cluster, execute the following command :

$ kubectl apply -f microgateway-deployment.yaml

Kubernetes will create the necessary resources (Deployment and Service) based on the YAML file.

Step 4 : Access the Microgateway in Kubernetes ๐Ÿšช

Once the microgateway is deployed, you can access it using the external IP provided by the LoadBalancer service. Run the following command to retrieve the external IP :

$ kubectl get services

Look for the EXTERNAL-IP column for the my-microgateway-service. If the external IP is pending or not yet available, wait for a few moments and re-run the command.

Once you have the external IP, you can access your microgateway using that IP. Open a web browser or use an API testing tool and navigate to http://<EXTERNAL-IP>.

Congratulations! Youโ€™ve built, secured, and deployed your microgateway using Docker, Spring Boot, and Kubernetes!

You can download the project below :

microgateway-docker.zip513.8KB

Conclusion

We have explored how to build a microgateway using Docker, Spring Boot, and Gateways. We have seen how to create a simple gateway that can route requests to different microservices. We have also seen how to use Spring Boot to create microservices that can be exposed by the gateway. Finally, we have seen how to use Gateways to configure the routing of requests to microservices.

By following the steps in this guide, you will be able to build your own microgateway that can be used to route requests to microservices. This will help you to create a more scalable and resilient architecture for your applications.