개인 프로젝트

회원 CRUD Rest API 구현 및 리팩토링

juyxonn 2024. 10. 30. 10:46

개요

이번 글에서는 Spring Boot로 회원 CRUD Rest API 기능을 구현하고, 이후 코드의 안전성을 높이기 위한 리팩토링 과정에 대해 설명하겠습니다. 프로젝트의 주요 기능으로는 회원가입, 회원 조회, 회원 수정, 회원 삭제가 포함됩니다. 이 과정에서 발생한 문제와 해결 방법도 함께 다룹니다.

 

1. 구현 내용

1-1. 회원가입 API

회원가입 시에는 MemberDto 객체로 요청을 받고, 이를 Member 엔티티로 변환하여 저장합니다. 이를 통해 클라이언트에서 입력받은 데이터를 Service에서 활용할 수 있도록 합니다.

// 회원가입
    @PostMapping("/register")
    public MemberResponseDto register(@Validated @RequestBody MemberDto memberDto) {
        Member member = Member.builder()
                .id(memberDto.getId())
                .username(memberDto.getUsername())
                .password(memberDto.getPassword())
                .email(memberDto.getEmail())
                .build();

        Long id = memberService.register(memberDto);
        log.info("username={}, password={}, email={}", member.getUsername(), member.getPassword(), member.getEmail());

        Member registeredMember = memberService.findOne(id);
        return MemberResponseDto.fromEntity(registeredMember); // 등록 후 엔티티를 Dto로 변환해 반환
    }

 

회원 등록 성공

 

 

1-2. 회원 조회 / 전체 조회API

1. 회원의 ID를 통해 회원 정보를 조회합니다. 조회된 엔티티 객체를 MemberDto로 변환하여 반환함으로써 필요한 정보만 클라이언트에게 전달합니다.

2. 전체 조회는 회원 전체를 리스트로 조회하며, 모든 회원의 정보를 List<MemberDto> 형태로 반환하여 간결하게 조회할 수 있도록 합니다.

// 1. 회원 정보 단일 조회
    @GetMapping("/member/{memberId}")
    public MemberResponseDto findOne(@PathVariable Long memberId) {
        // 멤버 검색
        Member member = memberService.findOne(memberId);

        return MemberResponseDto.fromEntity(member); // 변환 코드 간소화
    }

// 2. 전체 회원 조회
    @GetMapping("/members")
    public Result findMembers() {
        List<Member> findMembers = memberRepository.findAll();
        List<MemberResponseDto> collect = new ArrayList<>();

        for (Member member : findMembers) {
            collect.add(MemberResponseDto.fromEntity(member));
        }

        return new Result<>(collect.size(), collect);
    }

 

memberId가 52번인 회원 단일 조회 성공
회원 전체 조회

 

 

1-3. 회원 수정 API

ID로 회원을 조회한 뒤, 수정 요청이 들어온 데이터를 사용해 회원 정보를 업데이트합니다.

// 회원 수정
    @PutMapping("/editMember/{memberId}")
    public MemberResponseDto editMember(
            @PathVariable Long memberId,
            @Validated @RequestBody MemberDto memberDto) {

        memberService.update(memberId, memberDto);

        Member findMember = memberService.findOne(memberId);
        return MemberResponseDto.fromEntity(findMember); // 변환 코드 간소화
    }

memberId가 52번인 회원 수정 성공

 

 

1-4. 회원 삭제 API

회원의 ID를 통해 해당 회원을 삭제하며, 성공 시 간단한 메시지를 반환합니다.

// 회원 탈퇴
    @DeleteMapping("/delete/{memberId}")
    public String delete(@PathVariable Long memberId) {
        memberService.delete(memberId);
        return "삭제 완료";
    }

 

MemberId가 52번인 회원 삭제
삭제 후 전체 회원 조회

 

2. 트러블 슈팅:

DTO 미사용에 따른 리팩토링

초기에는 Member 엔티티를 그대로 노출하는 방식으로 데이터를 주고받았습니다. 하지만 이를 그대로 사용할 경우 다음과 같은 문제가 발생할 수 있었습니다:

  • 보안 문제: 엔티티에는 데이터베이스 필드에 대한 세부 정보가 포함될 수 있습니다. 이를 그대로 노출하면 민감한 정보가 외부에 노출될 가능성이 있습니다.
  • 유연성 부족: 클라이언트와 서버 간의 데이터 형식이 변할 때, 엔티티의 변경이 서비스 전체에 영향을 줄 수 있습니다.

이를 해결하기 위해 **DTO(Data Transfer Object)**를 사용하여 엔티티의 일부 속성만 클라이언트에 전달하도록 리팩토링을 진행했습니다.

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class MemberDto {

    private Long id;
    @NotBlank(message = "유저 이름은 공백일 수 없습니다.")
    private String username;

    @NotNull(message = "비밀번호는 필수 항목입니다.")
    @Size(min = 8, max = 16, message = "비밀번호는 8자 이상, 16자 이하여야 합니다.")
    private String password;

    @NotNull(message = "이메일은 필수 항목입니다.")
    @Email(message = "이메일 형식이 올바르지 않습니다.")
    private String email;

    public static MemberDto fromEntity(Member entity){
        return new MemberDto().builder()
                .username(entity.getUsername())
                .password(entity.getPassword())
                .email(entity.getEmail())
                .build();
    }
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class MemberResponseDto {
    @JsonIgnore
    private Long id;

    @NotBlank(message = "유저 이름은 공백일 수 없습니다.")
    private String username;

    @NotNull(message = "이메일은 필수 항목입니다.")
    @Email(message = "이메일 형식이 올바르지 않습니다.")
    private String email;

    public static MemberResponseDto fromEntity(Member entity){
        return new MemberResponseDto().builder()
                .username(entity.getUsername())
                .email(entity.getEmail())
                .build();
    }
}

이후, MemberDto를 통해 필요한 정보만을 선택적으로 제공함으로써 보안성을 높이고 코드의 유연성을 개선했습니다.

 

마무리

이번 회원 CRUD 구현 과정에서 안전하고 견고한 코드 구조의 중요성을 알 수 있었습니다. 향후 인증과 인가 기능을 추가하며 더 안정적인 API를 구축할 예정입니다.