본문 바로가기

MSA

개별 마이크로서비스 통신2

요구사항: 관리자 입장에서 해당 상품의 주문내역을 조회하고 싶은 상황!

 

0단계. 요청하는 쪽:item-service , 요청받는 쪽:order-service

// build.gradle 의존성 추가
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'

 

 

package com.example.itemservice;

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 // fegin 클라이언트로 등록
public class ItemServiceApplication {

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

}

 

 

package com.example.itemservice.feginClient;

import com.example.itemservice.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;

// 발신지: item, 목적지: order
// order service쪽으로 요청할 예정입니다.
// name은 유레카 기준 발신지를 작성, path는 게이트웨이기준,
@FeignClient(name = "ORDER-SERVICE", path = "order-service")
public interface ItemtoOrderFeignClient {

    @GetMapping("orders/{productId}/products")
    public List<Order> getOrdersByItemId(@PathVariable String productId);
}

 

 

1단계,아이템에 더미데이터 넣어주기

실제 Entitiy 역할이 아닌 값만 받아서 처리해주는 용도로 생성

order-service에 있는 order 엔티티와 동일한 필드 값으로 구성하지만 @entitiy를 사용하지 않음

package com.example.itemservice.domain;

import jakarta.persistence.Column;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;

import java.time.LocalDateTime;

// DTO보다는 VO로 만들기
@Getter @Setter @AllArgsConstructor @NoArgsConstructor
@ToString @Builder
public class Order {
    private Long id;

    private String orderId;

    private Long count;

    private LocalDateTime createAt;

    private String userId;

    private String productId;
}

 

2단계, 아이템을 이용한 구매내역을 추가해주기

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);

    // productId는 ItemId와 같습니다.
    Optional<List<Order>> findOrderByProductId(String productId);

}

 

 

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);
    }

    @GetMapping("orders/{productId}/products")
    public ResponseEntity<?>  getOrderListByProductId(@PathVariable String productId){
        List<Order> orderList = orderService.getOrderListByProductId(productId)
                .orElseThrow(()-> new RuntimeException("없는 유저 아이디로 조회하셨습니다"))
                .stream().toList();
        return ResponseEntity.ok(orderList);
    }
}

 

3단계, 아이템을 조회했을 때 해당 아이템과 엮인 구매내역이 함께 조회될 수 있도록 fegin을 이용해 비동기 요청 및 데이터 병합 진행

package com.example.itemservice.dto;

import com.example.itemservice.domain.Item;
import com.example.itemservice.domain.Order;
import lombok.*;

import java.util.List;

// 도메인 Item과 같은 멤버 필드 값에 List<Order>만 추가됨 , @Setter 꼭 필요함
@Getter @Setter @ToString
@AllArgsConstructor @NoArgsConstructor
@Builder
public class ResponseOrderByItemDto {

    private String productId; // 아이템 고유 식별 uuid

    private String productName; //판매처에서 사용하는 상품명

    private Long stock; //재고량

    private Long pricePerItem;//개당 가격

    private List<Order> orderList;

    public static ResponseOrderByItemDto FromDto(Item item){
        return ResponseOrderByItemDto.builder()
                .productId(item.getProductId())
                .productName(item.getProductName())
                .stock(item.getStock())
                .pricePerItem(item.getPricePerItem())
                .build();
    }
}

 

 

package com.example.itemservice.repository;

import com.example.itemservice.domain.Item;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface ItemRepository extends JpaRepository<Item,Long> {
    public Optional<Item> findItemByProductId(String productId);
    
}

 

 

package com.example.itemservice.service;

import com.example.itemservice.domain.Item;
import com.example.itemservice.domain.Order;
import com.example.itemservice.dto.RequestCreateItemDto;
import com.example.itemservice.dto.ResponseOrderByItemDto;
import com.example.itemservice.feginClient.ItemtoOrderFeignClient;
import com.example.itemservice.repository.ItemRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor

public class ItemService {
    private final ItemRepository itemRepository;
    private final ItemtoOrderFeignClient orderFeignClient;

    public void createItem(RequestCreateItemDto ItemDto){
        Item saveItem = ItemDto.toEntity();
        itemRepository.save(saveItem);
    }

    public ResponseOrderByItemDto findOrderByProduct(String productId){
        // 1. 요청측 -> 특정 아이템을 가져옵니다
        Item finditem = itemRepository.findItemByProductId(productId)
                .orElseThrow(()-> new RuntimeException("존재하지 않는 아이템입니다"));

        // 2. 요청하는쪽+요청받는쪽DTO-> ResponseOrderByItemDTO로 변경하는 코드 추가
        ResponseOrderByItemDto itemDto = ResponseOrderByItemDto.FromDto(finditem);


        // 3. 요청받는쪽으로 FEGIN사용하여 API요청-> feign 클라이언트를 이용해서 특정 아이템의 구매목록을 가져옵니다.
        List<Order> orderList = orderFeignClient.getOrdersByItemId(productId);

        // 4. 요청받는쪽 값을 요청하는 쪽의 필드로 합침-> ResponseOdrerByItemDTO내 setter를 통해 합치는 과정 진행
        itemDto.setOrderList(orderList);

        // 5. 합쳐준 DTO(ResponseOrderByItemDto)를 리턴
        return itemDto;
    }
}

 

 

package com.example.itemservice.controller;

import com.example.itemservice.dto.RequestCreateItemDto;
import com.example.itemservice.dto.ResponseOrderByItemDto;
import com.example.itemservice.service.ItemService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("item-service")
public class ItemController {

    private final ItemService itemService;

    @GetMapping("health-check")
    public String healthCheck(){
        return "item-service service is available";
    }

    // 등록 성공시 201
    @PostMapping("items")
    public ResponseEntity<?> CreateItem(@RequestBody RequestCreateItemDto ItemDto){
        itemService.createItem(ItemDto);

        return new ResponseEntity(HttpStatus.CREATED);
    }

    // Feign Item to Order
    @GetMapping("items/{productId}/orders")
    public ResponseEntity<?> getOrdersByProductId(@PathVariable String productId){
        ResponseOrderByItemDto dto = itemService.findOrderByProduct(productId);
        return ResponseEntity.ok(dto);
    }

}

item-service에서 order-service의 주문내역을 조회하는 것을 단계별로 살펴 볼 수 있습니다.