Spring Boot Microservices — Part5— OpenFeign

In this example, I will show you how to make the use of OpenFeign library to make the inter service communication calls.

invenory-service

pom.xml

<?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>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>inventory-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>inventory-service</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.2</spring-cloud.version>
</properties>


<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

Inventory.java

package com.example.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Table(name = "inventory")
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Inventory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String skuCode;
private Integer stock;
}

InventoryController.java

package com.example.controller;

import com.example.model.Inventory;
import com.example.repository.InventoryRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Optional;

@RestController
@RequestMapping("/api/inventory")
@RequiredArgsConstructor
@Slf4j
public class InventoryController {
private final InventoryRepository inventoryRepository;

@GetMapping("/{skuCode}")
public Boolean isInStock(@PathVariable("skuCode") String skuCode) {
log.info("Checking stock for product with skucode - " + skuCode);

Optional<Inventory> optionalInventory = inventoryRepository.findBySkuCode(skuCode);
if(!optionalInventory.isPresent()){
new RuntimeException("Cannot Find Product by sku code " + skuCode);
}
return optionalInventory.get().getStock() > 0;
}
}

MainApp.java

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class InventoryServiceApplication {

public static void main(String[] args) {
SpringApplication.run(InventoryServiceApplication.class, args);
}
}

application.properties

#MySQL DB
spring.datasource.username=root
spring.datasource.password
=password

# Need to look at the ways to

spring.application.name=inventory-service
server.port
=0
eureka.instance.instance-id
=${spring.application.name}:${random.uuid}
#spring.config.import=optional:configserver:http://localhost:8888?fail-fast=true&max-attempts=10&max-interval=1500
spring.config.import=optional:configserver:http://localhost:8888?fail-fast=true&max-attempts=10&max-interval=1500&vault://secret/order-service

management.endpoints.web.exposure.include
=*

#RabbitMQ
spring.rabbitmq.host=localhost
spring.rabbitmq.port
=5672
spring.rabbitmq.username
=guest
spring.rabbitmq.password
=guest

db.sql

CRAETE DATABASE `inventory-service`.`inventory`;CREATE TABLE `inventory-service`.`inventory` (
`id` bigint NOT NULL AUTO_INCREMENT,
`sku_code` varchar(255) DEFAULT NULL,
`stock` int DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `inventory-service`.`inventory` (`id`, `sku_code`, `stock`) VALUES ('1', 'IPHONE_12_RED', '100');
INSERT INTO `inventory-service`.`inventory` (`id`, `sku_code`, `stock`) VALUES ('2', 'IPHONE_12_GREY', '0');

api-gateway-service

pom.xml

<?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>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>api-gateway-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>api-gateway-service</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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>

application.properties

spring.application.name=api-gateway
spring.cloud.gateway.discovery.locator.enabled
=true
spring.cloud.gateway.discovery.locator.lower-case-service-id
=true

eureka.instance.instance-id
=${spring.application.name}-${random.uuid}
spring.cloud.loadbalancer.ribbon.enabled
=false

spring.cloud.gateway.routes[0].id
=product-service
spring.cloud.gateway.routes[0].uri
=lb://product-service
spring.cloud.gateway.routes[0].predicates[0]
=Path=/api/product

spring.cloud.gateway.routes[1].id
=order-service
spring.cloud.gateway.routes[1].uri
=lb://order-service
spring.cloud.gateway.routes[1].predicates[0]
=Path=/api/order

MainApp.java

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class ApiGatewayServiceApplication {

public static void main(String[] args) {
SpringApplication.run(ApiGatewayServiceApplication.class, args);
}
}

order-service

pom.xml

<?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>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>order-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>order-service</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.2</spring-cloud.version>
</properties>


<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>

InventoryClient.java

package com.example.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(name = "inventory-service")
public interface InventoryClient {
@GetMapping("/api/inventory/{skuCode}")
Boolean checkStock(@PathVariable String skuCode);
}

OrderController.java

package com.example.controller;

import com.example.client.InventoryClient;
import com.example.dto.OrderDto;
import com.example.model.Order;
import com.example.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;
import java.util.concurrent.ExecutorService;

@RestController
@RequestMapping("/api/order")
@RequiredArgsConstructor
@Slf4j
public class OrderController {
private final OrderRepository orderRepository;
private final InventoryClient inventoryClient;
// private final Resilience4JCircuitBreakerFactory circuitBreakerFactory;
// private final StreamBridge streamBridge;
// private final ExecutorService traceableExecutorService;

@PostMapping
public String placeOrder(@RequestBody OrderDto orderDto) {
boolean isProductInStock = orderDto.getOrderLineItemsList()
.stream()
.allMatch(orderLineItems -> inventoryClient.checkStock(orderLineItems.getSkuCode()));

if (isProductInStock) {
Order order = new Order();
order.setOrderLineItems(orderDto.getOrderLineItemsList());
order.setOrderNumber(UUID.randomUUID().toString());

orderRepository.save(order);
return "Order Placed Successfull!";
}else{
return "Order Failed !, One of the products in the order is not in the stock!";
}
}

}

OrderDto.java

package com.example.dto;

import com.example.model.OrderLineItems;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class OrderDto {
private List<OrderLineItems> orderLineItemsList;
}

Order.java

package com.example.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.util.List;

@Entity
@Table(name = "t_orders")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String orderNumber;
@OneToMany(cascade = CascadeType.ALL)
private List<OrderLineItems> orderLineItems;
}

OrderLineItems.java

package com.example.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.math.BigDecimal;

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Entity
@Table(name = "order_line_items")
public class OrderLineItems {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String skuCode;
private BigDecimal price;
private Integer quantity;
}

OrderRepository.java

package com.example.repository;

import com.example.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderRepository extends JpaRepository<Order, Long> {
}

MainApp.java

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class OrderServiceApplication {

public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}

}

application.properties

#MySQL DB
spring.datasource.username=root
spring.datasource.password
=password

# Need to look at the ways to

spring.application.name=order-service
server.port
=0
eureka.instance.instance-id
=${spring.application.name}:${random.uuid}
#spring.config.import=optional:configserver:http://localhost:8888?fail-fast=true&max-attempts=10&max-interval=1500
spring.config.import=optional:configserver:http://localhost:8888?fail-fast=true&max-attempts=10&max-interval=1500&vault://secret/order-service

management.endpoints.web.exposure.include
=*

#RabbitMQ
spring.rabbitmq.host=localhost
spring.rabbitmq.port
=5672
spring.rabbitmq.username
=guest
spring.rabbitmq.password
=guest

GET- http://localhost:8080/api/order, since DB has 0 in the stock

GET — http://localhost:8080/api/order

--

--

--

Java Developer and enthusiast

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Best format for drum and percussive loops: RX2

Automated Integration Test Branch Patterns

Mock Your API Responses with Random Key

4 architecture options for your multitenant analytics solution

Write an Android Studio Plugin Part 4: Jira Integration

The Tools I Use to Build Digital Products for the Web

A screenshot of a terminal running Neovim with a Typescript code file in the current buffer.

The 7 Universal Laws of the Universe and How to Use Them to Create a Better Life

Going grey with git : basic commands

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
PA

PA

Java Developer and enthusiast

More from Medium

Spring Boot Microservices — Part2— config-server

Microservices (Spring boot) centralised logging with ELK Stack and Kafka.

Java, ElasticSearch and Spring Boot Framework tutorial

MICROSERVICES WITH JAVA SPRING