Mastering Microservices: Inter - Service Communication using Spring Cloud Feign Client Part- 1

πŸš€ Welcome to Blog on Implementing the Spring Cloud OpenFeign Client in the Microservice Banking Application! πŸŒπŸ’Έ

Embark on a journey to master the intricacies of OpenFeign in implementing a microservices-driven banking system. Whether you're a seasoned developer or a tech enthusiast, delve into practical guidance and an in-depth exploration of constructing a robust microservices application.

Let's delve into the world of seamless inter-service communication! πŸš€

If you are new to the Spring Cloud OpenFeign Client, navigate to the Spring Boot Tutorial: Spring Cloud Feign Client article and delve into its contents for exploration🀯.

Development

Now, move to your favorite IDE or to the Spring Initializer and get the following dependencies add them to our User Service project that we created in the Mastering Microservices: Implemenatation of User Service article.

Now, what are we gonna use?

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

We will be having some new layers to implement the Spring Cloud OpenFeign client.

Model Layer

We will craft a class named Account in the package labeled model.external. This class will be instrumental in capturing the data retrieved from the Account Service.

Account.java

package org.training.user.service.model.external;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Account {

    private Long accountId;

    private String accountNumber;

    private String accountType;

    private String accountStatus;

    private BigDecimal availableBalance;

    private Long userId;
}

External Layer

Now, we will construct an interface named AccountService within the external package. This interface will serve as the conduit for fetching data from other Account service.

AccountService.java

package org.training.user.service.external;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignClientProperties;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.training.user.service.model.external.Account;

@FeignClient(name = "account-service", configuration = FeignClientProperties.FeignClientConfiguration.class)
public interface AccountService {

    @GetMapping("/accounts")
    ResponseEntity<Account> readByAccountNumber(@RequestParam String accountNumber);
}

Service Layer

Now, add a method to retrieve a user by account number in the interface, if it hasn't been added to the UserService interface in the previous article, Mastering Microservices: Implemenatation of User Service.

UserService.java

UserDto readUserByAccountId(String accountId);

UserServiceImpl.java

package org.training.user.service.service.implementation;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.training.user.service.exception.EmptyFields;
import org.training.user.service.exception.ResourceConflictException;
import org.training.user.service.exception.ResourceNotFound;
import org.training.user.service.external.AccountService;
import org.training.user.service.model.Status;
import org.training.user.service.model.dto.CreateUser;
import org.training.user.service.model.dto.UserDto;
import org.training.user.service.model.dto.UserUpdate;
import org.training.user.service.model.dto.UserUpdateStatus;
import org.training.user.service.model.dto.response.Response;
import org.training.user.service.model.entity.User;
import org.training.user.service.model.entity.UserProfile;
import org.training.user.service.model.external.Account;
import org.training.user.service.model.mapper.UserMapper;
import org.training.user.service.repository.UserRepository;
import org.training.user.service.service.KeycloakService;
import org.training.user.service.service.UserService;
import org.training.user.service.utils.FieldChecker;

import javax.transaction.Transactional;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;
    private final KeycloakService keycloakService;
    private final AccountService accountService;

    private UserMapper userMapper = new UserMapper();

    @Value("${spring.application.success}")
    private String responseCodeSuccess;

    @Value("${spring.application.not_found}")
    private String responseCodeNotFound;

    @Override
    public Response createUser(CreateUser userDto) {
        .....
    }

    @Override
    public List<UserDto> readAllUsers() {
        .....
    }

    @Override
    public UserDto readUser(String authId) {
        .....
    }

    @Override
    public Response updateUserStatus(Long id, UserUpdateStatus userUpdate) {
        .....
    }

    @Override
    public UserDto readUserById(Long userId) {
        .....
    }

    @Override
    public Response updateUser(Long id, UserUpdate userUpdate) {
        .....
    }

    @Override
    public UserDto readUserByAccountId(String accountId) {

        ResponseEntity<Account> response = accountService.readByAccountNumber(accountId);
        if(Objects.isNull(response.getBody())){
            throw new ResourceNotFound("account not found on the server");
        }
        Long userId = response.getBody().getUserId();
        return userRepository.findById(userId)
                .map(user -> userMapper.convertToDto(user))
                .orElseThrow(() -> new ResourceNotFound("User not found on the server"));
    }
}

We will be only implementing readUserByAccountId(String accountId) method, since all the other methods are already implemented.

Controller Layer

Now, let's expose the method that we implemented now as API endpoint.

package org.training.user.service.controller;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.training.user.service.model.dto.CreateUser;
import org.training.user.service.model.dto.UserDto;
import org.training.user.service.model.dto.UserUpdate;
import org.training.user.service.model.dto.UserUpdateStatus;
import org.training.user.service.model.dto.response.Response;
import org.training.user.service.service.UserService;

import java.util.List;

@Slf4j
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @PostMapping("/register")
    public ResponseEntity<Response> createUser(@RequestBody CreateUser userDto) {
        .....
    }

    @GetMapping
    public ResponseEntity<List<UserDto>> readAllUsers() {
        .....
    }

    @GetMapping("auth/{authId}")
    public ResponseEntity<UserDto> readUserByAuthId(@PathVariable String authId) {
        ......
    }

    @PatchMapping("/{id}")
    public ResponseEntity<Response> updateUserStatus(@PathVariable Long id, @RequestBody UserUpdateStatus userUpdate) {
        ......
    }

    @PutMapping("{id}")
    public ResponseEntity<Response> updateUser(@PathVariable Long id, @RequestBody UserUpdate userUpdate) {
        ......
    }

    @GetMapping("/{userId}")
    public ResponseEntity<UserDto> readUserById(@PathVariable Long userId) {
        ......
    }

    @GetMapping("/accounts/{accountId}")
    public ResponseEntity<UserDto> readUserByAccountId(@PathVariable String accountId) {
        return ResponseEntity.ok(userService.readUserByAccountId(accountId));
    }
}

Note: Please don't try to run the application, since we need to add exception handling configuration.

Postman Collection

I'm using Postman to test the APIs, and I will be attaching the Postman collection below so that you can go through it.

Run In Postman

Conclusion

Thanks for reading our latest article on Mastering Microservices: Inter - Service Communication using Spring Cloud Feign Client Part- 1 with practical usage.

You can get source code for this tutorial from our GitHub repository.

Happy Coding!!!!😊

0
Subscribe to my newsletter

Read articles from Karthik Kulkarni directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Karthik Kulkarni
Karthik Kulkarni

CSE'23 Grad πŸŽ“ | Aspiring Java Developer 🌟 | Proficient in Spring, Spring Boot, REST APIs, Postman πŸ’» | Ready to Contribute and Grow πŸš€