Skip to content

[백은호] Sprint7 #204

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: 백은호
Choose a base branch
from

Conversation

BackEunHo
Copy link
Collaborator

@BackEunHo BackEunHo commented Jun 29, 2025

기본 요구사항

프로파일 기반 설정 관리

  • 개발, 운영 환경에 대한 프로파일을 구성하세요.
    • application-dev.yamlapplication-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 클래스를 통해 예외 코드명과 메시지를 정의하세요.

      • 아래는 예시입니다. 필요하다고 판단되는 다양한 코드를 정의하세요.

      • 예시

    • 모든 예외의 기본이 되는 DiscodeitException 클래스를 정의하세요.

      • 클래스 다이어그램

      • details는 예외 발생 상황에 대한 추가정보를 저장하기 위한 속성입니다.

        • 예시
          • 조회 시도한 사용자의 ID 정보
          • 업데이트 시도한 PRIVATE 채널의 ID 정보
    • DiscodeitException을 상속하는 주요 도메인 별 메인 예외 클래스를 정의하세요.

      • UserExceptionChannelException 등
      • 실제로 활용되는 클래스라기보다는 예외 클래스의 계층 구조를 명확하게 하기 위한 클래스 입니다.
    • 도메인 메인 예외 클래스를 상속하는 구체적인 예외 클래스를 정의하세요.

      • UserNotFoundException, UserAlreadyExistException 등 필요한 예외를 정의하세요.

      • 예시

  • 기존에 구현했던 예외를 커스텀 예외로 대체하세요.

    • NoSuchElementException
    • IllegalArgumentException
  • ErrorResponse를 통해 일관된 예외 응답을 정의하세요.

    • 클래스 다이어그램

    • 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를 활용해 테스트 가독성을 높이세요.

슬라이스 테스트

두개의 테스트 메서드를 통해

  • [x ] 레포지토리 레이어의 슬라이스 테스트를 작성하세요.
    • [ ] @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을 활용해 독립적으로 실행하세요.
    • [ ]

심화 요구사항

MDC를 활용한 로깅 고도화

  • 요청 ID, 요청 URL, 요청 방식 등의 정보를 MDC에 추가하는 인터셉터를 구현하세요.
    • 클래스명: MDCLoggingInterceptor
    • 패키지명: com.**.discodeit.config
    • 요청 ID는 랜덤한 문자열로 생성합니다. (UUID)
    • 요청 ID는 응답 헤더에 포함시켜 더 많은 분석이 가능하도록 합니다.
      • 헤더 이름: Discodeit-Request-ID
  • [ ] WebMvcConfigurer를 통해 MDCLoggingInterceptor를 등록하세요.
    • 클래스명: WebMvcConfig
    • 패키지명: com.**.discodeit.config
  • Logback 패턴에 MDC 값을 포함시키세요.
    • 로그 출력 예시

      # 패턴
      {년}-{월}-{일} {시}:{분}:{초}:{밀리초} [{스레드명}] {로그 레벨(5글자로 맞춤)} {로거 이름(최대 36글자)} [{MDC:요청ID} | {MDC:요청 메소드} | {MDC:요청 URL}] - {로그 메시지}{줄바꿈}
      
      # 예시
      25-01-01 10:33:55.740 [main] DEBUG o.s.api.AbstractOpenApiResource [827cbc0b | GET | /v3/api-docs] - Init duration for springdoc-openapi is: 216 ms
      

Spring Boot Admin을 활용한 메트릭 가시화

  • Spring Boot Admin 서버를 구현할 모듈을 생성하세요.

    • IntelliJ 화면 참고

    • 모듈 정보는 다음과 같습니다.

    • 의존성

  • admin 모듈의 메인 클래스에 @EnableAdminServer 어노테이션을 추가하고, 서버는 9090번 포트로 설정합니다.

    import de.codecentric.boot.admin.server.config.EnableAdminServer;
    
    @SpringBootApplication
    @EnableAdminServer
    public class AdminApplication {
        public static void main(String[] args) {
            SpringApplication.run(AdminApplication.class, args);
        }
    }
    # application.yaml
    spring:
        application:
            name: admin
    server:
        port: 9090
    
  • admin 서버 실행 후 [localhost:9090/applications](http://localhost:9090/applications) 에 접속해봅니다.

  • discodeit 프로젝트에 Spring Boot Admin Client를 적용합니다.

    • 의존성을 추가합니다.

      dependencies {
          ...
          implementation 'de.codecentric:spring-boot-admin-starter-client:3.4.5
      }
      
    • admin 서버에 등록될 수 있도록 설정 정보를 추가합니다.

      # application.yml
      spring:
        application:
          name: discodeit
          ...
        boot:
          admin:
            client:
              instance:
                name: discodeit
      ...
      
      # application-dev.yml
      spring:
        application:
          name: discodeit
          ...
        boot:
          admin:
            client:
              url: http://localhost:9090
      ...
      
      # application-prod.yml
      spring:
        application:
          name: discodeit
          ...
        boot:
          admin:
            client:
              url: ${SPRING_BOOT_ADMIN_CLIENT_URL}
      ...
      
    • discodeit 서버를 실행하고, admin 대시보드에 discodeit 인스턴스가 추가되었는지 확인합니다.

  • admin 대시보드 화면을 조작해보면서 각종 메트릭 정보를 확인해보세요.

    • 주요 API의 요청 횟수, 응답시간 등
    • 서비스 정보

테스트 커버리지 관리

  • 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% 이상의 코드 커버리지를 달성하세요.

스크린샷

단위 테스트 결과
image

멘토에게

  • 기본 요구사항 충족 후 다시 제출합니다.(슬라이스 테스트, 통합 테스트 추가)

BackEunHo added 14 commits June 23, 2025 16:12
- .gitignore 파일에 로그 및 바이너리 컨텐츠 스토리지 관련 항목 추가
- 개발 환경(application-dev.yaml) 및 프로덕션 환경(application-prod.yaml) 설정 파일 추가
- 기본 application.yaml 파일에서 데이터베이스 연결 설정 분리 및 dev 프로파일 활성화
- API 요청 및 응답에 대한 디버그 및 정보 로그 추가
- logback-spring.xml 파일 생성하여 로그 설정 추가
- CustomException 클래스를 삭제하고 DiscodeitException 클래스를 새로 추가하여 예외 처리 구조를 개선
- ErrorCode 열거형을 추가하여 다양한 예외 코드 및 메시지를 정의
- ErrorResponse 클래스를 추가하여 일관된 예외 응답 형식 제공
- GlobalExceptionHandler에서 DiscodeitException 및 관련 예외 처리 로직을 통합하여 클라이언트에 구조화된 오류 정보 제공
- 사용자 도메인 관련 예외의 기본 클래스인 UserException 클래스 추가
- 중복 사용자 생성 시 발생하는 DuplicateUserException 클래스 추가
- 비밀번호 오류 시 발생하는 InvalidPasswordException 클래스 추가
- 사용자를 찾을 수 없을 때 발생하는 UserNotFoundException 클래스 추가
- 채널 도메인 관련 예외의 기본 클래스인 ChannelException 추가
- 채널을 찾을 수 없을 때 발생하는 ChannelNotFoundException 클래스 추가
- 비공개 채널 수정 시도 시 발생하는 PrivateChannelUpdateException 클래스 추가
- 메시지 도메인 관련 예외의 기본 클래스인 MessageException 추가
- 메시지를 찾을 수 없을 때 발생하는 MessageNotFoundException 클래스 추가
- 다양한 생성자 및 정적 팩토리 메서드 구현
- 바이너리 콘텐츠 도메인 관련 예외의 기본 클래스인 BinaryContentException 추가
- 바이너리 콘텐츠를 찾을 수 없을 때 발생하는 BinaryContentNotFoundException 클래스 추가
- 파일 삭제 실패 시 발생하는 FileDeleteFailedException 클래스 추가
- 파일 업로드 실패 시 발생하는 FileUploadFailedException 클래스 추가
- 각 예외 클래스에 다양한 생성자 및 정적 팩토리 메서드 구현
- 사용자 상태 도메인 관련 예외의 기본 클래스인 UserStatusException 추가
- 중복된 사용자 상태 생성 시도 시 발생하는 DuplicateUserStatusException 클래스 추가
- 잘못된 사용자 상태 조회/수정 시 발생하는 InvalidUserStatusException 클래스 추가
- 사용자 상태를 찾을 수 없을 때 발생하는 UserStatusNotFoundException 클래스 추가
- 각 예외 클래스에 다양한 생성자 및 정적 팩토리 메서드 구현
- 읽기 상태 도메인 관련 예외의 기본 클래스인 ReadStatusException 추가
- 중복된 읽기 상태 생성 시도 시 발생하는 DuplicateReadStatusException 클래스 추가
- 읽기 상태를 찾을 수 없을 때 발생하는 ReadStatusNotFoundException 클래스 추가
- 각 예외 클래스에 다양한 생성자 및 정적 팩토리 메서드 구현
- BinaryContentCreateRequest, LoginRequest, MessageUpdateRequest, ReadStatusCreateRequest, ReadStatusUpdateRequest, UserCreateRequest, UserStatusCreateRequest, UserStatusUpdateRequest, UserUpdateRequest에 유효성 검사 어노테이션 추가
- 필수 필드에 대한 메시지 정의 및 최대/최소 길이 제한 설정
- AuthController, ReadStatusController, UserController의 요청 DTO에 @Valid 어노테이션 추가하여 유효성 검사 적용
- 사용자 생성 및 수정, 읽기 상태 생성 및 수정 시 유효성 검사를 통해 데이터 무결성 강화
- CustomException 클래스를 삭제하고 각 도메인별로 세분화된 예외 클래스를 추가하여 예외 처리 구조를 개선
- 사용자, 채널, 메시지, 메시지 읽기 상태, 바이너리 콘텐츠 관련 예외 클래스에 정적 팩토리 메서드 추가
- build.gradle에 Actuator 의존성 추가
- application-dev.yaml에 개발 환경에 맞는 Actuator 설정 및 데이터베이스 정보 추가
- application-prod.yaml에 운영 환경에 맞는 Actuator 설정 및 데이터베이스 정보 추가
- application.yaml에 기본 Actuator 설정 및 애플리케이션 정보 추가
- BasicChannelService, BasicMessageService, BasicUserService에 대한 단위 테스트 클래스 추가
- 각 서비스의 주요 메서드에 대한 성공 및 실패 케이스 테스트 구현
- 테스트 데이터 생성을 위한 TestDataBuilder 및 상수 정의를 위한 TestConstants 클래스 추가
@BackEunHo BackEunHo requested a review from IDontHaveBrain June 29, 2025 14:31
@BackEunHo BackEunHo added 미완성🛠️ 스프린트 미션 제출일이지만 미완성했습니다. 죄송합니다. 순한맛🐑 마음이 많이 여립니다.. labels Jun 29, 2025
BackEunHo added 2 commits July 1, 2025 10:15
- 채널 생성 시 중복된 참가자가 있을 경우 발생하는 DuplicateParticipantsException 클래스 추가
- GlobalExceptionHandler의 예외 클래스별 핸들러를 하나의 핸들러로 통합 처리하도록 변경
- BasicChannelService에서 참가자 검증 로직을 수정하여 중복 참가자 발생 시 해당 예외를 던지도록 변경
- BasicChannelServiceTest에서 중복 참가자 예외에 대한 테스트 케이스 추가
- 바이너리 콘텐츠 생성, 조회, 삭제 메서드에 대한 성공 및 실패 케이스 테스트 구현
- 사용자 상태 생성, 조회, 업데이트, 삭제 메서드에 대한 성공 및 실패 케이스 테스트 구현
Copy link
Collaborator

@IDontHaveBrain IDontHaveBrain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

단위 테스트를 작성하기 시작한 점이 훌륭하십니다!
피드백 내용과 추가적으로 최종 테스트는 결국 실제 비즈니스 로직을 실행하는 통합테스트로 지금처럼 차근차근 진행하여 보시길 추천드립니다.

import lombok.Getter;

@Getter
public class DiscodeitException extends RuntimeException {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

각 케이스별 커스텀 예외를 구조화하고 시도하신 점은 인상깊습니다.
하지만, 현재 DiscodeitException 을 제외한 XXXException 들은 오버엔지니어링 내지 불필요하여 보입니다.

대부분의 커스텀 예외 핸들러(handleUserNotFoundException, handleDuplicateUserException 등)의 내부 로직이 거의 동일합니다.
HttpStatus를 각 메소드에서 명시적으로 지정하고 있지만, 이 정보는 이미 ErrorCode Enum 안에 들어있습니다.

새로운 로직과 기능이 추가되어 새로운 예외 타입을 추가할 때마다 핸들러 메소드를 계속 추가해야 하며 GlobalExceptionHandler 또한 수정이 필요하므로 매우 번거로울 것으로 보입니다.

현재 코드베이스에선 DiscodeitException 하나만으로 충분하지 않을까요 ?
커스텀 메세지는 생성자나 팩토리메서드를 통해 매개변수로 받으셔도 되고, ErrorCode 상 메세지에 플레이스 홀더("{}는 이미 존재하는 사용자입니다.", "{user}는 이미 존재하는 사용자입니다.")를 사용하여 치환하여도 됩니다.

throw new CustomException.BinaryContentNotFoundException("파일을 읽는 중 오류가 발생했습니다: " + e.getMessage());
log.error("프로필 파일 읽기 실패 - 파일명: {}, 오류: {}",
profileFile.getOriginalFilename(), e.getMessage());
throw BinaryContentNotFoundException.of();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IOException 은 "파일을 찾을 수 없는" 문제가 아니라 "파일을 읽는 도중 입출력 오류가 발생한" 상황입니다.
FileUploadFailedException 이 조금 더 적합하지 않을까 생각됩니다!

추가적으로 차후 시간이 되신다면 현재 여러 컨트롤러에서 바이너리를 받는 경우 BinaryContentCreateRequest 로 요청을 변환하는 코드가 매우 중복 및 산재되어 있습니다.
Util성 정적 메서드로 빼셔도 좋고, CommonController 를 정의하여 모든 컨트롤러에서 상속받아도 됩니다.
현재는 제공된 프론트 페이지에 spec을 맞추느라 어쩔 수 없는 부분이 있지만,
RequestDto 안에서 MultipartFile 을 받을 수 있습니다!

BackEunHo added 5 commits July 2, 2025 13:14
- build.gradle에 H2 데이터베이스 의존성 추가
- application-test.yaml 파일 생성하여 테스트 환경에 맞는 데이터베이스 설정 및 로깅 구성 추가
…트 추가

- UserStatusRepository에 사용자 ID로 상태를 삭제하는 deleteByUserId 메서드 추가
- ChannelRepositoryTest, MessageRepositoryTest, ReadStatusRepositoryTest, UserRepositoryTest, UserStatusRepositoryTest 파일 생성 및 각 테스트 케이스 구현
- 기존의 *ServiceTest에서 중복된 import 문 제거
- MockBean 대신 Mockito로 변경
- MissingServletRequestParameterException 및 MethodArgumentTypeMismatchException에 대한 핸들러 메서드 추가
- BaseIntegrationTest, ChannelIntegrationTest, MessageIntegrationTest, UserIntegrationTest 파일 생성 및 각 테스트 케이스 구현
- TestDataBuilder에 사용자 및 채널 생성 요청 메서드 추가
- 각 API의 성공 및 실패 케이스에 대한 테스트 구현
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
미완성🛠️ 스프린트 미션 제출일이지만 미완성했습니다. 죄송합니다. 순한맛🐑 마음이 많이 여립니다..
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants