1. 사전 설정 및 이론
- fegin을 사용하도록 요청을 넣는 입장의 마이크로 서비스 애플리케이션 측에 의존성 부여
- Eureka를 지나서 fegin 요청을 넣음으로 엔트포인트가 아닌 서비스명으로 넣을 수 있었음
- 통신용 인터페이스에서 호출 정보를 분리해서 저정하기 때문에 Fegin관련 특정 로직을 실행하는 서비스 클래스 내부에 인터페이스와 구현체를 생성하고 해당 인터페이스 내부의 요청 메서드를 호출하면, 타 마이크로 서비스에 그대로 요청이 전달됨
// 인터페이스 자료형, 인터페이스명, 생성자만 만들면 알아서 구현체를 만들어서 대입함
@Service
public class Service {
private final Repository repository;
private final 인터페이스명 인터페이스명;
public Service(Repository repository,
인터페이스명 인터페이스명) {
this.repository = repository; // 도메인에 있는 DB에서 직접 들고 올 repository
this.인터페이스명 = 인터페이스명; // 다른 도메인에서 가져오는 조인처럼 사용하는 것
}
public 리턴자료형1 조인데이터얻어오기 (자료형 키값) {
리턴자료형1 변수명 = Repository.findById(키값)
.orElseThrow(RuntimeException::new);
리턴자료형2 자료변수 = 인터페이스명.비동기요청메서드(id);
}
}
2. 구현
- Gateway-service
-yml 파일
server:
port: 8000
spring:
application:
name: apigateway-server
cloud:
gateway:
default-filters:
- name: GlobalFilter
args:
message: Global Filter Default Message Test
pre: true
post: true
routes: # 개별 마이크로서비스 정보를 routes에 기입함
# 유저 서비스 등록
- id: user-service
uri: lb://USER-SERVICE #직접적인 주소를 등록하지 않고 로드밸런서를 설정해서 디스커버리 유레카를 통한다.
predicates:
- Path=/user-service/**
- id: order-service
uri: lb://ORDER-SERVICE
predicates:
- Path=/order-service/**
- id: item-service
uri: lb://ITEM-SERVICE
predicates:
- Path=/item-service/**
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
default-zone: http://127.0.0.1:8761/eureka
instance:
instance-id: ${spring.cloud.client.hostname}:${spring.application.instance_id:${random.value}}
user-service
- build.gradle
ext {
set('springCloudVersion', "2022.0.4")
}
dependencies {
// openfeign
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
- Application.java
Fegin은 Eureka를 기반으로 작동함으로 개별 Discovery-client에서 어노테이션을 붙여서 Fegin 클라이언트임을 등록하여 비동기 요청임을 명시합니다.
package com.example.userservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // Eureka를 지나서 feign 요청을 넣는다 특정한 엔드포인트가 아닌 서비스명으로 넣을 수 있음
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
-User
package com.example.userservice.domain;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Entity
@Getter // @Setter은 엔티티 수정을 막기 위해서, 캡슐화 + 은닉화
@AllArgsConstructor @NoArgsConstructor @Builder
@Table(name="users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String name;
@Column(unique = true)
private String userId;
private String encPw;
private String uuid;
private LocalDateTime createAt;
}
// 도메인 레이어는 다른 레이어를 참조하면 안 되지만 다른 레이어에서 도메인 레이어를 참조해도 된다.
- UsertoOrderFeginClient.java
name 지정 시 spring.application.name이 아닌 Eureka 사이트 Application name으로 해주세요.
요청 하는 쪽: user-service , 요청 받는 쪽: order-service
package com.example.userservice.feignclient;
import com.example.userservice.domain.Order;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
// name: 유레카에 등록된 서비스명, path:게이트웨이 서버에서 붙는 서비스 앞단에 붙는 접두어
@FeignClient(name="ORDER-SERVICE",path = "order-service") // order-service로 유레카에 등록된 서비스의
public interface UsertoOrderFeignClient {
@GetMapping("/orders/{userId}/users") // orders/{userId}로 호출을 넣으면
public List<Order> getOrderListByUserId(@PathVariable String userId); // 요청을 처리한다
}
-ResponseFindUserDto
package com.example.userservice.dto;
import com.example.userservice.domain.Order;
import com.example.userservice.domain.User;
import lombok.*;
import java.util.List;
@Getter @Setter
@AllArgsConstructor @NoArgsConstructor @Builder @ToString
public class ResponseFindUserDto {
private Long id;
private String uuid;
private String email;
private String name;
private String userId;
// 필요하다면 구매 내역을 같이 가져올 수 있도록 처리합니다.
private List<Order> orderList;
public ResponseFindUserDto(User user){
this.id = user.getId();
this.email =user.getEmail();
this.name =user.getName();
this.uuid =user.getUuid();
this.userId=user.getUserId();
}
}
-UserRepository
package com.example.userservice.repoaitory;
import com.example.userservice.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User,Long> {
User findUserByUserId(String userId);
}
- UserController
package com.example.userservice.controller;
import com.example.userservice.dto.RequestCreateUserDto;
import com.example.userservice.dto.ResponseFindUserDto;
import com.example.userservice.service.UserService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@RestController
// MVC 패턴에서 @RequestMapping("user-service")가 없어도 빌드 오류가 발생하지는 않지만
// 게이트웨이서 predicates: - Path=/user-service/** 로 등록되었기 때문에 @RequestMapping("user-service")가 필요함!!
@RequestMapping("user-service")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("users")
public ResponseEntity<?> createUser(@Valid @RequestBody RequestCreateUserDto userDto){
userService.creatUser(userDto);
return new ResponseEntity(HttpStatus.CREATED); // 상태코드 2001
}
@GetMapping("users/{userId}/orders")
public ResponseEntity<?> findOrdersByUserId(@PathVariable String userId){
ResponseFindUserDto userDto = userService.findUserOrderList(userId);
return ResponseEntity.ok(userDto);
}
}
Order-service
-Order
Entity 목적으로 만들지 않았기 때문에, @Table 어노테이션이 없습니다.
ResponseFindUserDto에서 Order 클래스 리스트값을 위해 생성되었습니다.
package com.example.userservice.domain;
// ResponseFindUserDto에서 import 에러를 막기 위해서 생성
import lombok.*;
import java.time.LocalDateTime;
// Entity 목적으로 만들지 않음
@Getter @Setter @AllArgsConstructor @NoArgsConstructor
@Builder @ToString
public class Order {
private Long id;
private String orderId;
private Long count;
private LocalDateTime createAt;
private String userId;
private String productId;
}
- OrderRepository
package com.example.orderservice.repository;
import com.example.orderservice.domain.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface OrderRepository extends JpaRepository<Order,Long> {
// feign을 위한 쿼리메서드로 userId를 이용해 해당 유저의 전체 order 목록을 가져오는 메서드
Optional<List<Order>> findOrderByUserIdOrderByCreateAtDesc (String userId);
}
-OrderServcice
package com.example.orderservice.service;
import com.example.orderservice.domain.Order;
import com.example.orderservice.dto.RequestCreateOrderDto;
import com.example.orderservice.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
public void createOrder(RequestCreateOrderDto orderDto){
orderRepository.save(orderDto.toEntity());
}
// userId를 입력하면 Order리스크를 넘겨주는 메서드 호출을 통해 리턴
public Optional<List<Order>> getListOrder(String userId){
Optional<List<Order>> orderUserList = orderRepository.findOrderByUserIdOrderByCreateAtDesc(userId);
// .orElseThrow(()->new RuntimeException( "해당 유저가 없습니다"));
return orderUserList;
}
}
-OrderController
package com.example.orderservice.controller;
import com.example.orderservice.domain.Order;
import com.example.orderservice.dto.RequestCreateOrderDto;
import com.example.orderservice.service.OrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequiredArgsConstructor
@RequestMapping("order-service")
public class OrderController {
private final OrderService orderService;
@GetMapping("health-check")
public String healthCheck(){
return "item-service server is available";
}
@PostMapping("orders")
public ResponseEntity<?> createOrder(@RequestBody RequestCreateOrderDto requestCreateOrderDto){
orderService.createOrder(requestCreateOrderDto);
return new ResponseEntity(HttpStatus.CREATED);
}
@GetMapping("orders/{userId}/users")
public ResponseEntity<?> getOrderListByUserId(@PathVariable String userId){
List<Order> orderList = orderService.getListOrder(userId)
.orElseThrow(()-> new RuntimeException("없는 유저 아이디로 조회하였습니다"))
.stream().toList(); // .stream().toList()는 Optional에서 유저 아이디로 조회한 주문 목록을 가져와서, 해당 주문 목록을 리스트로 변환하는 과정을 나타냅니다.
return ResponseEntity.ok(orderList);
}
}
'MSA' 카테고리의 다른 글
개별 마이크로 서비스 Custom Exception 처리 (0) | 2023.11.12 |
---|---|
개별 마이크로서비스 통신2 (0) | 2023.11.07 |
Github 레포지토리 config 서버 연동 (0) | 2023.11.01 |
Spring Cloud Config 서버를 활용한 DB 정보관리 (0) | 2023.11.01 |
Spring Cloud Config 서버를 활용한 연동 (1) | 2023.11.01 |