[WIL-3] Spring 실습

이승현·2024년 7월 7일

Spring을 본격적으로 배우기 시작한지 열흘에 가까워지고 있다. 웹 개발이 처음인건 아니지만 체계적으로 배우기 시작한건 이번이 처음이라 여기 저기 부딪힌것도 많다.

오늘까지 기본적인 페이지 구성을 위한 핵심인 CRUD부터 JPA, JWT와 같은 개념을 배웠다. 근데 나는 맞으면서 배우는 타입이라 교육 과정에서 살짝 빠져서 Spring과 친해지기 위해 내 맘대로 웹 개발 목표를 완성했다. 사실 차라리 내가 생각하는데로 해보고 멘토 분이 피드백을 더 날카롭게 받고싶다는 생각이 들기도 했다. 그렇게 LV2 까지 완료했고 피드백을 받았다. 기본적으로 View까지 할 필요가 없는 과제에서 View까지 만들어 어느정도 사용자와 상호작용을 할 수 있도록 만들었다.

받은 코멘트는 다음과 같다.

1.패키지 정리 방식

패키지의 배치는 계층별, 도메인별로 나누어져 있다. 쉽게 말해 각 기능들을 어떻게 정리할 것인가에 대한 개념이다.

  • 계층별 : 각 기능별로 정리하는 방법

    controller
    ⎿ ProductController
    ⎿ MemberController
    ⎿ CartController
    service
    ⎿ ProductService
    ⎿ MemberService
    ⎿ CartService
    dao
    ⎿ ProductDao
    ⎿ MemberDao
    ⎿ CartDao
    domain
    ⎿ Product
    ⎿ Member
    ⎿ Cart

  • 도메인별 : 각 도메인별로 정리하는 방법

    product
    ⎿ controller
    ⎿ service
    ⎿ dao
    ⎿ dto
    member
    ⎿ controller
    ⎿ service
    ⎿ dao
    ⎿ dto
    cart
    ⎿ controller
    ⎿ service
    ⎿ dao
    ⎿ dto

아무래도 도메인별로 정리하는 게 깔끔할 것이라 생각하지만 지금은 이전에 기능들을 참고할 일이 많다. 계층별로 정리하고 이후에 주석까지 깔끔하게 정리를 하고 나서 도메인 형식으로 바꿔주어야 할 것 같다.

2. Restful한 API

이 부분이 제일 많이 배운 부분이다.
지금까지는 모든 기능을 컨트롤러에 때려넣고 다 처리했다. 물론 작동은 잘 하지만 컨트롤러가 복잡하고 길어지게 되었다.
Restful한 API라는 개념은 이러한 기능을 분리하는 방법이다. 따라서 View를 처리할 ViewController와 API를 처리할 RestController를 잘라냈다.

또한 API의 이름도 수정했다. 이전까지 POST로 다 처리했더니 url이
backoffice/api/add/teacher
backoffice/api/delete/teacher
backoffice/api/get/teacher
이렇게 동사가 포함되었다. 그래서 이 부분에 많은 수정을 했다. 스파르타에서 알려주는 API 설계 규칙을 특히 참고했다.

이 부분을 따라했더니 오히려 깔끔하게 API를 설계할 수 있었다.

3. 컨트롤러로 넘어온 서비스 로직

이게 LV2에서 구현했던 컨트롤러다. 그저 끔찍하다

/* 도서 대출 */
    @ResponseBody
    @PostMapping("/detail/{id}/loan")
    public Map<String, Object> loanBook(@PathVariable("id") Integer id,@RequestBody Map<String,String> param) {
        /* 결과 반환을 위한 Map 초기화 */
        Map<String, Object> resultMap = new HashMap<>();
        /* 전달받은 ID 입력값 */
        String member_id = (String)param.get("member");

        /* member값이 존재하는지 확인 */
        Member booksMember = bookService.getMember(member_id);
        if(booksMember == null){
            resultMap.put("result","사용자가 존재하지 않습니다");
        }else{
            if(bookService.checkLoanable(member_id)){
                resultMap.put("result","미납 도서가 있어 대출이 불가능합니다.");
            }else{
                /* 도서가 존재하는지 확인 */
                Book book = bookService.getBook(id);
                if(book == null){
                    resultMap.put("result", "도서가 존재하지 않습니다.");
                }else{
                    /* 현재 패널티 상태인지 확인 / 날씨 값이 있으면 패널티 상태 */
                    LocalDate date = bookService.checkPanerty(member_id);
                    if(date != null){
                        resultMap.put("result","페널티 기간입니다." + date);
                    }else{
                        /* 대출 내역을 생성 */
                        if(bookService.create(new LoanRequestDTO(booksMember, book))){
                            resultMap.put("result","success");
                        }else{
                            resultMap.put("result","도서 대출 실패!");
                        }
                    }
                }
            }
        }
        /* 결과 반환 */
        return resultMap;
    }

이런 습관이 특히 Django에서 들었던것 같다. python하나에서 처리하면서 대충 쑤셔넣는 습관이 있었는데 이 부분을 깔끔하게 Service로 넘기는 작업을 해야한다.


    /* 강의 추가 */
    public ResponseEntity<String> create(LectureRequestDTO lectureRequestDTO){
        try {
            Optional<Teacher> optionalTeacher = teacherRepository.findById(lectureRequestDTO.getTeacher_id());
            if(optionalTeacher.isPresent()){
                Lecture lecture = new Lecture(lectureRequestDTO);
                lecture.setTeacher(optionalTeacher.get());
                lectureRepository.save(lecture);
                return ResponseEntity.ok("강의가 추가되었습니다.");
            }else{
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body("강사가 존재하지 않습니다.");
            }
        }catch (IllegalArgumentException e) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());

        }catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }

LV3 단계에서 그런 점을 적용했는데 Service에서 이렇게 ResponseEntity에 결과 상태와 메시지를 담아서 반환한다. 이게 확실히 ajax 단계에서 받아 처리하기 편하게 구성되었다. 무엇보다 중요한건 안그래도 복잡한 RestController가 깔끔하게 정리된다는 점이었다.

지금은 LV3까지 정리를 어느정도 마친 상태였지만 또 많은 조언을 얻어서 코드를 더 조밀하고 안정적으로 작성할 수 있도록 했으면 좋겠다.

0개의 댓글