[ ORM ]/JPA

[ JPA ] E-commerce 프로젝트 - 회원 등록 API

환이s 2025. 7. 7. 15:28
728x90


Intro

 

안녕하세요. 환이s입니다👋

 

E-commerce 시스템을 구축하면서 가장 기본이자 중요한 기능 중 하나는 바로 회원 등록 기능입니다.

 

회원 등록은 단순히 사용자 정보를 저장하는 것을 넘어, 이후의 로그인, 주문, 결제 등 다양한 기능과 밀접하게 연결되기 때문에 신중하게 설계되어야 합니다.

 

이번 포스팅에서는 Spring Boot와 JPA를 활용한 E-commerce 프로젝트에서 회원 등록 API를 어떻게 구현했는지 소개하며, Controller와 Service 계층의 처리 방식까지 전반적인 구현 과정을 단계별로 설명드릴 예정입니다.

 

이번 예제에 사용되는 소스 코드는 이전 포스팅에서 다뤘던 내용을 기반으로 진행해 보겠습니다.
해당 포스팅에서는 JPA를 활용한 각 도메인 개발 과정을 다뤘으니, 궁금하신 분들은 아래 링크를 참고하시면 도움이 될 것 같습니다.

 

 

[ JPA ] E-commerce 프로젝트 - 회원 도메인 개발

Intro 안녕하세요. 환이s입니다👋이전 포스팅에서 프로젝트에 필요한 엔티티 설계를 진행했습니다. 이어서 요구사항 구현 기능으로 회원 등록과 목록 조회 기능을 코드로 구현하고, 테스트 코

drg2524.tistory.com

 

 

[ JPA ] E-commerce 프로젝트 - 상품 도메인 개발

Intro 안녕하세요. 환이s입니다👋이전 포스팅에서 회원 도메인 개발까지 알아봤습니다. 이어서 상품 도메인을 개발해 보겠습니다. 상품 테스트는 회원 테스트와 비슷하므로 생략하겠습니다🙂

drg2524.tistory.com

 

 

[ JPA ] E-commerce 프로젝트 - 주문 도메인 개발

Intro안녕하세요. 환이s입니다👋이전 포스팅에서 상품 도메인 개발까지 알아봤습니다.이어서 주문 도메인을 개발해 보겠습니다🙂 ✅ 구현 기능     1️⃣ 상품 주문     2️⃣ 주문 내역 조

drg2524.tistory.com

 

 

[ JPA ] E-commerce 프로젝트 - 주문 검색 기능 개발 (JPQL,Criteria,Querydsl)

Intro안녕하세요. 환이s입니다👋이전 포스팅에서 주문 도메인 개발까지 알아봤습니다.이어서 앞서 말씀드린 주문 파트의 핵심인 검색 기능 개발을 진행하면서 JPA에서 동적 쿼리를 어떻게 해결

drg2524.tistory.com


회원 등록 API 구현

 

먼저, 가장 단순한 방식인 회원 엔티티(Member)를 직접 요청 본문으로 받는 방식부터 살펴보겠습니다.

 

이 방식은 빠르게 구현할 수 있다는 장점이 있지만, 실무에서는 다양한 문제를 야기할 수 있습니다.

코드를 통해 어떤 방식으로 작성되는지 확인해 보고, 이후에 이 방식이 갖는 문제점에 대해서도 함께 짚어보겠습니다.

 

1️⃣ MemberApiController - V1 

 

📌 코드 예시

@PostMapping("/api/v1/members")
public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) {
    Long id = memberService.join(member);
    return new CreateMemberResponse(id);
}

 @Data
    static class CreateMemberResponse {
        private Long id;

        public CreateMemberResponse(Long id) {
            this.id = id;
        }
    }

 

위 코드는 클라이언트로부터 Member 엔티티 형태의 JSON 데이터를 받아, 해당 데이터를 서비스 계층을 통해 저장한 뒤 생성된 ID를 반환하는 구조입니다.

 

요청 예시는 다음과 같습니다.

POST /api/v1/members
Content-Type: application/json

{
  "name": "userA"
}

 

응답 예시는 다음과 같습니다.

{
  "id": 1
}

 

⚠️ V1 방식의 문제점

 

이 방식은 단순해 보이지만 아래와 같은 문제점들이 존재합니다.

 

  1. 엔티티가 API 스펙에 노출됨
    • Member 엔티티 구조가 그대로 외부에 드러나며, API의 변경 범위가 엔티티에 종속됩니다.
    • 예를 들어, Member 클래스에 새로운 필드가 추가되거나 이름이 변경되면, API 스펙도 함께 바뀌게 됩니다.
  2. 검증 로직이 엔티티에 침투
    • @Valid를 통해 유효성 검사를 하게 되면, 엔티티에 @NotEmpty, @Length 등의 검증 어노테이션이 추가되어 도메인 로직과 프레젠테이션 로직이 뒤섞이게 됩니다.
  3.  재사용 어려움
    • 하나의 엔티티는 다양한 API에서 재사용될 수 있지만, API 마다 요구하는 필드나 조건이 다르기 때문에 모든 경우를 하나의 엔티티로 커버하기 어렵습니다.

2️⃣ MemberApiController - V2

앞서 살펴본 V1 방식에서는 Member 엔티티를 직접 API 요청에 사용하면서 여러 가지 문제점들이 발생했습니다.

 

이러한 문제를 해결하기 위한 대표적인 방법은 API 요청과 응답에 별도의 DTO(Data Transfer Object)를 사용하는 것입니다.

 

V2에서는 클라이언트가 보내는 데이터를 Member 엔티티가 아닌 전용 DTO(CreateMemberRequest)로 받아 처리합니다.

 

📌 코드 예시

@PostMapping("/api/v2/members")
public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request) {
    Member member = new Member();
    member.setName(request.getName());

    Long id = memberService.join(member);
    return new CreateMemberResponse(id);
}

@Data
static class CreateMemberRequest {
    private String name;
}

 @Data
    static class CreateMemberResponse {
        private Long id;

        public CreateMemberResponse(Long id) {
            this.id = id;
        }
    }

 

위 코드에서는 클라이언트로부터 JSON 요청을 받을 때 CreateMemberRequest라는 별도의 DTO 객체를 통해 데이터를 받고, 서비스 계층에 넘기기 전 Member 객체로 변환하여 저장합니다.

 

응답도 동일하게 DTO(CreateMemberResponse)를 사용하여 필요한 데이터만 선택적으로 반환할 수 있습니다.

 

✅ V2 방식의 장점

  1.  엔티티 보호
    • 클라이언트에 Member 엔티티가 직접 노출되지 않기 때문에, 엔티티 변경이 API 스펙에 영향을 주지 않습니다.
  2. API 명세 분리 
    • 요청/응답에 필요한 필드만 DTO에 포함시킬 수 있어, API 명세를 더 명확하고 간결하게 설계할 수 있습니다.
  3. 검증 로직 분리
    • @Valid 검증 어노테이션을 DTO에 적용하면, 프레젠테이션 계층에서의 검증 로직이 도메인 모델과 분리되어 유지보수가 용이해집니다.

 

이처럼 DTO를 활용한 방식은 유지보수성과 확장성 면에서 훨씬 유리하며, 실무에서 가장 많이 사용되는 패턴입니다.

 

전체 소스 구성은 다음과 같습니다.

 

📌 전체 코드 구성 

@RestController
@RequiredArgsConstructor
public class MemberApiController {

    private final MemberService memberService;

    /**
     * 등록 V1: 요청 값으로 Member 엔티티를 직접 받는다.
     * 문제점
     * - 엔티티에 프레젠테이션 계층을 위한 로직이 추가된다.
     * - 엔티티에 API 검증을 위한 로직이 들어간다. (@NotEmpty 등등)
     * - 실무에서는 회원 엔티티를 위한 API가 다양하게 만들어지는데, 한 엔티티에 각각의 API를 위한
     모든 요청 요구사항을 담기는 어렵다.
     * - 엔티티가 변경되면 API 스펙이 변한다.
     * 결론
     * - API 요청 스펙에 맞추어 별도의 DTO를 파라미터로 받는다.
     */
    @PostMapping("/api/v1/members")
    public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member){
        Long id = memberService.join(member);
        return new CreateMemberResponse(id);
    }

    /**
     * 등록 V2: 요청 값으로 Member 엔티티 대신에 별도의 DTO를 받는다.
     */
    @PostMapping("/api/v2/members")
    public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request){
        Member member = new Member();
        member.setName(request.getName());

        Long id = memberService.join(member);

        return new CreateMemberResponse(id);
    }
   
    @Data
    static class CreateMemberRequest {
        private String name;

    }

    @Data
    static class CreateMemberResponse {
        private Long id;

        public CreateMemberResponse(Long id) {
            this.id = id;
        }
    }
}

마치며

 

이번 포스팅에서는 E-commerce 프로젝트에서 가장 기본적인 기능인 회원 등록 API를 JPA 기반으로 어떻게 구현할 수 있는지 살펴보았습니다.

 

처음에는 엔티티를 그대로 요청으로 받아 처리하는 V1 방식을 구현해 보고, 그 과정에서 발생할 수 있는 문제점들을 바탕으로 DTO를 활용한 V2 방식으로 개선해 보았습니다.

 

이처럼 실무에서는 엔티티를 직접 API 요청/응답에 사용하는 방식은 지양하고, API 스펙에 맞는 전용 DTO를 사용하여 명확하고 유연한 구조를 만드는 것이 중요합니다.

 

다음 포스팅에서는 등록에 이어, 회원 수정 API를 어떻게 구현하고, 어떤 식으로 DTO를 활용하여 데이터를 안전하게 전달할 수 있을지 단계별로 다뤄보겠습니다.

 

읽어주셔서 감사합니다 😊

다음 포스팅에서 뵙겠습니다👋

 

위 포스팅 글은 김영한님의 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화 강의를 참고했습니다.

728x90