Skip to content

[박진솔] sprint7 #213

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 5 commits into
base: 박진솔
Choose a base branch
from

Conversation

JinsolPark
Copy link
Collaborator

@JinsolPark JinsolPark commented Jul 2, 2025

기본 요구사항

프로파일 기반 설정 관리

  • 개발, 운영 환경에 대한 프로파일을 구성하세요.
    • 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 클래스를 통해 예외 코드명과 메시지를 정의하세요.
      • 아래는 예시입니다. 필요하다고 판단되는 다양한 코드를 정의하세요.
      • 예시
    • 모든 예외의 기본이 되는 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에 제약 조건 관련 어노테이션을 추구하세요.
  • 컨트롤러에 @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을 활용해 독립적으로 실행하세요.

멘토님께

  • User, Channel, Message에서 delete 메서드 테스트가 실패합니다... (서비스에서부터 오류가 나는데 서비스쪽이 문제 같은데 디버깅을 완료 못했습니다..;)
  • 레포지토리 테스트랑 심화 요구사항에는 손을 못댔습니다.. ㅠ ㅠ
  • 늦게 제출해서 죄송합니다..!!

@JinsolPark JinsolPark requested a review from dongjoon1251 July 2, 2025 11:14
@JinsolPark JinsolPark added 미완성🛠️ 스프린트 미션 제출일이지만 미완성했습니다. 죄송합니다. 순한맛🐑 마음이 많이 여립니다.. 지각제출⏰ 제출일 이후에 늦게 제출한 PR입니다. labels Jul 2, 2025
@JinsolPark JinsolPark self-assigned this Jul 2, 2025
Comment on lines 9 to 14
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Logging {

}
Copy link
Collaborator

Choose a reason for hiding this comment

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

LoggingAspect에서 사용하려고 하신듯 합니다. 다만 pointcut을 정하기 위해서 별도 애노테이션을 만들지 않아도 가능합니다. 클래스명이 Service나 Controller로 끝나는 모든 메소드에 대해 aop를 설정해서 쓰는 것도 가능해요~!

Comment on lines 33 to 49
/* API 구현 절차
* 1. 엔드포인트(End-point)
* - 엔드포인트는 URL과 HTTP 메서드로 구성됨.
* - 엔드포인트는 다른 API와 겹치지 않는(중복되지 않는) 유일한 값으로 정의할 것.
* 2. 요청(Request)
* - 요청으로부터 어떤 값을 받아야 하는지 정의.
* - 각 값을 HTTP 요청의 header, body 등 어느 부분에서 어떻게 받을지 정의.
* 3. 응답(Response) - 뷰 기반이 아닌 데이터 기반(객체 반환) 응답으로 작성.
* - 응답 상태 코드 정의
* - 응답 데이터 정의
* - (옵션) 응답 헤더 정의
* */

/* 반환값에 따라 사용되는 메서드
* 1. 논리 뷰 - ViewResolver, View
* 2. Java 객체 - HttpResponse .....
* */
Copy link
Collaborator

Choose a reason for hiding this comment

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

주석을 달게 된다면 javadoc 포맷에 맞춰서 적용해서 문서로 보여질 수 있게 시도해보시면 좋을 것 같습니다~!

Javadoc 적용 방법 정리

Javadoc은 Java 소스 코드에 포함된 주석을 기반으로 HTML 형식의 API 문서를 자동으로 생성하는 도구입니다. 아래는 Javadoc을 작성하고 생성하는 방법을 단계별로 정리한 내용입니다.


1. Javadoc 주석 작성

Javadoc 주석은 클래스, 메서드, 필드 선언 바로 위에 작성하며 /**로 시작합니다. 주요 태그는 다음과 같습니다:

  • 클래스 주석: 클래스의 역할과 용도를 설명합니다.
  • 메서드 주석: 메서드의 동작, 매개변수, 반환값 등을 설명합니다.
    • @param: 메서드 매개변수를 설명.
    • @return: 반환값을 설명.
    • @throws 또는 @exception: 발생할 수 있는 예외를 설명.
  • 필드 주석: 필드의 역할을 설명.

예시:

/**
 * 계산기 클래스입니다.
 * 기본적인 사칙연산 기능을 제공합니다.
 */
public class Calculator {

    /**
     * 두 숫자를 더합니다.
     *
     * @param a 첫 번째 숫자
     * @param b 두 번째 숫자
     * @return 두 숫자의 합
     */
    public int add(int a, int b) {
        return a + b;
    }
}

2. Javadoc 생성

Javadoc 문서를 생성하려면 JDK에 포함된 javadoc 도구를 사용하거나 IDE를 통해 생성할 수 있습니다.

(1) 명령줄에서 생성
  1. 터미널에서 프로젝트 디렉토리로 이동합니다.
  2. 다음 명령어를 실행하여 HTML 문서를 생성합니다:
    javadoc -d docs -encoding UTF-8 -charset UTF-8 -sourcepath src -subpackages com.example
    • -d docs: 문서를 저장할 디렉터리 지정.
    • -encoding UTF-8: 소스 파일 인코딩 설정.
    • -sourcepath: 소스 코드 경로 지정.
    • -subpackages: 하위 패키지 포함.
(2) IntelliJ IDEA에서 생성
  1. 메뉴에서 Tools > Generate JavaDoc 선택.
  2. Javadoc 설정 창에서 다음 항목을 지정:
    • Scope: 문서를 생성할 파일/디렉터리 범위 선택 (예: 전체 프로젝트).
    • Output Directory: 생성된 문서를 저장할 폴더 지정.
    • Visibility Level: 공개 수준 선택 (Public, Protected 등).
  3. 한글 깨짐 방지를 위해 추가 명령어 입력:
    -encoding UTF-8 -charset UTF-8 -docencoding UTF-8
    
  4. Generate 버튼 클릭 후 문서 생성.

3. Javadoc 활용

  • IDE에서 Javadoc 보기:
    • IntelliJ: 코드 위에 커서를 올려놓고 Ctrl+Q를 눌러 빠른 문서 확인.
    • NetBeans/Eclipse: 클래스나 메서드를 우클릭하고 "Show Javadoc" 선택.
  • 브라우저에서 HTML 문서 열기:
    • 생성된 디렉터리 내 index.html 파일을 열어 API 문서 확인.

4. 추가 팁

  • 패키지 주석 작성: 패키지에 대한 설명은 package-info.java 파일에 작성합니다.
    /**
     * 이 패키지는 계산기 관련 클래스들을 포함합니다.
     */
    package com.example.calculator;
  • Lombok 사용 시 문제 해결: Lombok으로 생성된 메서드는 기본적으로 Javadoc에 포함되지 않습니다. 이를 해결하려면 Gradle의 delombok 태스크를 사용해야 합니다.

5. 결론

Javadoc은 코드의 가독성과 유지보수성을 높이는 중요한 도구입니다. 표준 태그와 올바른 형식을 사용해 주석을 작성하고, IDE 또는 명령줄 도구를 통해 쉽게 HTML 문서를 생성할 수 있습니다.

public DiscodeitException(ErrorCode errorCode, Map<String, Object> details) {
this.timestamp = Instant.now();
this.errorCode = errorCode;
this.details = details;
Copy link
Collaborator

Choose a reason for hiding this comment

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

details 객체를 그대로 할당하면 어떤 문제가 생길 수 있을까요?

문제점

1. 불변성(Immutable) 보장 실패

  • details가 외부에서 전달된 Map<String, Object> 타입 그대로 할당되고 있습니다.
  • 만약 외부에서 전달된 details 맵이 이후에 변경된다면, DiscodeitException 객체 내부의 details도 함께 변경됩니다.
  • 이는 예외 객체의 상태가 예기치 않게 변할 수 있음을 의미하며, 예외 객체는 생성 이후 상태가 변하지 않는 것이 바람직합니다.

2. 캡슐화 위반

  • 외부에서 전달된 객체를 그대로 참조하므로, 예외 객체의 내부 상태가 외부에 노출됩니다.

개선 방법

1. 방어적 복사(Defensive Copy)

  • 생성자에서 전달받은 맵을 새로운 맵으로 복사하여 할당하면, 외부 변경으로부터 내부 상태를 보호할 수 있습니다.
public DiscodeitException(ErrorCode errorCode, Map<String, Object> details) {
    super(errorCode.getMessage());
    this.errorCode = errorCode;
    this.details = (details == null) ? null : new HashMap<>(details);
}

2. 불변 맵 사용

  • Java 9 이상이라면 Map.copyOf()를 사용할 수도 있습니다.
this.details = (details == null) ? null : Map.copyOf(details);

결론

  • 예외 객체의 내부 상태가 외부에 의해 변경될 수 있으므로, 방어적 복사를 통해 불변성을 보장하는 것이 좋습니다.
  • 특히 예외 객체는 로그나 디버깅 용도로 많이 사용되므로, 상태가 변하지 않도록 주의해야 합니다.

Comment on lines 20 to 28
public ErrorResponse(DiscodeitException ex, int status) {
this(Instant.now(), ex.getErrorCode().name(), ex.getMessage(), ex.getDetails(),
ex.getClass().getSimpleName(), status);
}

public ErrorResponse(Exception ex, int status) {
this(Instant.now(), ex.getClass().getSimpleName(), ex.getMessage(), new HashMap<>(),
ex.getClass().getSimpleName(), status);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

생성자에 사용된 파라미터를 이용해서 ErrorResponse를 생성하는 정적 생성자를 만들어보면 어떨까요?

이펙티브 자바에서는 생성자 대신 정적 생성자(정적 팩터리 메서드, static factory method)를 사용하라고 권장합니다. 그 이유와 주요 장점은 다음과 같습니다.

정적 팩터리 메서드의 장점

  • 의미 있는 이름을 가질 수 있다

    • 생성자는 클래스 이름과 같아야 하지만, 정적 팩터리 메서드는 이름을 자유롭게 정할 수 있어, 반환 객체의 특성을 명확하게 드러낼 수 있습니다.
  • 호출할 때마다 새로운 객체를 생성하지 않아도 된다

    • 내부적으로 캐싱된 객체를 반환하거나, 기존 객체를 재사용할 수 있어 불필요한 객체 생성을 줄이고 성능을 높일 수 있습니다.
  • 반환 타입의 하위 타입 객체를 반환할 수 있다

    • 반환 타입이 인터페이스나 추상 클래스라면, 실제 구현체를 숨기고 유연하게 객체를 반환할 수 있습니다. 예를 들어, 컬렉션 팩터리 메서드는 내부적으로 비공개 클래스를 반환하기도 합니다.
  • 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다

    • 입력 값이나 상황에 따라 최적화된 하위 타입 객체를 반환할 수 있습니다. 클라이언트는 반환 객체의 구체 타입을 몰라도 되므로, API를 유연하게 설계할 수 있습니다.
  • 반환할 객체의 클래스를 나중에 추가할 수 있다

    • 정적 팩터리 메서드 작성 시점에 반환할 객체의 클래스가 없어도 되고, 추후에 추가할 수 있어 확장성이 높습니다.

단점

  • 상속이 어렵다

    • public 또는 protected 생성자가 없으면 하위 클래스를 만들 수 없습니다.
  • API에서 찾기 어렵다

    • 생성자와 달리, 정적 팩터리 메서드는 명명 규칙이 필요하며, 문서화와 네이밍이 중요합니다.

대표적인 네이밍 패턴

  • of: 여러 매개변수로 객체 생성
  • from: 하나의 값을 변환해 객체 생성
  • valueOf: 변환 또는 캐싱된 객체 반환
  • getInstance: 인스턴스 반환(기존 객체일 수도 있음)
  • newInstance: 항상 새로운 인스턴스 반환
  • with: 일부 속성만 변경한 새 객체 생성

정리하면, 정적 팩터리 메서드는 생성자보다 더 많은 유연성과 명확성을 제공하므로, 객체 생성이 필요한 경우 먼저 정적 팩터리 메서드 사용을 고려하는 것이 이펙티브 자바의 권장 사항입니다.

Comment on lines 18 to 28
private HttpStatus determineHttpStatus(DiscodeitException e) {
ErrorCode errorCode = e.getErrorCode();
return switch (errorCode) {
case USER_NOT_FOUND, CHANNEL_NOT_FOUND, MESSAGE_NOT_FOUND, BINARY_CONTENT_NOT_FOUND,
READ_STATUS_NOT_FOUND, USER_STATUS_NOT_FOUND -> HttpStatus.NOT_FOUND;
case DUPLICATE_USER, DUPLICATE_READ_STATUS, DUPLICATE_USER_STATUS -> HttpStatus.CONFLICT;
case USER_PASSWORD_NOT_MATCHED -> HttpStatus.UNAUTHORIZED;
case PRIVATE_CHANNEL_UPDATE, INVALID_REQUEST, VALIDATION_ERROR -> HttpStatus.BAD_REQUEST;
case INTERNAL_SERVER_ERROR -> HttpStatus.INTERNAL_SERVER_ERROR;
};
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

ErrorCode의 필드로 HttpStatus를 갖게하면 이부분은 생략할 수 있지 않을까요?

Comment on lines 60 to 61
// .map(binaryContentService::create)
// .map(binaryContentMapper::toEntity)
Copy link
Collaborator

Choose a reason for hiding this comment

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

사용하지 않는 코드는 주석 대신 과감하게 삭제해주세요~!

Comment on lines 7 to 8
username: ${{DB_USERNAME}}
password: ${{DB_PASSWORD}}
Copy link
Collaborator

Choose a reason for hiding this comment

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

환경변수 참조는 중괄호가 한쌍만 들어가야합니다~!
예시: ${DB_USERNAME}


spring:
datasource:
url: jdbc:postgresql://localhost:5432/discodeit
Copy link
Collaborator

Choose a reason for hiding this comment

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

production phase에서는 DB url도 환경변수를 사용해서 노출이 안되게 하는 것이 안전합니다~!


# 애플리케이션 정보 (Actuator info 엔드포인트용)
info:
app:
Copy link
Collaborator

Choose a reason for hiding this comment

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

app: 부분은 삭제되어도 무방해보입니다.

show-details: always

# 애플리케이션 정보 (Actuator info 엔드포인트용)
info:
Copy link
Collaborator

Choose a reason for hiding this comment

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

info log를 actuator api(http://localhost:8081/actuator/info) 를 통해 조회해보면 빈 json이 나옵니다. 이를 해결하기 위해 아래와 같은 설정이 추가로 필요합니다.


management:
  endpoint:
    health:
      show-details: never
  info:
    env:
      enabled: false

@JinsolPark JinsolPark changed the base branch from main to 박진솔 July 4, 2025 10:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
미완성🛠️ 스프린트 미션 제출일이지만 미완성했습니다. 죄송합니다. 순한맛🐑 마음이 많이 여립니다.. 지각제출⏰ 제출일 이후에 늦게 제출한 PR입니다.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants