![[인프런 워밍업 클럽 3기 / 백엔드 프로젝트] 3주차 정리](https://cdn.inflearn.com/public/files/blogs/c0cdf56b-9eb6-4e0a-93d2-62378618c5be/인프런스터디 썸네일.png)
[인프런 워밍업 클럽 3기 / 백엔드 프로젝트] 3주차 정리
공부 내용 정리
이번주에는 GET 요청을 제외한 나머지의 요청들을 개발 (어드민 서비스)에 대해 공부하고 이에 Exception에 일관성있게 처리가능하도록 하는 법을 배웠습니다.
그래서 이번 글에서는 Exception 처리에 대해서 정리해보았습니다.
커스텀 예외처리 순서
1. 커스텀 예외 클래스 계층 정의
2. 각 예외 상황에 맞는 구체적인 커스텀 예외 클래스 생성
3. 예외 발생 시 @RestControllerAdvice를 통해 공통적으로 처리
4. 적절한 HTTP 상태 코드와 메시지를 클라이언트에게 반환
이러한 구조를 통해 비즈니스 로직에서 의미 있는 예외를 던지고, 사용자에게 명확한 메시지와 상태 코드로 응답할 수 있었습니다.
내용
abstract class MementoCapsException
모든 커스텀 예외의 공통 부모 클래스 정의
httpStatus를 저장하여 어떤 HTTP 상태 코드로 응답할지를 정의
RuntimeException을 상속받아 체크 예외가 아닌 언체크 예외
class MementoCapsNotFoundException(message: String)
각각의 예외 상황에 맞는 클래스들이 MementoCapsException을 상속받아 정의
프로젝트에서 발생할 수 있는 예외인 NOT_FOUND, BAD_REQUEST, FORBIDDEN, INTERNAL_SERVER_ERROR 에 대해 정의했으며 각각 파라미터로 넘겨받은 메시지를 부모클래스의 메시지로 넘겨주었습니다.
class PresentationApiControllerAdvice
@RestControllerAdvice 를 선언한 예외처리 컨트롤러 입니다
애플리케이션 전역에서 발생한 익셉션은 해당 컨트롤러로 넘겨받아 처리하게됩니다.
클래스 안에 @ExceptionHandler 를 가진 함수를 선언하여 예외 타입에 따라 다른 처리 로직을 실행하였습니다.
미션 5
미션 5는 삽입, 수정, 삭제 REST API를 만들어 각각 테스트케이스를 3개 이상씩 만들어 제출하는 것 이였습니다.
해당 프로젝트에서는 중심적인 데이터 베이스 엔티티의 관계가 N:M 관계로 이루어져있었습니다.
따라서 중간에 연결 테이블 엔티티가 존재했습니다.
그래서 처음 저장 로직을 작성할때 다음과 같은 고민이 생겼습니다.
문제 상황
타임캡슐을 처음 생성하면 해당 캡슐의 작성자가 OWNER 로써 관계 테이블 (User_Capsule)에 등록되어야 했습니다.
즉 타임캡슐 테이블 데이터 생성과 동시에 작성자_캡슐 테이블의 데이터도 저장되어야 했습니다.
이때 처음에는 Capsule 엔티티에서 생성자 파라미터로 writer: User를 넘긴 뒤, 내부에서 작성자 정보를 UserCapsule로 매핑하는 방식을 사용했습니다.
하지만 실제로 제가 설계한 테이블은 Capsule 엔티티에는 writer 필드가 존재하지 않고, UserCapsule(role = OWNER)로만 작성자를 식별하는 구조였기에 구조와 맞지않고, 엔티티의 책임이 커진다는 느낌을 받아 수정해보았습니다.
해결 방법
UserCapsule은 capsule.id, user.id가 모두 필요한 관계이므로
캡슐을 먼저 저장 → 관계를 이후에 저장하는 방식이 더 명확하다고 판단했습니다.
따라서 변경된 저장 순서는 다음과 같습니다.
캡슐 생성 로직 실행 1. 캡슐 테이블에 (작성자 정보 없이 순수한 캡슐 데이터만 있는) 캡슐 정보 저장 2. 입력받은 작성자 아이디를 가지고 user 테이블에서 user 정보 얻기 3. 저장된 캡슐 데이터에서 방금 작성한 캡슐 정보 얻기 4. 캡슐정보와 유저 정보를 매핑 (Role = OWNER)후 저장
이렇게 하게되면 UserCapsuleRepository의 save 로 멤버와 작성자 모두 처리가 가능합니다.
즉 서비스 단에서 "캡슐 저장"이라는 비지니스 로직은 다음과 같이 정의됩니다.
캡슐 데이터를 저장한다.
작성자 데이터를 가져온다.
캡슐 데이터와 작성자 데이터를 매핑하여 OWNER라는 Role 값으로 유저캡슐 데이터에 저장한다.
이렇게 구조를 바꾸니 멤버를 추가할때도 엔티티의 책임은 덜어내고 비슷한 방식으로 처리가 가능했습니다.
캡슐 데이터를 불러옵니다.
초대된 사용자 데이터를 불러옵니다.
두 데이터를 매핑하여 MEMBER 라는 Role 값으로 유저 캡슐 데이터에 저장합니다.
이렇게 정립한 의존성 규칙을 유지하며 나머지 기능들도 작성할 수 있었습니다.
테스트 코드 작성
테스트 코드는 예외처리에 정의된 부분들에 대해서 테스트 할 수 있었습니다.
정상적인 데이터 처리 케이스
Vaild에 위반되어 400 에러가 발생하는 케이스
url Path에 들어갈 값을 잘못 전달하여 404에러가 발생하는 케이스
request Body값이 잘못 전달되어 400 에러가 발생하는 케이스
해당 기능을 허용하지 않는 role을 가진 멤버가 기능을 요청할 경우 403에러를 발생하는 케이스 등
각 기능에 맞게 테스트 케이스를 작성하였습니다.
회고
GOOD
설계 시 발생할 에러들에 대해서 미리 설계 문서에도 정리해두었었는데, 이 덕에 테스트 케이스를 작성하는데 많은 도움이 되었습니다.
PROBLEM
각 레이어들에서 어느정도의 기능을 구현하는 것이 책임을 올바르게 분리하는 것인지 고민하는 과정이 어려웠습니다. 아직도 어떠한 방법이 맞다라고 확신할 수 없지만, 그래도 조금씩 규칙을 가지고 진행해보았습니다.
초대코드를 생성하는 기능이 있는데 이 기능은 요청이 들어오면 초대 코드 문자열을 자동으로 생성하는 기능이라 어떻게 테스트를 해야할지 고민이 되었습니다.
TRY
초대 코드 테스트 코드를 다시 작성해보기
댓글을 작성해보세요.