회원 CRUD Rest API 구현 및 리팩토링
개요
이번 글에서는 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);
}
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); // 변환 코드 간소화
}
1-4. 회원 삭제 API
회원의 ID를 통해 해당 회원을 삭제하며, 성공 시 간단한 메시지를 반환합니다.
// 회원 탈퇴
@DeleteMapping("/delete/{memberId}")
public String delete(@PathVariable Long memberId) {
memberService.delete(memberId);
return "삭제 완료";
}
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를 구축할 예정입니다.