🎁 모든 강의 30% + 무료 강의 선물🎁

[인프런 워밍업 클럽 3기 백엔드 ]발자국 3주차

[인프런 워밍업 클럽 3기 백엔드 ]발자국 3주차

 

해당 글은 ‘입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기(정보근)’ 강의 를 수강하고 작성한 내용입니다.

https://www.inflearn.com/course/입문자-spring-boot-kotlin-포트폴리오/dashboard

📝 강의 내용 정리


[실습] 공통 개발 - Exception과 Advice

오류의 종류

  • Throwable: 오류 최상위 클래스

  • Error: 애플리케이션이 대응할 수 없는 오류

  • Exception: 애플리케이션에서 대응할 수 있는 오류(개발자가 예외 처리를 해줘야함)

    • UncheckedException: RuntimeException을 상속하는 모든 예외, Transactional에서 롤백의 대상이 됨

    • CheckedException: Exception을 상속하며 RuntimeException이 아닌 모든 예외. 롤백을 하고 싶은 경우 별도로 지정해줘야하고 try-catch를 이용해 반드시 예외처리를 해줘야함

관련 어노테이션

  • @ExceptionHandler: 컨트롤러에서 던지 예외를 잡아 처리해주는 역할. 컨트롤러마다 예외를 처리하는 중복 코드를 작성할 필요없이, 같은 예외를 공통적으로 처리할 수 있음. (컨트롤러 클래스에 선언할 경우에만 동작!)

  • @ControllerAdvice: 범위 내의 모든 컨트롤러 클래스에 @ExceptionHandler를 공통적으로 적용해줌. 컨트롤러 클래스는 세분화 되었지만, 예외는 똑같이 처리해야 할 경우 사용. 예외별로 다른 뷰를 리턴할 수 있으며 @ResponseBody를 붙여 리턴 값으로 응답할 수 도있음

    • @RestControllerAdvice: @ControllerAdvice와 @ResponseBody가 같이 선언되어 있음

코드 설명

  • AdminApiControllerAdvice

    인터셉터와 비슷, 예외처리에 특화된 인터셉터

    컨트롤러가 예외를 던지면 해당 예외를 잡아서 대응해주는 컨트롤러

    @ExceptionHandler
    fun handleException(e: AdminException): ResponseEntity<String>{
    
    }
    
    • 해당 메서드를 컨트롤러안에 넣어도 동작하는데, 공통된 에러 처리를 위해서 따로 빼는게 좋음(중복 방지, 관리 용이)

  • 처리할 에러 종류→3가지

    • AdminException: 개발자가 정의하여 의도적으로 던지는 AdminException 처리용

    • MethodArgumentNotValidException: Validation에서 던지는 예외를 처리, 클라이언트에서 보낸 데이터 오류이기 때문에 BadRequest응답

    • Exception: 그 외 예외 처리. “시스템 오류”라는 메세지는 클라이언트쪽에 구체적인 오류를 숨기는 목적

[실습] 공통 개발 - DTO

ApiResponse

  • companion object{}: 이 안에 정의되는 메서드들은 static 메서드가 됨

  • static으로 생성한 이유: 여러 컨트롤러에서 동일한 응답을 편하게 사용하기 위해

  • 데이터 저장, 수정, 삭제에 대한 메세지 지정

FormElementDTO

  • html에 input 태그에 들어갈 유형을 정해주는 클래스(텍스트, 데이트, 셀렉트)

  • 서버에서 넘겨준 form을 보고 프론트에서 어떤 형태로 form을 만들지 선택할 수 있도록

    ⇒ 이게 여기서 화면까지 그려주기때문에 필요한 기능 같음

TableDTO

서비스 단에서 클라이언트로 줄 응답을 만들때 여기 DTO통해서 주면 얘가 알아서 형식 맞춰주는 DTO

  • varag: 그냥 “a”,”b” 형태로 줘도 [”a”,”b”] 같이 리스트로 처리해줌

  • 필드 종류

    • name: 테이블의 이름

    • columns: 테이블이 갖고 있는 컬럼들

    • records: 각 레코드들

  • 동작 방식

    • 테이블의 컬럼 정보와 레코드 정보를 엔티티 클래스에서 받아옴

    ex) Link 테이블 값을 조회한다고 하면

    name: Link
    columns:[link_id, name, content, is_active, created_Date_time
    																, updated_date_time]
    records:[
    	[1, "Github","<http://ddd>", true, 날짜, 날짜],
    	[2, "Instagram","<http://ddd>", true, 날짜, 날짜]
    ]
    

[실습] 공통 개발 - 인터셉터

어드민의 사이드바 메뉴를 프론트가 아닌 서버에서 관리하기 위함 목적

  • PageDTO: 소메뉴, 메뉴명과 해당 페이지로 이동하는 경로를 가짐

  • MenuDTO: 대메뉴, 메뉴명과 포함된 소메뉴를 가짐

AdminInterceptor

위의 MenuDTO와 PageDTO를 생성하여 Model에 넣어주는 역할

AdminInterceptorConfiguration

  • 인터셉터를 설정

  • /admin 이하의 모든 경로에 대해 동작하도록

[실습] 조회 개발 - 연관관계 없음

  • 서비스에서 repository를 바로 주입해서 쓰는것과 이전의 퍼싸드 패턴 둘중에 뭐가 더 좋을건가..

  • 서비스에서 DTO로 변환할때 TableDTO 사용

    TableDTO안에 만든 from 메서드가 클래스 info와 entity만 넣으면 dto로 변환해줌

  • controller

    • 먼저 Form 요소 세팅 → model에 넣어줌

    • 서비스에서 테이블 정보 받아오기 → model에 넣어줌

[실습] 조회 개발 - 연관관계 있음

  • getExperienceDetailTable 메서드 설명

    모든 experienceDetail 을 다 조회해서 화면에 뿌려주지 않고, 컨트롤러에서 상세조회 값이 true인 애들만 상세조회 버튼이 노출되고, 해당 버튼을 누르면 여기 메서드로 들어와 디테일 값을 넘겨주는 방식

    (매개변수 id가 nullable한 이유: 상세조회 버튼 누르기 전에 빈 리스트를 먼저 주기 위해서)

[실습] 삽입, 수정 API 개발 - 연관관계 없음

@NotBlank: 해당 필드가 비어있지 않아야 함을 나타냄, 비어있으면 예외 발생

  • 두번째 toEntity 메서드 → 엔티티 수정 용(엔티티 수정 시엔 해당 메서드 호출)

  • 컨트롤러에서 데이터 받아올 때, data class로 DTO받아온 다음에 해당 데이터를 toEntity()메서드를 이용해서 엔티티형으로 변환, 해당 엔티티 값을 레포지토리에 저장

[실습] 삽입, 수정, 조회 API 개발 - 연관관계 일대다

  • valid 종류

    • @field:Positive

      값이 0보다 커야함

    • @field:Min(value = 정수), @field:Max (value = 정수)

      값의 최대, 최솟값 정의

  • val detailMap = experience.details.map { [it.id](<http://it.id/>) to it }.toMap()

    {id: experienceDetail} 형식으로 만들어줌

  • experience서비스의 update메서드에 repository.save()메서드를 호출하지 않는 이유 → JPA더티체킹에 의해 트랜잭션이 종료될 때 업데이트가 자동으로 반영됨.

미션


[미션4] 조회 REST API 만들기


일단 로그인 없이 게시글을 조회해오는 부분만 구현했다. 게시물 조회는 목록 조회와 상세 조회 두가지를 구현했다

코틀린으로 구현을 하다보니 에러처리를 어떻게 할지 몰라 애를 좀 먹었었다. 현재는 exceptionHandler를 이용해 IllegalState예외만 잡아놓게 처리해놨는데, 강의를 보니 여러 에러들과 200코드를 한번에 처리하는 방법이 있었다. 미션 후 적용해 볼 생각이다.

미션 주제에 한 api에 테스트를 3개 작성하라는 내용이 있었다. 어떤 테스트를 진행할까 고민하다가, 일단 각 기능이 제대로 동작하는지 성공 테스트 2개와 존재하지 않는 아이디로 조회를 할 때 발생하는 예외 테스트를 진행해 보았다. 기능이 좀 더 구체화 되면 그에 따라 테스트도 늘 예정이다.

[미션5] 삽입, 수정, 삭제 REST API 만들기


삽입, 수정, 삭제 기능을 구현하려고 보니 조회기능 구현할땐 발목을 잡지 않았던 로그인이 문제였다. 원래 계획은 스프링 시큐리티를 붙이는 거였어서 붙이고 나면 현재 로그인된 멤버값을 받아오는건 일이 아니였는데 로그인이 구현되어있지 않는 상태에서 멤버값을 어떻게 가져오지가 걱정이였다. 맨처음 삽입 기능만 구현할 때는 임시로 Path에 받아오도록 구현했었는데 아무리 임시라고 해도 보안상에 좋지 않을꺼라는 생각이 들었다. 그래서 그나만 안전한 RequestBody에 memberId를 받아오는 걸로 구현해 놓았다.

삽입기능은 많이 복잡한 기능이 아니다보니 수월하게 작성할 수 있었는데 수정에서 문제가 발생했다. 강의에선 더티체크를 통해 값을 save하지 않아도 jpa가 레포지토리에 업데이트를 진행한다고 했었는데 테스트를 해보니 값이 변경되지 않았다. 이유를 찾아보니 메서드위에 Transactional 어노테이션을 누락했었다. 어노테이션 추가하고 나선 값이 제대로 변경되는 것이 확인되었다.

삭제 기능에서도 memberId를 전달하는 것이 문제가되었다. 수정이나 삽입은 body값이 존재하니 거기에 숨겨서 보내면 됬는데 삭제에서 requestBody를 쓰는건 Rest에 위반이 될것같았다. 그래서 하는 수 없이 삭제에서는 param값으로 memberId를 받아오는 형식으로 구현해놓았다.

테스트 작업은 일단 각 기능들이 정상적으로 동작하는지와 프로젝트 주제에 관리자와 사용자 권한 내용이 있기때문에 현재 사용자가 권한이 있는 사용자인지를 테스트하는 식으로 진행하였다.

📅3주차 회고


강의에서 어드민 개발을 시작하면서 확실히 구현하는 기능이 많아졌다. 그리고 DTO를 통합해 데이터를 한번에 변환하는 것과 에러와 성공 관련 상태코드들을 한곳에서 처리하는 부분도 다뤄져있어서 유익했다. 그동안 프로젝트에서는 예외처리 부분 코드를 가져와서 사용했었는데, 이번 기회에 직접 작성해보니 핸드러 흐름을 알기에 좋았던 것 같다.

미션을 진행하면서 아쉬운 부분이 많이 느껴졌다. 좀 다급하게 개발을 하다보니 구현하면서도 이게 맞아..? 하는 부분들이 여럿있었고, 좀더 기능을 구체화해서 리팩토링을 해봐야겠다는 생각이 들었다. 어찌보면 간단한 기능들인데도 막상 구현하려니 쉽지는 않아서 힘들었다. 그래도 하나씩 기능이 구현될 때마다 재미는 있었다. 워밍업 클럽이 끝나고 나서도 기능 구체화를 위해 개발과 리팩토링을 꾸준히 진행할 예정이다.

댓글을 작성해보세요.


채널톡 아이콘