취뽀몽

[Spring] 회원가입 REST API 리팩토링 본문

spring

[Spring] 회원가입 REST API 리팩토링

허몽구 2023. 3. 12. 02:43

오늘은 예전에 진행했던 프로젝트를 리팩토링 하려고 한다.

사실 리팩토링 관련한 지식이 거~의 없어서... 리팩토링이라고 해도 될 지는 모르겠지만...

더 효율 좋은 코드로, 프론트가 보기 편하도록 바꿔보도록 하겠다. 

오늘은 회원가입 API를 수정하려고 한다.

 

수정 전

1. Entity

@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name="User")
@AllArgsConstructor
@Builder
public class User extends BaseTimeEntity {

    @Id
    @Column(name="user_id", unique = true)
    private String id; // 아이디

    @Column(name = "user_pw", unique = true)
    private String password; // 비밀번호

    @Column(name = "user_checkpw")
    private String checkpassword; // 비밀번호 확인

    @Column(name = "user_nickname", unique = true, length = 20)
    private String nickname; // 닉네임

    private String date; // 분리수거 버리는 날 지정

}

 

2. Reposotory

@Repository
public interface UserRepository extends JpaRepository<User, String> {

    Optional<User> findById(String id);

    Optional<User> findByNickname(String nickname);
}

 

3. Service

@Service
@Transactional
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;


    public void save(UserDto userDto) {
        userDto.setPassword(passwordEncoder.encode(userDto.getPassword()));
        userRepository.save(userDto.toEntity());
    }

 

4. Controller

@PostMapping("/users/new-user") // 회원가입
    public ResponseEntity<String> join(@RequestBody UserDto userDto) throws UserPasswordMismatchException {
        if (!userDto.getPassword().equals(userDto.getCheckpassword())) {
            throw new UserPasswordMismatchException("패스워드가 일치하지 않습니다.");
        }
        userService.save(userDto);
        return ResponseEntity.ok("join success");
    }

 

5. UserDto

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserDto {
    private String id; // 유저 아이디
    private String password; // 유저 비밀번호
    private String checkpassword; // 비밀번호 확인
    private String nickname; // 유저 닉네임
    private String date; // 쓰레기 버리는 날

    public User toEntity(){
        return User.builder()
                .id(id)
                .password(password)
                .checkpassword(checkpassword)
                .nickname(nickname)
                .date(date)
                .build();
    }
}

 

딱봐도 어노테이션 무지성으로 갖다 붙이고 효율 좋은 코드보단 이상하긴 해도 돌아가는 코드에 집중한 것 같아 보인다...

@Builder랑 @AllArgsConstructor 둘 다 클래스 앞에 선언해 둔 거 봐도 봐도.. 이런 내가... .싫다...(또룩)

이유는 https://jiyoungmerong.tistory.com/49 여기 밑 부분에 작성해뒀다!

 

또, 포스트맨 돌려봐도 "Join Success" 라는 문구만 띄워준다. 성의없는 백엔드...

그래서 다음과 같이 코드를 고쳐봤다.

 

수정 후

1. Entity

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name="User")
public class User extends BaseTimeEntity {
    @Id
    @Column(name="user_id", unique = true)
    private String id; // 아이디

    @Column(name = "user_pw", unique = true)
    private String password; // 비밀번호

    @Column(name = "user_nickname", unique = true, length = 20)
    private String nickname; // 닉네임

    private String date; // 분리수거 버리는 날 지정

    @Builder
    public User(String id, String password, String nickname, String date){
        this.id = id;
        this.password = password;
        this.nickname = nickname;
        this.date = date;
    }
}

@NoArgsConstructor(access = AccessLevel.PROTECTED)를 설정해서 무분별한 객체 생성을 막았다. 

 

예를 들어 Service단에서 

public void create(){
    User user = new User();
    user.setId(user.getId());
}

이런 식으로 무분별하게 객체 생성을 하지 못 하도록 막는 것이다.

실제로 'User()' has protected access in '...' 이런 식으로 오류가 뜬다.

 

또한, @Build와 @NoArgsConstructor(access = AccessLevel.PROTECTED)은 함께 사용하지 못해서 생성자에 @Builder를 설정해줬다.

@NoArgsConstructor(access = AccessLevel.PROTECTED) 설정을 하지 않았더라도 @Builder는 생성자에 설정해두는 것이 좋다!

 

실제로 @Builder와 @NoArgsConstructor(access = AccessLevel.PROTECTED)를 함께 사용하면 다음과 같은 오류가 발생한다.

이 부분은 밑의 블로그에서 설명을 너무 잘 써두셨다! 참고하세요..

 

 

@NoargsConstructor(AccessLevel.PROTECTED) 와 @Builder

@NoargsConstructor(AccessLevel.PROTECTED) 와 @Builder를 함께 사용할때 주의할 점에 대해서 서술합니다. "왜" 안되는지와 "왜" 이렇게 해결 할 수 있는지에 대해 집중하여 서술합니다. 1. 왜 NoargsConstructor(Access

cobbybb.tistory.com

 

checkpassword 컬럼도 삭제했는데, 굳이 비밀번호 확인까지 DB에 저장해야할까? 싶었다. (당연한 말이지만)

프론트 쪽에서 충분히 검사해줄 수 있는 문제라고 생각했기 때문에 과감히 지워줬다. 애초에 안 만들었어도 됐다...

 

2. SignupRequest

@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
@NoArgsConstructor
public final class SignupRequest { // 회원가입 요청

    private String id;

    private String password;

    private String nickname;

    private String date;
}

 

처음엔 UserDto로 요청을 보냈는데, request 패키지에 요청하는 것들로 묶어두면 더욱 보기 편할 것 같아 만들었다. 

id와 비밀번호, 닉네임, 날짜를 요청받는 DTO라고 보면 된다.

 

3. ApiResponse

@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ApiResponse<T> {
    private final int code;
    private final String message;
    private T data;

    public static ApiResponse success(SuccessStatus successStatus) {
        return new ApiResponse<>(successStatus.getHttpStatus().value(), successStatus.getMessage());
    }
}

원래는 죽어라 ResponseEntity만 적었는데, 400이나 500 에러가 났을 때 응답 반응이 성공 때와는 다르게 출력됐다.

응답 반응이 다르면 프론트가 해야할 일이 늘어나고 보기 불편하지 않을까? 라고 생각해서 찾아본 것이 '공통 응답 클래스' 였다.

이 부분에 대해서는 다음에 자세히 포스팅 하도록 하겠다...!!

생성자를 여러개 만들 수 있고, 공통적인 응답을 보낼 수 있다는 것이 장점이다.

우선 Success 응답만 만들어봤다. HttpStatus(Http 상태)와 메세지를 보내는 메소드를 만들었다. 

 

4. SuccessStatus

@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public enum SuccessStatus {

    SIGNUP_SUCCESS(HttpStatus.CREATED, "회원가입 성공!"),
    ;

    private final HttpStatus httpStatus;
    private final String message;
}

회원가입이 성공하면, 201번과 "회원가입 성공!" 이라는 메세지를 출력하는 SuccessStatus를 만들었다.

이제 SIGNUP_SUCCESS를 사용하면 201번과 "회원가입 성공!" 이라는 메세지가 출력되는 것이다.

 

5. Controller

@RestController
@RequiredArgsConstructor
public class UserController {

    @PostMapping("/users/new-user") // 회원가입
    public ApiResponse<String> join(@RequestBody SignupRequest request) {
        return ApiResponse.success(SuccessStatus.SIGNUP_SUCCESS);
    }

위에 만들었던 공통 응답 클래스를 토대로 회원가입 로직을 만들어줬다.

요청되는 값은 SignupRequest이고, 응답은 201번과 "회원가입 성공!" 이라는 메세지가 출력되도록 했다.

 

6. 포스트맨

원하는 값을 넣고 보내면,

 

위와 같이 201번과 회원가입 성공 메세지가 나온다. 

회원가입에서 굳이 보여줄 데이터는 없다고 생각돼서 안 넣었지만, 넣고싶다면 코드를 다음과 같이 수정하면 된다.

 

회원가입 응답 데이터 추가

1. SignupResponse

@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class SignupResponse {

    private String userId;
    private String nickname;
    private String date;

    public static SignupResponse of(String userId, String nickname, String date) {
        return new SignupResponse(userId, nickname, date);
    }
}

응답 데이터로 넣고 싶은 값을 선택한다.

of는 여러 매개변수를 선택할 수 있다.

 

2. Service

@Transactional
    public SignupResponse create(SignupRequest request){
        User user = User.builder()
                .id(request.getId())
                .password(passwordEncoder.encode(request.getPassword()))
                .nickname(request.getNickname())
                .date(request.getDate())
                .build();

        userRepository.save(user);

        return SignupResponse.of(request.getId(), request.getNickname(), request.getDate());
    }

 

3. Controller

@PostMapping("/users/new-user") // 회원가입
    public ApiResponse<SignupResponse> join(@RequestBody SignupRequest request) {
        return ApiResponse.success(SuccessStatus.SIGNUP_SUCCESS, userService.create(request));
    }

 

4. 포스트맨

이렇게 요청을 보내면

 

이번엔 데이터에 id, 비밀번호, 날짜가 같이 출력된다.

 

실제 데이터베이스에도 잘 저장된다.  비밀번호는 암호화를 시켜놔서 저렇게 보인다!

 


 

처음으로 프로젝트 코드를 다시 짜보는 시간을 가졌는데 예전 코드가 너무 별로였어서 그런지.. 공부할 꺼리가 굉장히 많아서 좋았다!

아직 수정해야될 부분이 생각나긴 하지만! 계속해서 수정해야겠다~~