Post

(Sparta_WIL_02) 스프링 기본기

(Sparta_WIL_02) 스프링 기본기

1. Log

1. New Keyword

  • RestAPI 패턴
  • Builder / MapStruct - JPA사용하려면 익숙해져야하는 부분
  • 영속성 컨텍스트 - 어려움

2. 문제의 기록

  • 엔티티를 생성하는 방법
    • 엔티티를 생성할때는 생성자가 있어야 하는데,
    • PK나 FK가 걸려있으면 기본 생성자로 작업하는데 까다로워짐
    • 따라서 별도로 Request에서 요청오는 인자들만 별도로 생성자만들고
    • @Builder 해주면 조금 빠르게 작업은 가능함.
    • 그런데 서비스단에서 FK, PK를 넣어줘야함.
  • 엔티티 편의 메소드
    • 엔티티 자체를 수정할때 Setter를 쓰게 되면
    • 자동으로 작업해주는 기능들에서 Setter를 이용해서 뭔가 할 수가 있어서 조심해야함.
    • 별도로 편의메소드로 setter처럼 만들어주는게 유리.
    • 그리고 필요로 하는 추가 메소드도 편의 메소드!
  • 마이바티스는 SQL로 한큐에, JPA는 서비스단에서 거의 모든 작업을 !

3. 1주차 과제 관련

1. 작업내용

1. 어드민 API - Category

  • 카테고리 등록 : POST /api/admin/categories
  • 카테고리 수정 : PUT /api/admin/categories/{categoryId}
  • 카테고리 삭제 : DELETE /api/admin/categories/{categoryId}

2. 어드민 API - Product

  • 상품 등록 : POST /api/admin/products
  • 상품 수정 : PUT /api/admin/products/{productId}
  • 상품 옵션 수정 : PUT /api/admin/products/{productId}/options

3. 사용자 API (User-Facing)

  • ProductController 및 ProductService 클래스 생성
  • 기능 미구현

2. 고민 & 질문

1. CategoryId를 조회하는 반복 작업

  • AdminProductService 와 CategoryService에서 공통적으로 CategoryId를 확인하는 부분이 생겼습니다.
  • 어차피 로직이 동일해서 공통 컴포넌트를 만들면 만들 수는 있을 것 같은데요,
  • 근데 클래스 자체가 의존하는 게 너무 늘어나는 느낌이라, 각 클래스 내에서 private로 분리해서 사용하는 방법을 채택했습니다.
  • 이거를 그냥 지금처럼 서비스단에 두고 처리하는 게 낫은 방법일까요?

2. Entity

  • 엔티티를 생성하고 편의성 메소드만들고 요런 부분들이 익숙하지 않은 상태라, 혹시 놓치거나 불필요한 부분이 있을까요?

4. 2주차 피드백

관리자용 카테고리와 상품 등록 흐름은 JPA 연관관계를 활용해 꽤 자연스럽게 풀어냈습니다. 다만 사용자 조회 API가 아직 비어 있고, 검증과 응답 설계가 일부 흔들려서 현재 단계는 기본기 위에 기능을 완성해 가는 중간 지점에 가깝습니다.

📢 주요 피드백

  • [카테고리 등록] ✔️: parentId가 있을 때 부모 카테고리를 먼저 조회해 검증한 뒤 저장하도록 구성되어 있어, 최상위 카테고리와 하위 카테고리 생성 흐름의 핵심은 잘 구현했습니다.
  • [카테고리 수정] ❌: 자기 자신을 부모로 지정하는 직접 순환은 막았지만, description 수정이 빠져 있고 응답도 수정된 카테고리 정보라기보다 id 중심이라 요구사항을 끝까지 충족했다고 보기는 어렵습니다.
  • [카테고리 삭제] ✔️: 하위 카테고리와 연결된 상품 존재 여부를 확인한 뒤 삭제를 막는 로직이 있어, 삭제 조건을 서비스 계층에서 안전하게 지키려는 방향은 좋습니다.
  • [상품 등록] ✔️: category 존재 검증 후 상품을 만들고 옵션을 함께 연결해 저장하는 흐름이 잘 이어져 있어, 관리자 등록 API의 핵심 비즈니스 로직은 이해하고 구현한 흔적이 분명합니다.
  • [상품 수정] ❌: 상품 정보와 카테고리 변경 자체는 처리하지만, 응답 DTO가 요구사항의 상세 구조를 충분히 담지 못하고 요청값 검증도 컨트롤러에서 실제로 발동하지 않아 완성도는 아쉽습니다.
  • [상품 옵션 수정] ❌: PUT 기반 전체 교체 방식으로 접근한 점은 좋지만, 다른 상품에 속한 옵션 id가 들어왔을 때 이를 명시적으로 차단하는 소속 검증이 없어 요구사항의 중요한 안전장치가 비어 있습니다.
  • [상품 목록 조회] ❌: ProductSearchResponse가 비어 있고 getProducts가 null을 반환하므로, 동적 검색·정렬·페이징 요구사항은 아직 구현되지 않은 상태입니다.
  • [상품 상세 조회] ❌: getProduct가 null을 반환하고 ProductResponse도 id, 생성일시, 수정일시, 카테고리 객체 등 요구한 상세 구조를 담기 어려워 실제 사용자가 기대하는 상세 조회 API로 보기 어렵습니다.
  • [요청 검증과 응답 일관성] ❌: DTO에 제약 조건은 일부 적어 두었지만 컨트롤러에 @Valid가 없어 검증이 실질적으로 동작하지 않고, ApiResponse도 data와 message 사용 방식이 섞여 있어 응답 형태가 엔드포인트마다 달라질 가능성이 큽니다.

👨‍🏫 개선 포인트

  • [사용자 조회 API] 개선: 현재 가장 큰 문제는 사용자용 조회 기능이 비어 있다는 점입니다. 특히 ProductController의 productService 필드가 final이 아니어서 생성자 주입이 되지 않으므로, 구현을 채우기 전에도 실제 호출 시 NullPointerException 가능성이 있습니다. 먼저 컨트롤러 주입부터 안정화하고, 그 다음 목록 조회는 조건 조합과 Pageable, 상세 조회는 상품과 옵션, 카테고리를 DTO로 명확히 변환하는 순서로 완성해 보세요.
  • [검증과 응답 구조] 개선: DTO에 어노테이션을 붙였다고 해서 자동으로 검증이 되는 것은 아닙니다. @RequestBody와 @ModelAttribute에 @Valid를 붙여야 price, stock, name 같은 조건이 실제 요청 단계에서 걸러지고, categoryId 누락도 500 에러가 아니라 400 계열 검증 실패로 다룰 수 있습니다. 또한 ApiResponse는 data 또는 message 중 하나로 기준을 정해 일관되게 쓰는 편이 클라이언트와 테스트 코드 모두에서 훨씬 다루기 쉬워집니다.
  • [수정 로직과 매핑 완성도] 개선: 카테고리 수정에서는 name만 바꾸고 description은 놓치고 있어서 PUT 요청의 의미를 충분히 살리지 못하고 있습니다. 상품 응답도 MapStruct 기본 매핑만으로는 categoryId, options처럼 이름이 다른 필드를 기대대로 채우기 어렵기 때문에, 명시 매핑이나 별도 DTO 조립 로직을 두는 편이 더 안전합니다. 상품 옵션 수정도 현재는 내 상품 옵션만 찾아 수정하는 구조라서 타 상품 option id가 들어왔을 때 실패를 명확히 알려 주지 못하므로, 요청 id 목록과 실제 소속을 먼저 검증하는 단계가 필요합니다.

🛎️ 추가 팁

  • 양방향 연관관계를 쓸 때는 엔티티의 편의 메서드가 양쪽 상태를 함께 맞추는지가 중요합니다. 예를 들어 카테고리 부모를 바꿀 때 parent만 교체하면 메모리 상 children 컬렉션과 어긋날 수 있으니, addChild와 부모 변경 규칙을 조금 더 명확히 정리해 두면 JPA를 훨씬 안정적으로 이해할 수 있습니다.
  • 목록 조회를 구현할 때는 QueryDSL 자체 문법보다 동적 조건을 어떻게 조립하느냐가 핵심입니다. BooleanBuilder, Pageable, 정렬 파라미터 파싱을 함께 연습하면 이번 과제 범위 안에서 동적 쿼리 감각을 가장 빠르게 익힐 수 있습니다.
  • 상세 조회나 목록 조회에서 카테고리와 옵션을 함께 내려주기 시작하면 조회 횟수가 늘어나는 문제가 생길 수 있습니다. 다음 단계 학습 키워드로는 fetch join, EntityGraph, Lazy 로딩과 N+1 문제를 같이 보면 지금 작성한 구조를 더 실무적으로 다듬는 데 큰 도움이 됩니다.
This post is licensed under CC BY 4.0 by the author.