[박정현] Sprint7#227
Conversation
- `UserService` - `UserStatusService` - `ChannelService` - `ReadStatusService` - `MessageService` - `BinaryContentService` - `AuthService`
- `BinaryContentMapper` - `ReadStatusMapper` - `UserStatusMapper`
- `BasicBinaryContentService` - `BasicReadStatusService` - `BasicUserStatusService`
- `ChannelMapper` - `MessageMapper` - `UserMapper`
- `BasicAuthService` - `BasicUserService` - `BasicChannelService` - `BasicMessageService`
…tructor` 애너테이션 추가
- message가 여러 개의 BinaryContent를 가지고 있다면 같은 message가 BinaryContent의 수만큼 출력될 수 있기 때문
…geControllerTest` 구현
…elControllerTest` 구현
…ngInterceptor` 구현
joonfluence
left a comment
There was a problem hiding this comment.
전체 요약
Spring Boot Sprint7 요구사항(프로파일 기반 설정, Logback, MDC 인터셉터, 예외 계층, Spring Validation, JaCoCo)을 전반적으로 잘 구현했습니다. 특히 MDCLoggingInterceptor와 단위/슬라이스 테스트의 완성도가 높습니다. 다만 운영 환경 시크릿 노출과 비즈니스 예외 스택 트레이스 과다 출력 문제 수정이 필요합니다.
P1. src/main/resources/application-prod.yaml — 운영 DB 자격증명 평문 노출
운영 DB 계정 정보가 소스 코드에 평문으로 커밋되어 있습니다:
username: discodeit_user
password: discodeit1234레포지토리에 한 번 올라간 시크릿은 히스토리에 영구적으로 남습니다. 환경 변수 참조 방식으로 수정해 주세요:
username: ${DB_USERNAME}
password: ${DB_PASSWORD}배포 환경에서는 .env 파일이나 서버 환경 변수, 또는 AWS Secrets Manager 등에서 주입하세요.
P3. Spring Boot Admin 클라이언트 미구현
PR 체크리스트의 Spring Boot Admin 항목이 미체크 상태이며, build.gradle에 Admin 클라이언트 의존성이 없습니다. Sprint7 요구사항에 포함된 항목이므로 구현이 필요합니다:
implementation 'de.codecentric:spring-boot-admin-starter-client:3.4.5'별도 Admin 서버를 운영하거나, application.yaml에 spring.boot.admin.client.url 설정을 추가해 주세요.
P3. 통합 테스트 미완성
PR 체크리스트에서 통합 테스트 항목이 모두 미체크로 남아 있습니다. build.gradle에 testcontainers 의존성이 이미 추가되어 있으므로, @SpringBootTest + PostgreSQL TestContainer 조합의 통합 테스트 구현까지 마무리해 주시면 좋겠습니다.
P4. src/main/resources/logback-spring.xml — <springProfile> 미사용
<springProfile> 태그가 없어 dev/prod 구분 없이 콘솔과 파일 양쪽 모두 항상 활성화됩니다. 운영 환경에서는 파일 출력만 사용하도록 분리하는 것이 좋습니다:
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</springProfile>
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</springProfile>P5. MDCLoggingInterceptor + 테스트 코드 👍
MDCLoggingInterceptor 구현이 매우 우수합니다 — 전체 UUID 사용(substring 없이 충돌 방지), 응답 시간(ms) 측정, HTTP 상태 범위별 로그 레벨 분기(5xx→error, 4xx→warn, 2xx→info), finally { MDC.clear() } 메모리 누수 방지까지 꼼꼼하게 구현되어 있습니다.
BasicUserServiceTest와 UserControllerTest도 @Nested 구조, Given/When/Then, 다양한 엣지케이스(null 입력, 중복, 파일 업로드 실패, 파일 읽기 실패 등)를 모두 커버한 점이 인상적입니다.
| log.error("[Exception] 커스텀 예외: timestamp={}, code={}, message={}, details={}", e.getTimestamp(), e.getErrorCode().name(), e.getMessage(), e.getDetails(), e); | ||
| } else { | ||
| log.warn("[Exception] 커스텀 예외: timestamp={}, code={}, message={}, details={}", e.getTimestamp(), e.getErrorCode().name(), e.getMessage(), e.getDetails(), e); | ||
| } |
There was a problem hiding this comment.
P2. 비즈니스 예외에 스택 트레이스 과다 출력
log.warn("...", e.getTimestamp(), ..., e)처럼 마지막 인자로 예외 객체 e를 전달하면, SLF4J는 자동으로 전체 스택 트레이스를 함께 출력합니다. UserNotFoundException, DuplicatedEmailException 같은 비즈니스 예외에도 수십 줄의 스택 트레이스가 찍혀 운영 로그 노이즈가 심해지고 실제 시스템 오류를 찾기 어려워집니다.
비즈니스 예외는 메시지만, 시스템 예외(Exception 핸들러)는 스택 트레이스를 출력하도록 구분해 주세요:
| } | |
| log.warn("[Exception] 커스텀 예외: timestamp={}, code={}, message={}, details={}", | |
| e.getTimestamp(), e.getErrorCode().name(), e.getMessage(), e.getDetails()); |
1. 프로젝트 마일스톤
2. 기본 요구사항
2-1. 프로파일 기반 설정 관리
application-dev.yaml,application-prod.yaml파일을 생성하세요.2-2. 로그 관리
Lombok의
@Slf4j애너테이션을 활용해 로깅을 쉽게 추가할 수 있도록 구성하세요.application.yaml에 기본 로깅 레벨을 설정하세요.info레벨로 설정합니다.환경 별 적절한 로깅 레벨을 프로파일 별로 설정해보세요.
debug, 운영 환경에서는info레벨로 설정합니다.Spring Boot의 기본 로깅 구현체인 Logback의 설정 파일을 구성하세요.
logback-spring.xml파일을 생성하세요.다음 예시와 같은 로그 메시지를 출력하기 위한 로깅 패턴과 출력 방식을 커스터마이징하세요.
로그 출력 예시
콘솔과 파일에 동시에 로그를 기록하도록 설정하세요.
{프로젝트 루트}/.logs경로에 저장되도록 설정하세요.로그 파일은 일자별로 롤링되도록 구성하세요.
로그 파일은 30일간 보관하도록 구성하세요.
서비스 레이어와 컨트롤러 레이어의 주요 메소드에 로깅을 추가하세요.
2-3. 예외 처리 고도화
커스텀 예외를 설계하고 구현하세요.
패키지명:
com.sprint.mission.discodeit.exception[.{도메인}]ErrorCodeEnum 클래스를 통해 예외 코드명과 메시지를 정의하세요.아래는 예시입니다. 필요하다고 판단되는 다양한 코드를 정의하세요.
예시
모든 예외의 기본이 되는
DiscodeitException클래스를 정의하세요.클래스 다이어그램
details는 예외 발생 상황에 대한 추가정보를 저장하기 위한 속성입니다.DiscodeitException을 상속하는 주요 도메인 별 메인 예외 클래스를 정의하세요.UserException,ChannelException등도메인 메인 예외 클래스를 상속하는 구체적인 예외 클래스를 정의하세요.
UserNotFoundException,UserAlreadyExistException등 필요한 예외를 정의하세요.예시
기존에 구현했던 예외를 커스텀 예외로 대체하세요.
NoSuchElementExceptionIllegalArgumentExceptionErrorResponse를 통해 일관된 예외 응답을 정의하세요.클래스 다이어그램
int status: HTTP 상태코드String exceptionType: 발생한 예외의 클래스 이름앞서 정의한
ErrorResponse와@RestControllerAdvice를 활용해 예외를 처리하는 예외 핸들러를 구현하세요.ErrorResponse)을 가져야 합니다.2-4. 유효성 검사
@NotNull,@NotBlank,@Size,@Email등@Valid를 사용해 요청 데이터를 검증하세요.MethodArgumentNotValidException을 전역 예외 핸들러에서 처리하세요.2-5. Actuator
Discodeit1.7.0173.4.0/actuator/info/actuator/metrics/actuator/health/actuator/loggers2-6. 단위 테스트
Mockito를 활용해 Repository 의존성을 모의(mock)하세요.BDDMockito를 활용해 테스트 가독성을 높이세요.2-7. 슬라이스 테스트
@DataJpaTest를 활용해 테스트를 구현하세요.application-test.yaml을 생성하세요.test프로파일을 활성화 하세요.@EnableJpaAuditing을 추가하세요.@WebMvcTest를 활용해 테스트를 구현하세요.WebMvcTest에서 자동으로 등록되지 않는 유형의 Bean이 필요하다면@Import를 활용해 추가하세요.2-8. 통합 테스트
@SpringBootTest를 활용해 Spring 애플리케이션 컨텍스트를 로드하세요.@Transactional을 활용해 독립적으로 실행하세요.3. 심화 요구사항
3-1. MDC를 활용한 로깅 고도화
MDCLoggingInterceptorcom.**.discodeit.configDiscodeit-Request-IDWebMvcConfigurer를 통해MDCLoggingInterceptor를 등록하세요.WebMvcConfigcom.**.discodeit.config로그 출력 예시
3-2. Spring Boot Admin을 활용한 메트릭 가시화
Spring Boot Admin 서버를 구현할 모듈을 생성하세요.
IntelliJ 화면 참고
모듈 정보는 다음과 같습니다.
의존성
admin모듈의 메인 클래스에@EnableAdminServer애너테이션을 추가하고, 서버는 9090번 포트로 설정합니다.admin서버 실행 후 localhost:9090/applications 에 접속해봅니다.discodeit 프로젝트에 Spring Boot Admin Client를 적용합니다.
dependencies { ... implementation 'de.codecentric:spring-boot-admin-starter-client:3.4.5 }admin 대시보드 화면을 조작해보면서 각종 메트릭 정보를 확인해보세요.
3-3. 테스트 커버리지 관리
JaCoCo 플러그인을 추가하세요.
plugins { id 'jacoco' } test { finalizedBy jacocoTestReport } jacocoTestReport { dependsOn test reports { xml.required = true html.required = true } }테스트 실행 후 생성된 리포트를 분석해보세요.
build/reports/jacoco경로에서 확인할 수 있습니다.com.sprint.mission.discodeit.service.basic패키지에 대해서 60% 이상의 코드 커버리지를 달성하세요.BasicUserService테스트 커버리지 98% 달성BasicChannelService테스트 커버리지 99% 달성BasicMessageService테스트 커버리지 85% 달성