ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring 프로젝트 회고(1) - 회원가입
    BackEnd 2022. 7. 16. 00:00

    동아리 후기보다 프로젝트 회고(?)를 먼저 작성하게 되었다. 

    프로젝트는 자기소개서 작성을 도와주는 경험 기록용 어플리케이션이다. 

    (1) 프로젝트 구조도

    일단 User 폴더 하위로 controller, domain, dto, service 4개로 나뉘어져 있다. Message와 Status의 경우, 클라이언트의 요청에 맞춰 응답할 상태코드 / 메시지를 어디서든 참조 가능하도록 전역 클래스로 선언해두었다. 

     

    ✅ 기본적으로 MVC 디자인 패턴은 Model, View, Controller로 구성되어 있다. 

    - Model : 어플리케이션에서 사용되는 데이터, 그 데이터를 처리하는 부분

    - View : 사용자에게 보여지는 UI

    - Controller : 사용자의 입력을 처리하는 부분

     

    이 패턴을 좀 생각하면서 프로젝트 구조를 먼저 만들었다. 

    controller는 HttpMethod들과 사용자의 요청이 들어오는 경로를 담은 파일들을 넣었다. 

    domain에는 Repository와 DB 테이블 정의해둔 파일들을 넣었다. (jpa 매핑을 위한 것들)

    dto는 view를 위한 클래스들로, service와 domain 사이 주고 받는 객체를 정의해둔 파일을 넣었다. 

    service는 controller에 맞춰 domain으로 직접 요청 보내서 응답을 받고, 그를 다시 dto형태로 가공하여 controller에 돌려준다. 

     

    (2) 회원가입

    - 로컬 회원가입으로 한 번 구현해봤다. 

    - user관련된 것들은 모두 /user/어쩌구 이런 식의 경로라 @RequestMapping의 value에 /user이 들어가 있다. 클라이언트와는 json객체로 응답한다.

     

    📌domain/User.java

    - userId가 Primary Key이다. 

    - BaseTimeEntity는 User마다 생성된 시간을 DB에서 저장해서, 따로 Config폴더에 구현해두었다. (구글링하면 이 부분은 많이 나와서 생략) 

    @Getter
    @NoArgsConstructor
    @Entity
    public class User extends BaseTimeEntity {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long userId;
    
        @Column(length=30, nullable = false, unique = true)
        private String id;
    
        @Column(length=60, nullable = false)
        private String pw;
    
        @Column(length=30, nullable = false)
        private String nickname;
    
        @Builder
        public User(String id, String pw, String nickname) {
            this.id = id;
            this.pw = pw;
            this.nickname = nickname;
        }
    }

    📌domain/UserRepository.java

    - jpa를 사용

    Optional<T> : null값일수도 있는 객체를 감싸주는 Wrapper클래스. User가 null일 수도 있기 때문에 앞에 선언해주었다. 

    밑의 함수는 id 중복검사 때문에 따로 만든건데, 기본적인 CRUD 메소드는 JpaRepository 상속시 굳이 명시하지 않아도 사용이 가능하다. 

    public interface UserRepository extends JpaRepository<User, Long> {
        Optional<User> findById(String id);
    }

    📌dto/UserJoinRequestDto.java

    - Builder 사용해서 생성. 생성자와 마찬가지로 생성 시점에 값을 채워주는 것은 똑같은데, Builder를 사용하는 것이 어느 필드에 어떤 값을 채워야하는지 좀 더 명확하다. 

    @Getter
    @NoArgsConstructor
    public class UserJoinRequestDto {
        private String id;
        private String pw;
        private String nickname;
    
        @Builder
        public UserJoinRequestDto (String id, String pw, String nickname) {
            this.id = id;
            this.pw = pw;
            this.nickname = nickname;
        }
    
        public User toEntity() {
            return User.builder()
                    .id(id)
                    .pw(pw)
                    .nickname(nickname)
                    .build();
        }
    }

    📌 dto/UserJoinResponseDto.java

    - lombok의 Data 어노테이션이다. 

    - 클라이언트 측에서 userIdx를 나중에 요청에 담아야하는 경우도 있어서 회원가입이 완료되면 응답 데이터로 userIdx를 클라이언트 측에 보내준다. 

    @Data
    public class UserJoinResponseDto {
        private Long userIdx;
    
        public UserJoinResponseDto (Long userIdx) {
            this.userIdx = userIdx;
        }
    }

    📌 service/UserService.java

    - 사실 의존관계는 AppConfig파일처럼 클래스 외부에서 주입해주는 것이 SOLID원칙을 지키는 것이라 ... 왠지리팩토링이 좀 더 필요할 것 같긴 하다. 

    - 항상 클라이언트 측에서도 null이 있으면 서버로 요청보내는 것을 금지하는 로직이 있겠지만, 서버 입장에서 항상 클라이언트를 믿으면 안되기에(?) dto를 검사하는 로직을 달았다.

    - 근데 이게 뭔가 보기 싫어서,  Post관련 API 구현할 때에는 PostSaveRequestDto 내부에 isEmpty로직을 만들어뒀다. 참고용으로 PostSaveRequestDto 코드도 첨부.. 

    public boolean isEmpty() {
            if (type == null || title == null || startDate == null || endDate == null || situation == null || action == null || learned == null || tags == null) {
                return true;
            } else {
                return false;
            }
        }

    - PasswordEncoder는 DB에 pw 쌩으로 넣으면 안되기 때문에 필요하다. jsonWebTokenProvider에 경우 로그인 로직 구현할 때 사용해서, 이번 포스팅에는 생략했다. 

    - 회원정보에서 id가 unique필드이므로 이를 검사하는 로직도 필요하다. 이미 존재하면 ALREADY_EXIST 예외처리. 

    - JPA를 상속받았으므로 .save()를 통해 Create가 된다. 

    @RequiredArgsConstructor
    @Service
    public class UserService {
        private final UserRepository userRepository;
        private final PasswordEncoder passwordEncoder;
        private final JsonWebTokenProvider jsonWebTokenProvider;
    
        @Transactional
        public UserJoinResponseDto join(UserJoinRequestDto requestDto) throws Exception {
            if (requestDto.getId().length() == 0 || requestDto.getPw().length() == 0 || requestDto.getNickname().length() == 0) {
                throw new Exception(Message.MISSING_ARGUMENT);
            } else {
                Optional<User> user = userRepository.findById(requestDto.getId());
                if (user.isEmpty() == false) {
                    throw new Exception(Message.ALREADY_EXIST);
                }
                String password = passwordEncoder.encode(requestDto.getPw());
                UserJoinRequestDto dto = new UserJoinRequestDto(requestDto.getId(), password, requestDto.getNickname());
                Long userIdx = userRepository.save(dto.toEntity()).getUserId();
                return new UserJoinResponseDto(userIdx);
            }
        }

     

    📌 controller/UserApiController.java

    - Controller의 코드는 다음과 같다. userService의 join을 호출하고, 성공할 경우 성공 응답을, 실패할 경우 실패 메시지와 어느 부분에서 예외가 발생했는지에 따라 message를 함께 보내준다. 

    @RequiredArgsConstructor
    @RestController
    @RequestMapping(value = "/user", consumes = MediaType.APPLICATION_JSON_VALUE)
    public class UserApiController {
        private final UserService userService;
        @PostMapping("join")
        public ResponseEntity join(@RequestBody UserJoinRequestDto requestDto) {
            try {
                UserJoinResponseDto userJoinResponseDto = userService.join(requestDto);
                return ResponseEntity.ok().body(ResponseDto.res(Status.OK, Message.JOIN_SUCCESS, userJoinResponseDto));
            } catch (Exception e) {
                return ResponseEntity.badRequest().body(ResponseDto.res(Status.BAD_REQUEST, e.getMessage()));
            }
        }
    /* ... */

     

    아래처럼 postman테스트를 해보면 잘 되는 것을 확인할 수 있다. 

     

    'BackEnd' 카테고리의 다른 글

    Spring 프로젝트 회고 (2) - 자주 만났던 에러들  (1) 2022.08.31
Designed by Tistory.