DAY 14

남예준·2025년 9월 28일

MSA 실습 코드 리뷰

User Login

  1. /auth/signup 요청
  2. gateway에서 필터 패스 후 auth-service로 보냄
  3. auth 서버에서는 User를 저장한다.
  4. 다시 /auth/signIn을 요청하면 마찬가지로 gateway를 지나 auth 서버에서 jwt token을 발급해준다.
  5. 만일 다른 곳으로 요청이 가면 필터가 동작해서 헤더에서 토큰을 검증하고 문제 없으면 다음 필터를 진행시킨다.

Product

일단 기본적으로 다른 서비스와 통신하는 것은 없다.

전부 Order에 제공되는 service인 것 같다.

제공되는 API

  • product 생성 → 헤더에서 유저 값 가져와서 매니저만 생성 가능
  • product List 읽기
  • product 읽기
  • product 수정 → 헤더에서 유저 값 가져와서 매니저만 수정 가능
  • product 삭제
  • product 수량 줄이기

또한 Zipkin이 있는 걸로 봐서 분산 추적 및 모니터링을 수행하는 것 같다.

QueryDSL 기능을 사용하는 거 같은데 처음보는 기능이다.

Qclass 라는 걸 봤다.

import static com.spring_cloud.eureka.client.product.core.QProduct.product;
  1. QClass는 어떤 과정으로 만들어지는걸까?
    1. QClass는 컴파일 단계에서 엔티티를 기반으로 생성
    2. JPA_APT(JPAAnnotationProcessTool)가 @Entity와 같은 특정 어노테이션을 찾고 해당 클래스를 분석
  2. 그냥 Entity를 사용하면 될텐데 굳이 QClass를 새로 생성해서 사용하는 이유는 뭘까?
    1. QClass는 엔티티 클래스의 메타 정보를 담고 있는 클래스로, Querydsl은 이를 이용하여 타입 안정성(Type safe)를 보장하며 쿼리를 작성할 수 있게 된다. 
    2. QClass를 사용하여 쿼리를 작성하면 엔티티 속성을 직접 참조하고 조합하여 쿼리를 구성
    3. QClass를 사용하면 컴파일 시점에 오류를 확인할 수 있다. 
    4. QClass는 엔티티 속성의 타입을 정확하게 표현하므로, 타입에 맞지 않는 연산이나 비교를 시도하면 컴파일러가 오류를 감지할 수 있다.
    5. IDE의 자동완성 기능을 활용하여 속성 이름을 직접 기억하지 않고 쿼리 작성을 보다 편리하게 할 수 있다.
  • Querydsl을 사용하면 기본적으로 QClass라는 자식이 생성된다. 이는 엔티티 클래스의 메타 데이터를 가지고 있는 클래스이다.
  • EntityManager를 파라미터로 받아서 JPAQueryFactory라는 것을 bean으로 등록할 수 있다.
        @Bean
        JPAQueryFactory jpaQueryFactory(EntityManager em){
            return new JPAQueryFactory(em);
        }
QueryResults<Product> results = queryFactory
                .selectFrom(product)
                .where(
                        nameContains(searchDto.getName()),
                        descriptionContains(searchDto.getDescription()),
                        priceBetween(searchDto.getMinPrice(), searchDto.getMaxPrice()),
                        quantityBetween(searchDto.getMinQuantity(), searchDto.getMaxQuantity())
                )
                .orderBy(orders.toArray(new OrderSpecifier[0]))
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetchResults();
  • queryFactory에서 selectFrom()으로 테이블을 정한다.

  • where절에 조건을 기술하고

    • product.price.between 이런 식으로 처리하는 거도 보이고
    • like는 이런 느낌인 거 같다
      private BooleanExpression nameContains(String name) {
          return name != null ? product.name.containsIgnoreCase(name) : null;
      }
    • 뭔가 메소드 식으로 조건절을 처리한다.
  • orderBy로 order by 절에 대해 기술한다.

    • pageable 객체에서 sort 관련 정보를 끌어온다.
  • offset과 limit로 paging 처리에 대한 내용을 기술한다.

  • fetchResults로 QueryResults 결과를 가져온다.

  • 그리고 QueryResults의 인스턴스를 가공해서 원하는 결과를 만들어주면 된다.

  • 마지막으로 Service에서는 그냥 JpaRepository를 주입받게 해놓고 Jpa의 기능과 QueryDsl의 기능을 모두 쓰는 것도 확인했다.

    • JpaRepositoryProductRepositoryCustom를 impl한 ProductRepository

      • ProductRepositoryCustomProductRepositoryImpl가 가졌음 하는 기능을 넣고 ProductRepository 인스턴스에서도 수행이 가능하도록 다중 구현을 때려버림
    • ProductRepositoryCustom을 implement한 ProductRepositoryImpl

      public class ProductService {
      
          private final ProductRepository productRepository;
      
          @Transactional
          public ProductResponseDto createProduct(ProductRequestDto requestDto, String userId) {
              Product product = Product.createProduct(requestDto, userId);
              Product savedProduct = productRepository.save(product);
              return toResponseDto(savedProduct);
          }
      
          public Page<ProductResponseDto> getProducts(ProductSearchDto searchDto, Pageable pageable) {
              return productRepository.searchProducts(searchDto, pageable);
          }

Order

기능은 다음과 같은 것 같다

  • 주문 엔터티 생성
    • 요청이 들어오면 gateway가 order로 갖고 온 다음에 service까지 타서 product Client(Feign Client)를 이용해서 product-service의 api를 요청하게 된다. 그 다음 service-discovery에서 정보를 제공받아 LB를 이용해 product-service에서 정보를 처리 후 product Client를 통해 응답을 받는다.
  • 주문 리스트 검색
  • 주문 검색
  • 주문 수정
  • 주문 삭제

기본적으로 관계형 데이터베이스에는 컬렉션을 저장할 수 없다.

따라서 컬렉션을 저장하기 위해서는 별도의 테이블을 만들어서 컬렉션을 저장해야 한다.

이때 사용할 수 있는 것이 @ElementCollection@CollectionTable이다.

@ElementCollection

컬렉션 객체임을 JPA가 알 수 있게 하게 한다.

엔티티가 아닌 값 타입, 임베디드 타입에 대한 테이블을 생성하고 1대다 관계로 다룬다.

@CollectionTable

값 타입 컬렉션을 매핑할 테이블에 대한 역할을 지정하는 데 사용한다.

테이블의 이름과 조인정보를 적어줘야 한다.

대략적으로 이러한 흐름을 갖고 있다.

조금 더 설명이 필요한 것 같아 지피티한테 물어봤다.

지피티 said

1️⃣ 클라이언트(브라우저 등)A 서비스

  • 외부에서 A의 엔드포인트를 호출.

2️⃣ A 서비스 로직 처리 후

  • A 안의 @FeignClient@GetMapping("/something") 같은 B의 API를 호출하려고 함.

3️⃣ A 서비스 내부의 LoadBalancer 동작

  • A가 B 서비스의 “논리 이름”(예: @FeignClient(name = "b-service"))을 보고 A 서비스의 Spring Cloud LoadBalancerEureka(서비스 레지스트리) 에서 B의 실제 인스턴스 목록을 조회.
  • 그 목록 중 한 인스턴스의 IP:PORT를 선택해서 HTTP 요청을 보냄.

4️⃣ B 서비스 처리

  • 선택된 B 인스턴스가 요청을 받고 로직 수행 후 HTTP Response 생성.

5️⃣ HTTP 응답 반환

  • B 인스턴스가 HTTP 프로토콜을 통해 요청을 보낸 주체(A 서비스의 FeignClient) 에게 그대로 응답을 돌려줌.
  • 이미 TCP 커넥션이 열려 있으니, 일반적인 REST 호출처럼 Response가 그대로 돌아간다.
  • A는 그 결과(JSON, DTO 등)를 받고 이후 로직을 계속 처리.

다른 사람의 블로그도 들어가보면서 공부해봐야겠다.

프로젝트 API 명세서 기술

완료 얼추 했음…

0개의 댓글