Rental Application (React & Spring boot Microservice) - 18 : auth-service(5: Feign Client)
#1 Feign Client
Feign Client는 Netflix에서 개발된 REST Call을 추상화한 라이브러리입니다. 사용방법은 호출하려는 Http Endpoint에 대한 Interface를 생성해서 @FeignClient라는 어노테이션으로 사용할 수 있죠.
예를 들자면 유저 서비스에서 포스트에 관한 정보를 불러오고 싶을 때 @GetMapping(/post-service/:postId)와 같은 Http Endpoint에 관한 Interface를 생성하고 이 Interface를 @FeignClient라는 어노테이션으로 호출하여 게시글 정보를 불러 올 수 있습니다.
#2 Feign Client 구현
저는 유저서비스에서 유저가 작성한 게시글 정보들과 대여 정보를 불러오고 싶으니 2개의 FeignClient를 작성하도록 하겠습니다.
- auth-service - pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- AuthServiceApplication
package com.microservices.authservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class AuthServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AuthServiceApplication.class, args);
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Feign Client를 사용하기 위한 설정을 마치고 clients라는 패키지를 만들어 여기에 Client인터페이스를 관리하도록 하겠습니다.
- /clients/PostClient
package com.microservices.authservice.clients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
@FeignClient(name="post-service")
public interface PostClient {
@GetMapping("/{userId}/posts")
public List<ResponsePost> getPosts(@PathVariable("userId") String userId);
}
- /clients/RentalClient
package com.microservices.authservice.clients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
@FeignClient(name="rental-service")
public interface RentalClient {
@GetMapping("/{owner}/my_rentals")
public List<ResponseRental> getRentalsByOwner(@PathVariable("owner") String owner);
@GetMapping("/{borrower}/borrow_rentals")
public List<ResponseRental> getRentalsByBorrower(@PathVariable("borrower") String borrower);
}
인터페이스의 코드들은 post-service, rental-service의 컨트롤러에 미리 구현해둔 REST Call들을 인터페이스 형식으로 가져온 것들입니다. 즉, Http Endpoint에 대한 Interface를 생성한 것이죠. 그러면 이 코드들의 오류 코드드들을 구현해보도록 하겠습니다.
- ResponsePost
package com.microservices.authservice.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponsePost {
private Long id;
private String postType;
private String title;
private String content;
private String createdAt;
private String writer;
private String status;
@Builder
public ResponsePost(
Long id,
String postType,
String title,
String content,
String createdAt,
String writer,
String status
) {
this.id = id;
this.postType = postType;
this.title = title;
this.content = content;
this.createdAt = createdAt;
this.writer = writer;
this.status = status;
}
}
- ResponseRental
package com.microservices.authservice.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseRental {
private String rentalId;
private Long postId;
private Long price;
private String owner;
private String borrower;
private String startDate;
private String endDate;
private String createdAt;
@Builder
public ResponseRental(
String rentalId,
Long postId,
Long price,
String owner,
String borrower,
String startDate,
String endDate,
String createdAt
) {
this.rentalId = rentalId;
this.postId = postId;
this.price = price;
this.owner = owner;
this.borrower = borrower;
this.startDate = startDate;
this.endDate = endDate;
this.createdAt = createdAt;
}
}
- ResponseUser
package com.microservices.authservice.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
public class ResponseUser {
private String email;
private String nickname;
private String phoneNumber;
private String userId;
private String encryptedPwd;
private List<ResponsePost> posts;
private List<ResponseRental> rentals;
@Builder
public ResponseUser(
String email,
String nickname,
String phoneNumber,
String userId,
String encryptedPwd,
List<ResponsePost> posts,
List<ResponseRental> rentals
) {
this.email = email;
this.nickname = nickname;
this.phoneNumber = phoneNumber;
this.userId = userId;
this.encryptedPwd = encryptedPwd;
this.posts = posts;
this.rentals = rentals;
}
}
- UserDto
package com.microservices.authservice.dto;
import com.microservices.authservice.vo.ResponsePost;
import com.microservices.authservice.vo.ResponseRental;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
public class UserDto {
private String email;
private String password;
private String nickname;
private String phoneNumber;
private String userId;
private String encryptedPwd;
private List<ResponsePost> posts;
private List<ResponseRental> rentals;
@Builder
public UserDto(
String email,
String password,
String nickname,
String phoneNumber,
String userId,
String encryptedPwd,
List<ResponsePost> posts,
List<ResponseRental> rentals
) {
this.email = email;
this.password = password;
this.nickname = nickname;
this.phoneNumber = phoneNumber;
this.userId = userId;
this.encryptedPwd = encryptedPwd;
this.posts = posts;
this.rentals = rentals;
}
}
이 vo객체들은 FeignClient를 통해서 받아올 게시글과 대여에 관한 정보를 담을 클래스입니다. 그리고 최종적으로 유저의 정보를 반환할 때 이 정보들을 담을 클래스들을 리스트로 만들도록 하겠습니다.
- AuthController
package com.microservices.authservice.controller;
import com.microservices.authservice.dto.UserDto;
import com.microservices.authservice.service.AuthService;
import com.microservices.authservice.vo.RequestRegister;
import com.microservices.authservice.vo.ResponseUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/")
@Slf4j
public class AuthController {
...
@GetMapping("/{userId}/getUser")
public ResponseEntity<?> getUser(@PathVariable("userId") String userId) {
log.info("Auth Service's Controller Layer :: Call getUser Method!");
UserDto userDto = authService.getUser(userId);
return ResponseEntity.status(HttpStatus.OK).body(ResponseUser.builder()
.email(userDto.getEmail())
.nickname(userDto.getNickname())
.phoneNumber(userDto.getPhoneNumber())
.userId(userDto.getUserId())
.posts(userDto.getPosts())
.rentals(userDto.getRentals())
.build());
}
@GetMapping("/{nickname}/my-rental-list")
public ResponseEntity<?> getMyRentals(@PathVariable("nickname") String nickname) {
log.info("Auth Service's Controller Layer :: Call getMyRentals Method!");
UserDto userDto = authService.getRentalsByNickname(nickname);
return ResponseEntity.status(HttpStatus.OK).body(ResponseUser.builder().rentals(userDto.getRentals()));
}
@GetMapping("/{nickname}/my-borrow-list")
public ResponseEntity<?> getMyBorrow(@PathVariable("nickname") String nickname) {
log.info("Auth Service's Controller Layer :: Call getMyRentals Method!");
UserDto userDto = authService.getBorrowsByNickname(nickname);
return ResponseEntity.status(HttpStatus.OK).body(ResponseUser.builder().rentals(userDto.getRentals()));
}
}
저는 위 코드와 같이 Client들을 호출하기 위한 3개의 메서드를 만들었습니다. getUser는 유저의 정보를 불러오는 메서드입니다. 최종적으로 유저의 정보, 유저의 아이디 값을 이용한 게시물 정보를 불러올 수 있겠죠. getMyRentals, getMyBorrow 유저의 닉네임을 이용하여 대여 정보, 차용 정보를 불러 올 수 있는 메서드입니다. 앞서 RentalEntity에는 owner, borrower속성을 설정해두었는데 이 값과 nickname이 매칭되는 정보를 불러오게 할 메서드입니다.
- AuthService
package com.microservices.authservice.service;
import com.microservices.authservice.dto.UserDto;
import org.springframework.security.core.userdetails.UserDetailsService;
public interface AuthService extends UserDetailsService {
UserDto registerUser(UserDto userDto);
UserDto getUserDetailsByEmail(String email);
UserDto getUser(String userId);
UserDto getRentalsByNickname(String nickname);
UserDto getBorrowsByNickname(String nickname);
}
- AuthServiceImpl
package com.microservices.authservice.service;
import com.microservices.authservice.clients.PostClient;
import com.microservices.authservice.clients.RentalClient;
import com.microservices.authservice.dto.UserDto;
import com.microservices.authservice.entity.UserEntity;
import com.microservices.authservice.repository.AuthRepository;
import com.microservices.authservice.util.DateUtil;
import com.microservices.authservice.vo.ResponsePost;
import com.microservices.authservice.vo.ResponseRental;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Service
@Slf4j
public class AuthServiceImpl implements AuthService {
private AuthRepository authRepository;
private BCryptPasswordEncoder passwordEncoder;
private PostClient postClient;
private RentalClient rentalClient;
@Autowired
public AuthServiceImpl(
AuthRepository authRepository,
BCryptPasswordEncoder passwordEncoder,
PostClient postClient,
RentalClient rentalClient
) {
this.authRepository = authRepository;
this.passwordEncoder = passwordEncoder;
this.postClient = postClient;
this.rentalClient = rentalClient;
}
...
@Transactional
@Override
public UserDto getUser(String userId) {
log.info("Auth Service's Service Layer :: Call getUser Method!");
UserEntity userEntity = authRepository.findByUserId(userId);
if(userEntity == null) throw new UsernameNotFoundException(userId);
return UserDto.builder()
.email(userEntity.getEmail())
.nickname(userEntity.getNickname())
.phoneNumber(userEntity.getPhoneNumber())
.userId(userEntity.getUserId())
.encryptedPwd(userEntity.getEncryptedPwd())
.posts(postClient.getPosts(userId))
.build();
}
@Transactional
@Override
public UserDto getRentalsByNickname(String nickname) {
log.info("Auth Service's Service Layer :: Call getRentalsByNickname Method!");
return ResponseEntity.status(HttpStatus.OK).body(ResponseUser.builder()
.rentals(userDto.getRentals())
.build());
}
@Transactional
@Override
public UserDto getBorrowsByNickname(String nickname) {
log.info("Auth Service's Service Layer :: Call getBorrowsByNickname Method!");
return ResponseEntity.status(HttpStatus.OK).body(ResponseUser.builder()
.rentals(userDto.getRentals())
.build());
}
...
}
client들을 이용하여 서비스레이어에서 정보를 호출할 수 있도록 구현하였습니다.
- AuthRepository
package com.microservices.authservice.repository;
import com.microservices.authservice.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface AuthRepository extends JpaRepository<UserEntity, Long> {
UserEntity findByEmail(String email);
UserEntity findByUserId(String userId);
}
마지막으로 api-gateway에 해당 설정을 추가하도록 하겠습니다.
- id: rental
uri: lb://RENTAL-SERVICE
predicates:
- Path=/rental-service/**
filters:
- RemoveRequestHeader=Cookie
- RewritePath=/rental-service/(?<segment>.*), /$\{segment}
#3 테스트
1) /user-service/:userId/getUser
2) /user-service/:nickname/getMyRentals
3) /user-service/:nickname/getMyBorrow
위의 화면처럼 테스트 결과가 잘 나오는 모습을 볼 수 있습니다. 다음 포스트에서는 FeignClient의 오류, 마이크로서비스의 분산추적에 관해 다루도록 하겠습니다.
참고
인프런: Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA) - 이도원
Author And Source
이 문제에 관하여(Rental Application (React & Spring boot Microservice) - 18 : auth-service(5: Feign Client)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@biuea/Rental-Application-React-Spring-boot-Microservice-18-auth-service5-Feign-Client저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)