Spring Boot: Microservice Enhanced

In early series of Spring Boot demo we have gone through basics of booting an application using spring boot. In this blog i’ll show you what is microservice pattern and how actually we can implement.

When microservice will be useful

Below is the list of scenario where microservice is useful.

  • Divide whole application in workable modules.
  • Each module works individually.
  • We can deploy it independently.
  • Provide balancing among multiple web services.
  • Adding of new service will not affect others as it is independently working and loosely coupled.
  • Scale your application across horizons.

Goal

Goal of this demo is

  • Develop 3 different microservices.
  • Deploy all of them in separate environment (for Local we are running on separate port).
  • To know how one service will talk with another.

Use case

Here we are creating 3 different microservices. Technically we are creating 3 different project and also will run on different port.

  • CartService: Managing Cart Operations, ideally contains mapping between Product and User (Customer)
  • ProductService: Expose services related to product.
  • UserService: Expose services related to user.

Let’s look all of them in detail.
You can clone repository from below git.
GitHub Repo: https://github.com/yogeshmprajapati/kode12-spring-boot.git

MicroService 1: Product Service

Git Module: spring-boot-microservice-enhanced-product
Runs on: Port 8081

ProductVO.java

package com.kode12.vo;

import java.io.Serializable;

public class ProductVO implements Serializable{
   private long id;
   private String name;

  // getters and setters

}

ProductService.java

Service class to serve product detail, here we have hardcoded detail for product 1 and 2.

package com.kode12.service;

import com.kode12.vo.ProductVO;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class ProductService {

   public ProductVO getById(int productId) {

       ProductVO productVO = new ProductVO();

       switch (productId) {
           case 1:
               productVO.setId(1);
               productVO.setName("Apple MacBook Air MMGF2HN/A 13.3-inch Laptop");
               break;
           case 2:
               productVO.setId(2);
               productVO.setName("Samsung T3 Portable 500GB USB 3.0 External SSD (MU-PT500B/AM)");
               break;
       }
       return productVO;

   }
}

ProductController.java

Controller to expose product data.

package com.kode12.controller;

import com.kode12.service.ProductService;
import com.kode12.vo.ProductVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/product")
public class ProductController {

   @Autowired
   ProductService productService;

   @RequestMapping("/{productId}")
   public ProductVO getById(@PathVariable("productId") int productId) {

       return productService.getById(productId);
   }

}

SpringBootMicroserviceEnhancedProductApplication.java

Class with main method to start an application.

package com.kode12;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootMicroserviceEnhancedProductApplication {

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

application.properties

server.port=8081

Run MicroService 1 (Product Service)

Url 1: http://localhost:8081/product/1
Use: to get product by id 1.
Output:

{
    "id": 1,
    "name": "Apple MacBook Air MMGF2HN/A 13.3-inch Laptop"
}

Url 2: http://localhost:8081/product/2
Use: to get product by id 2.
Output:

{
    "id": 2,
    "name": "Samsung T3 Portable 500GB USB 3.0 External SSD (MU-PT500B/AM)"
}

MicroService 2: User Service

Git Module: spring-boot-microservice-enhanced-user
Runs on: Port 8082

UserVO.java

package com.kode12.vo;

import java.io.Serializable;

public class UserVO implements Serializable {
   private long id;
   private String name;

   public UserVO() {
   }

   public UserVO(long id) {
       this.id = id;
   }

  // getters and setters

}

UserService.java

Service class to serve user detail, here we have hardcoded detail for user 1.

package com.kode12.service;

import com.kode12.vo.UserVO;
import org.springframework.stereotype.Service;

@Service
public class UserService {

   public UserVO getById(int userId) {

       UserVO userVO = new UserVO();

       switch (userId) {
           case 1:
               userVO.setId(1);
               userVO.setName("Yogesh P");
               break;
       }
       return userVO;

   }
}

Controller to expose user data.

package com.kode12.controller;

import com.kode12.service.UserService;
import com.kode12.vo.UserVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {

   @Autowired
   UserService userService;

   @RequestMapping("/{userId}")
   public UserVO getById(@PathVariable("userId") int userId) {

       return userService.getById(userId);
   }

}

SpringBootMicroserviceEnhancedUserApplication.java

Class with main method to start an application.

package com.kode12;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootMicroserviceEnhancedUserApplication {

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

application.properties

server.port=8082

Run MicroService 2 (User Service)

Url 1: http://localhost:8081/user/1
to get user detail by id 1.
Output:

{
    "id": 1,
    "name": "Yogesh P"
}

MicroService 3: Cart Service

Git Module: spring-boot-microservice-enhanced-cart
Runs on: Port 8080

CartVO.java

package com.kode12.vo;

public class CartVO {
   private long id;
   private UserVO user;
   private ProductVO product;

   public CartVO() {
   }

   public CartVO(long id, UserVO user, ProductVO product) {
       this.id = id;
       this.user = user;
       this.product = product;
   }

   // getters and setters

}

ProductVO.java

Same as Microservice 1’s ProductVO.java

UserVO.java

Same as Microservice 2’s UserVO.java

CartService.java

Service class to serve cart detail, here we have hard coded following mapping

Order#1 {User#1, Product#1}
Order#2 {User#1, Product#2}

package com.kode12.service;

import com.kode12.vo.CartVO;
import com.kode12.vo.ProductVO;
import com.kode12.vo.UserVO;
import org.apache.catalina.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class CartService {

   @Autowired
   RestTemplate restTemplate;

   public List<CartVO> getAll(){
       List<CartVO> list = new ArrayList<CartVO>();

       // Order#1 -> User#1, Product#1
       // Order#1 -> User#1, Product#2

       Map<String, Object> userUriVariables = new HashMap<String, Object>();
       userUriVariables.put("userId", 1);
       ResponseEntity<UserVO> userResponseEntity = restTemplate.getForEntity("http://localhost:8082/user/{userId}", UserVO.class, userUriVariables);

       ProductVO productVO1 = restTemplate.getForEntity("http://localhost:8081/product/1", ProductVO.class).getBody();
       ProductVO productVO2 = restTemplate.getForEntity("http://localhost:8081/product/2", ProductVO.class).getBody();

       list.add(new CartVO(1, userResponseEntity.getBody(), productVO1));
       list.add(new CartVO(2, userResponseEntity.getBody(), productVO2));

       return list;

   }
}

Here we have used RestTemplate to talk with other microservices.

For Ex:
ProductVO productVO1 = restTemplate.getForEntity("http://localhost:8081/product/1", ProductVO.class).getBody();

The code above is used to request product service to provide product detail having product id is 1.

Run MicroService 3 (Cart Service)

Url: http://localhost:8080/cart/getAll
Use: to get all cart records.
Output:

[
    {
        "id": 1,
        "user": {
            "id": 1,
            "name": "Yogesh P"
        },
        "product": {
            "id": 1,
            "name": "Apple MacBook Air MMGF2HN/A 13.3-inch Laptop"
        }
    },
    {
        "id": 2,
        "user": {
            "id": 1,
            "name": "Yogesh P"
        },
        "product": {
            "id": 2,
            "name": "Samsung T3 Portable 500GB USB 3.0 External SSD (MU-PT500B/AM)"
        }
    }
]

Here User Name and Product Name for each user and product are not available in cart service, actually it will be from other microservices.

Issue

After looking into code you can have a doubt that here we have hard coded urls like http://localhost:8081 or http://localhost:8082 and in real time environment it may vary, so how to handle this thing in better way ?

The good answer of this is we need to use some discovery service like Eureka. It is used for middle tier load balancing and having multiple useful feature, i’ll show you how to work with eureka in upcoming posts.

Share current post by copy: https://goo.gl/5oyuwp
Happy Learning!
:)

Thanks,
Yogesh P