[전명훈] sprint7#162
Hidden character warning
Conversation
There was a problem hiding this comment.
불필요한 자료는 제거하거나 gitignore에 추가해주세요
| USER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 사용자를 찾을 수 없습니다."), | ||
| DUPLICATE_USER(HttpStatus.CONFLICT, "이미 존재하는 사용자입니다."), | ||
|
|
||
| // Channel 관련 예외 | ||
| CHANNEL_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 채널을 찾을 수 없습니다."), | ||
| PRIVATE_CHANNEL_UPDATE(HttpStatus.FORBIDDEN, "비공개 채널은 수정할 수 없습니다."), | ||
| USER_NOT_IN_CHANNEL(HttpStatus.FORBIDDEN, "채널에 참여하지 않은 사용자입니다."), |
There was a problem hiding this comment.
HttpStatus 추가 좋습니다 👍 👍 👍
| @RestControllerAdvice | ||
| public class GlobalExceptionHandler { |
There was a problem hiding this comment.
Advice는 패키지로 따로 분리하시는게 어떠신가요
Exception이 늘어날수록 Handler와 섞여서 분기하기 쉽지않아보입니다.
|
|
||
| private final Instant timestamp; | ||
| private final ErrorCode errorCode; | ||
| private final Map<String, Object> details; |
There was a problem hiding this comment.
Map 형태로 Detail을 반환하도록 되어있는데요 ( 아무래도 템플릿인거 같지만 )
이런 경우 도출되는 형태가 예측이 되지않기때문에 사용하기 어려운 부분이 있습니다.
따라서 객체를 따로 지정해서 업로드 하는 게 좋아보입니다.
There was a problem hiding this comment.
다른 Entity에 대한 Exception이 안보여서 추가 설정이 필요해보입니다.
Ex) ReadStatus, BinaryContent
| public UserNotFoundException() { | ||
| super(ErrorCode.USER_NOT_FOUND); | ||
| } |
There was a problem hiding this comment.
실패한 사유에 대한 값을 입력 파라미터에 받는 것도 괜찮아 보입니다.
| .contentType(MediaType.APPLICATION_JSON) | ||
| .content(objectMapper.writeValueAsString(request))) | ||
| .andExpect(status().isCreated()) | ||
| .andExpect(jsonPath("$.name").value("Test Channel")); |
There was a problem hiding this comment.
검증 value 값을 상단에 변수로 지정후에 사용하시면 좀더 유연하게 사용가능합니다.
| // when & then | ||
| mvc.perform(delete("/api/users/{userId}", userId)) | ||
| .andExpect(status().isNotFound()) | ||
| .andExpect(jsonPath("$.code").value("USER_NOT_FOUND")); |
There was a problem hiding this comment.
Errorcode의 경우 Enum 값으로 핸들링하시면 좀더 안전하게 구현하실수 있습니다.
| entityManager.flush(); | ||
| try { Thread.sleep(10); } catch (InterruptedException e) {} | ||
| entityManager.persist(new Message("Message 3", channel, user, List.of())); | ||
| entityManager.flush(); |
There was a problem hiding this comment.
entityManager.flush(); 후에 .clear() 호출이 필요합니다.
1차 캐시 값을 clear로 제거해주는 로직을 통해 정합성을 맞추는게 가능합니다.
|
|
||
| @DisplayName("update - 성공 (PUBLIC 채널)") | ||
| @Test | ||
| void update_Success_PublicChannel() { |
There was a problem hiding this comment.
네이밍 규칙을 맞춰보는게 필요해보입니다.
테스트 케이스가 다양해지면 질수록 네이밍이 길어지면서 식별하기 어려워집니다.
| UserDto user = userService.create(new UserCreateRequest("msgUser", "msg@email.com", "pass123"), Optional.empty()); | ||
| testUserId = user.id(); |
There was a problem hiding this comment.
Fixture 로직을 한번 참고해보시면 좋을거 같아요. 주로 사용하는 객체들을
정의하여 재사용성을 늘릴수 있습니다.
| UUID userId, | ||
| UUID channelId, | ||
| Instant lastReadAt |
There was a problem hiding this comment.
Valid 로직이 필요해보입니다. ID 값이 안들어오는 케이스를 제거해주세요
|
심화 학습의 경우 한번 살펴보시고 천천히 해주시면 될거같습니다. 추가로 코드가 충돌나는 부분은 개선해서 업데이트 한번 요청드립니다. |
요구사항
기본 요구사항
프로파일 기반 설정 관리
[ ] 개발, 운영 환경에 대한 프로파일을 구성하세요.
[ ] application-dev.yaml, application-prod.yaml 파일을 생성하세요.
[ ] 다음과 같은 설정값을 프로파일별로 분리하세요.
[ ] 데이터베이스 연결 정보
[ ] 서버 포트
로그 관리
[ ] Lombok의 @slf4j 어노테이션을 활용해 로깅을 쉽게 추가할 수 있도록 구성하세요.
[ ] application.yaml에 기본 로깅 레벨을 설정하세요.
기본적으로 info 레벨로 설정합니다.
[ ] 환경 별 적절한 로깅 레벨을 프로파일 별로 설정해보세요.
SQL 로그를 보기위해 설정했던 레벨은 유지합니다.
우리가 작성한 프로젝트의 로그는 개발 환경에서 debug, 운영 환경에서는 info 레벨로 설정합니다.
[ ] Spring Boot의 기본 로깅 구현체인 Logback의 설정 파일을 구성하세요.
[ ] logback-spring.xml 파일을 생성하세요.
[ ] 다음 예시와 같은 로그 메시지를 출력하기 위한 로깅 패턴과 출력 방식을 커스터마이징하세요.
로그 출력 예시
패턴
{년}-{월}-{일} {시}:{분}:{초}:{밀리초} [{스레드명}] {로그 레벨(5글자로 맞춤)} {로거 이름(최대 36글자)} - {로그 메시지}{줄바꿈}
예시
25-01-01 10:33:55.740 [main] DEBUG c.s.m.discodeit.DiscodeitApplication - Running with Spring Boot v3.4.0, Spring v6.2.0
[ ] 콘솔과 파일에 동시에 로그를 기록하도록 설정하세요.
[ ] 파일은 {프로젝트 루트}/.logs 경로에 저장되도록 설정하세요.
[ ] 로그 파일은 일자별로 롤링되도록 구성하세요.
[ ] 로그 파일은 30일간 보관하도록 구성하세요.
[ ] 서비스 레이어와 컨트롤러 레이어의 주요 메소드에 로깅을 추가하세요.
[ ] 로깅 레벨을 적절히 사용하세요: ERROR, WARN, INFO, DEBUG
[ ] 다음과 같은 메소드에 로깅을 추가하세요:
[ ] 사용자 생성/수정/삭제
[ ] 채널 생성/수정/삭제
[ ] 메시지 생성/수정/삭제
[ ] 파일 업로드/다운로드
예외 처리 고도화
[ ] 커스텀 예외를 설계하고 구현하세요.
패키지명: com.sprint.mission.discodeit.exception[.{도메인}]
[ ] ErrorCode Enum 클래스를 통해 예외 코드명과 메시지를 정의하세요.
아래는 예시입니다. 필요하다고 판단되는 다양한 코드를 정의하세요.
예시
6ag3lzl9i-image.png
[ ] 모든 예외의 기본이 되는 DiscodeitException 클래스를 정의하세요.
클래스 다이어그램
j5vtp941a-image.png
details는 예외 발생 상황에 대한 추가정보를 저장하기 위한 속성입니다.
예시
조회 시도한 사용자의 ID 정보
업데이트 시도한 PRIVATE 채널의 ID 정보
[ ] DiscodeitException을 상속하는 주요 도메인 별 메인 예외 클래스를 정의하세요.
UserException, ChannelException 등
실제로 활용되는 클래스라기보다는 예외 클래스의 계층 구조를 명확하게 하기 위한 클래스 입니다.
[ ] 도메인 메인 예외 클래스를 상속하는 구체적인 예외 클래스를 정의하세요.
UserNotFoundException, UserAlreadyExistException 등 필요한 예외를 정의하세요.
예시
a6f585icy-image.png
[ ] 기존에 구현했던 예외를 커스텀 예외로 대체하세요.
NoSuchElementException
IllegalArgumentException
…
[ ] ErrorResponse를 통해 일관된 예외 응답을 정의하세요.
클래스 다이어그램
3gqeampkw-image.png
int status: HTTP 상태코드
String exceptionType: 발생한 예외의 클래스 이름
[ ] 앞서 정의한 ErrorResponse와 @RestControllerAdvice를 활용해 예외를 처리하는 예외 핸들러를 구현하세요.
모든 핸들러는 일관된 응답(ErrorResponse)을 가져야 합니다.
유효성 검사
[ ] Spring Validation 의존성을 추가하세요.
[ ] 주요 Request DTO에 제약 조건 관련 어노테이션을 추구하세요.
@NotNull, @notblank, @SiZe, @Email 등
[ ] 컨트롤러에 @Valid 를 사용해 요청 데이터를 검증하세요.
[ ] 검증 실패 시 발생하는 MethodArgumentNotValidException을 전역 예외 핸들러에서 처리하세요.
[ ] 유효성 검증 실패 시 상세한 오류 메시지를 포함한 응답을 반환하세요.
Actuator
[ ] Spring Boot Actuator 의존성을 추가하세요.
[ ] 기본 Actuator 엔트포인트를 설정하세요.
health, info, metrics, loggers
[ ] Actuator info를 위한 애플리케이션 정보를 추가하세요.
애플리케이션 이름: Discodeit
애플리케이션 버전: 1.7.0
자바 버전: 17
스프링 부트 버전: 3.4.0
주요 설정 정보
데이터소스: url, 드라이버 클래스 이름
jpa: ddl-auto
storage 설정: type, path
multipart 설정: max-file-size, max-request-size
[ ] Spring Boot 서버를 실행 후 각종 정보를 확인해보세요.
/actuator/info
/actuator/metrics
/actuator/health
/actuator/loggers
단위 테스트
[ ] 서비스 레이어의 주요 메소드에 대한 단위 테스트를 작성하세요.
[ ] 다음 서비스의 핵심 메소드에 대해 각각 최소 2개 이상(성공, 실패)의 테스트 케이스를 작성하세요.
[ ] UserService: create, update, delete 메소드
[ ] ChannelService: create(PUBLIC, PRIVATE), update, delete, findByUserId 메소드
[ ] MessageService: create, update, delete, findByChannelId 메소드
[ ] Mockito를 활용해 Repository 의존성을 모의(mock)하세요.
[ ] BDDMockito를 활용해 테스트 가독성을 높이세요.
슬라이스 테스트
[ ] 레포지토리 레이어의 슬라이스 테스트를 작성하세요.
[ ] @DataJpaTest를 활용해 테스트를 구현하세요.
[ ] 테스트 환경을 구성하는 프로파일을 구성하세요.
[ ] application-test.yaml을 생성하세요.
[ ] 데이터소스는 H2 인메모리 데이터 베이스를 사용하고, PostgreSQL 호환 모드로 설정하세요.
[ ] H2 데이터베이스를 위해 필요한 의존성을 추가하세요.
[ ] 테스트 시작 시 스키마를 새로 생성하도록 설정하세요.
[ ] 디버깅에 용이하도록 로그 레벨을 적절히 설정하세요.
[ ] 테스트 실행 간 test 프로파일을 활성화 하세요.
[ ] JPA Audit 기능을 활성화 하기 위해 테스트 클래스에 @EnableJpaAuditing을 추가하세요.
[ ] 주요 레포지토리(User, Channel, Message)의 주요 쿼리 메소드에 대해 각각 최소 2개 이상(성공, 실패)의 테스트 케이스를 작성하세요.
[ ] 커스텀 쿼리 메소드
[ ] 페이징 및 정렬 메소드
[ ] 컨트롤러 레이어의 슬라이스 테스트를 작성하세요.
[ ] @WebMvcTest를 활용해 테스트를 구현하세요.
[ ] WebMvcTest에서 자동으로 등록되지 않는 유형의 Bean이 필요하다면 @import를 활용해 추가하세요.
예시
@import({ErrorCodeStatusMapper.class})
[ ] 주요 컨트롤러(User, Channel, Message)에 대해 최소 2개 이상(성공, 실패)의 테스트 케이스를 작성하세요.
[ ] MockMvc를 활용해 컨트롤러를 테스트하세요.
[ ] 서비스 레이어를 모의(mock)하여 컨트롤러 로직만 테스트하세요.
[ ] JSON 응답을 검증하는 테스트를 포함하세요.
통합 테스트
[ ] 통합 테스트 환경을 구성하세요.
[ ] @SpringBootTest를 활용해 Spring 애플리케이션 컨텍스트를 로드하세요.
[ ] H2 인메모리 데이터베이스를 활용하세요.
[ ] 테스트용 프로파일을 구성하세요.
[ ] 주요 API 엔드포인트에 대한 통합 테스트를 작성하세요.
[ ] 주요 API에 대해 최소 2개 이상의 테스트 케이스를 작성하세요.
[ ] 사용자 관련 API (생성, 수정, 삭제, 목록 조회)
[ ] 채널 관련 API (생성, 수정, 삭제)
[ ] 메시지 관련 API (생성, 수정, 삭제, 목록 조회)
[ ] 각 테스트는 @transactional을 활용해 독립적으로 실행하세요.
주요 변경사항
스크린샷
멘토에게