Skip to content

[강문구] sprint7 #195

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 12 commits into
base: 강문구
Choose a base branch
from

Conversation

Kangmoongu
Copy link
Collaborator

@Kangmoongu Kangmoongu commented Jun 27, 2025

Spring Boot 고급 설정 및 테스트 구현

기본 요구사항

1. 프로파일 기반 설정 관리

프로파일 구성

  • 개발, 운영 환경에 대한 프로파일을 구성하세요.
  • application-dev.yaml, application-prod.yaml 파일을 생성하세요.
  • 다음과 같은 설정값을 프로파일별로 분리하세요:
    • 데이터베이스 연결 정보
    • 서버 포트

2. 로그 관리

기본 로깅 설정

  • Lombok의 @Slf4j 어노테이션을 활용해 로깅을 쉽게 추가할 수 있도록 구성하세요.
  • application.yaml에 기본 로깅 레벨을 설정하세요.
    • 기본적으로 info 레벨로 설정합니다.
  • 환경 별 적절한 로깅 레벨을 프로파일 별로 설정해보세요.
    • SQL 로그를 보기위해 설정했던 레벨은 유지합니다.
    • 우리가 작성한 프로젝트의 로그는 개발 환경에서 debug, 운영 환경에서는 info 레벨로 설정합니다.

Logback 설정

  • 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
  • 다음과 같은 메소드에 로깅을 추가하세요:
    • 사용자 생성/수정/삭제
    • 채널 생성/수정/삭제
    • 메시지 생성/수정/삭제
    • 파일 업로드/다운로드

3. 예외 처리 고도화

커스텀 예외 설계

  • 커스텀 예외를 설계하고 구현하세요.
    • 패키지명: com.sprint.mission.discodeit.exception[.{도메인}]

ErrorCode Enum 클래스

  • ErrorCode Enum 클래스를 통해 예외 코드명과 메시지를 정의하세요.

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

DiscodeitException 클래스

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

클래스 다이어그램

  • details는 예외 발생 상황에 대한 추가정보를 저장하기 위한 속성입니다.
  • 예시:
    • 조회 시도한 사용자의 ID 정보
    • 업데이트 시도한 PRIVATE 채널의 ID 정보

도메인별 예외 클래스

  • DiscodeitException을 상속하는 주요 도메인 별 메인 예외 클래스를 정의하세요.
    • UserException, ChannelException
    • 실제로 활용되는 클래스라기보다는 예외 클래스의 계층 구조를 명확하게 하기 위한 클래스입니다.
  • 도메인 메인 예외 클래스를 상속하는 구체적인 예외 클래스를 정의하세요.
    • UserNotFoundException, UserAlreadyExistException 등 필요한 예외를 정의하세요.

기존 예외 대체

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

ErrorResponse 정의

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

클래스 다이어그램

  • int status: HTTP 상태코드
  • String exceptionType: 발생한 예외의 클래스 이름

전역 예외 핸들러

  • 앞서 정의한 ErrorResponse@RestControllerAdvice를 활용해 예외를 처리하는 예외 핸들러를 구현하세요.
    • 모든 핸들러는 일관된 응답(ErrorResponse)을 가져야 합니다.

4. 유효성 검사

Spring Validation 설정

  • Spring Validation 의존성을 추가하세요.
  • 주요 Request DTO에 제약 조건 관련 어노테이션을 추가하세요.
    • @NotNull, @NotBlank, @Size, @Email
  • 컨트롤러에 @Valid를 사용해 요청 데이터를 검증하세요.
  • 검증 실패 시 발생하는 MethodArgumentNotValidException을 전역 예외 핸들러에서 처리하세요.
  • 유효성 검증 실패 시 상세한 오류 메시지를 포함한 응답을 반환하세요.

5. Actuator

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

Actuator 확인

  • Spring Boot 서버를 실행 후 각종 정보를 확인해보세요.
    • /actuator/info
    • /actuator/metrics
    • /actuator/health
    • /actuator/loggers

테스트 구현

1. 단위 테스트

서비스 레이어 테스트

  • 서비스 레이어의 주요 메소드에 대한 단위 테스트를 작성하세요.
  • 다음 서비스의 핵심 메소드에 대해 각각 최소 2개 이상(성공, 실패)의 테스트 케이스를 작성하세요:
    • UserService: create, update, delete 메소드
    • ChannelService: create(PUBLIC, PRIVATE), update, delete, findByUserId 메소드
    • MessageService: create, update, delete, findByChannelId 메소드
  • Mockito를 활용해 Repository 의존성을 모의(mock)하세요.
  • BDDMockito를 활용해 테스트 가독성을 높이세요.

2. 슬라이스 테스트

레포지토리 테스트

  • 레포지토리 레이어의 슬라이스 테스트를 작성하세요.
  • @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 응답을 검증하는 테스트를 포함하세요.

3. 통합 테스트

통합 테스트 환경 구성

  • 통합 테스트 환경을 구성하세요.
  • @SpringBootTest를 활용해 Spring 애플리케이션 컨텍스트를 로드하세요.
  • H2 인메모리 데이터베이스를 활용하세요.
  • 테스트용 프로파일을 구성하세요.

통합 테스트 케이스

  • 주요 API 엔드포인트에 대한 통합 테스트를 작성하세요.
  • 주요 API에 대해 최소 2개 이상의 테스트 케이스를 작성하세요:
    • 사용자 관련 API (생성, 수정, 삭제, 목록 조회)
    • 채널 관련 API (생성, 수정, 삭제)
    • 메시지 관련 API (생성, 수정, 삭제, 목록 조회)
  • 각 테스트는 @Transactional을 활용해 독립적으로 실행하세요.

심화 요구사항

1. MDC를 활용한 로깅 고도화

MDC 인터셉터 구현

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

인터셉터 등록

  • WebMvcConfigurer를 통해 MDCLoggingInterceptor를 등록하세요.
    • 클래스명: WebMvcConfig
    • 패키지명: com.**.discodeit.config

Logback MDC 패턴

  • 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

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

Admin 서버 모듈 생성

  • Spring Boot Admin 서버를 구현할 모듈을 생성하세요.
  • 모듈 정보는 다음과 같습니다:
    • 의존성 추가 필요

Admin 서버 설정

  • 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 서버 확인

  • admin 서버 실행 후 localhost:9090/applications 에 접속해봅니다.

Admin Client 설정

  • 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}
# ...

Admin 대시보드 확인

  • discodeit 서버를 실행하고, admin 대시보드에 discodeit 인스턴스가 추가되었는지 확인합니다.
  • admin 대시보드 화면을 조작해보면서 각종 메트릭 정보를 확인해보세요.
    • 주요 API의 요청 횟수, 응답시간 등
    • 서비스 정보

3. 테스트 커버리지 관리

JaCoCo 플러그인 설정

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

체크리스트 요약

기본 요구사항 체크리스트

  • 프로파일 기반 설정 관리 완료
  • 로그 관리 시스템 구축 완료
  • 커스텀 예외 처리 시스템 구현 완료
  • Spring Validation 적용 완료
  • Actuator 설정 및 확인 완료

테스트 구현 체크리스트

  • 단위 테스트 작성 완료
  • 슬라이스 테스트 작성 완료
  • 통합 테스트 작성 완료

심화 요구사항 체크리스트

  • MDC 로깅 시스템 구현 완료
  • Spring Boot Admin 연동 완료
  • 테스트 커버리지 60% 이상 달성 완료

참고사항

  • 모든 설정 파일은 프로젝트 루트의 src/main/resources 디렉토리에 위치해야 합니다.
  • 테스트 관련 설정 파일은 src/test/resources 디렉토리에 위치해야 합니다.
  • 패키지 구조는 기존 프로젝트 구조를 유지하면서 새로운 기능을 추가해야 합니다.
  • 각 단계별로 테스트를 실행하여 정상 동작을 확인한 후 다음 단계로 진행하세요.

스크린샷

sprintmission7

image

@Kangmoongu Kangmoongu changed the base branch from main to 강문구 June 27, 2025 17:32
@Kangmoongu Kangmoongu added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label Jun 27, 2025
@Kangmoongu Kangmoongu changed the title 강문구 sprint7 [강문구] sprint7 Jun 27, 2025
@Kangmoongu Kangmoongu requested a review from dongjoon1251 June 27, 2025 17:33
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableAdminServer
Copy link
Collaborator

Choose a reason for hiding this comment

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

어드민서버 구성 👍

finalizedBy jacocoTestReport
}

jacocoTestReport {
Copy link
Collaborator

Choose a reason for hiding this comment

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

jacoco 적용 👍

Comment on lines +96 to +176
/**
* 민감한 정보를 제거하고 파라미터를 문자열로 변환
*/
private String sanitizeArguments(Object[] args) {
if (args == null || args.length == 0) {
return "없음";
}

return Arrays.stream(args)
.map(arg -> {
if (arg == null) {
return "null";
}

String argStr = arg.toString();
String className = arg.getClass().getSimpleName();

// 민감한 정보가 포함된 클래스들은 클래스명만 표시
if (className.toLowerCase().contains("password") ||
className.toLowerCase().contains("request") ||
className.toLowerCase().contains("multipart")) {
return className;
}

// UUID는 앞 8자리만 표시
if (arg instanceof UUID) {
return argStr.substring(0, 8) + "...";
}

// 문자열이 너무 길면 자르기
if (argStr.length() > 50) {
return argStr.substring(0, 50) + "...";
}

return argStr;
})
.reduce((a, b) -> a + ", " + b)
.orElse("없음");
}

/**
* 주요 식별자 추출 (UUID, ID 등)
*/
private String extractIdentifier(Object[] args) {
if (args == null || args.length == 0) {
return "미지정";
}

for (Object arg : args) {
if (arg instanceof UUID) {
return "ID:" + arg.toString().substring(0, 8) + "...";
}
if (arg != null && arg.getClass().getSimpleName().toLowerCase().contains("request")) {
// Request 객체에서 username이나 email 등을 추출할 수 있음
return "Request:" + arg.getClass().getSimpleName();
}
}

return "ID:미지정";
}


/**
* 메소드명으로부터 작업 유형 추출
*/
private String getOperationType(String methodName) {
if (methodName.startsWith("create")) {
return "생성";
}
if (methodName.startsWith("update")) {
return "수정";
}
if (methodName.startsWith("delete")) {
return "삭제";
}
if (methodName.startsWith("find")) {
return "조회";
}
return "처리";
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

이부분은 유틸성 메소드로 보입니다. 해당 로직을 테스트하기 용이하도록 별도 클래스로 분리해보면 어떨까요?
그리고 메소드 하나에 포함되는 라인수가 줄어들도록 더 작은 메소드들로 잘게 나누어보는 리팩토링을 진행해보면 어떨까요?

@Aspect
@Component
public class LoggingAspect {

Copy link
Collaborator

Choose a reason for hiding this comment

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

AOP를 이용하여 로깅과 관련된 공통 로직을 잘 분리해주셨습니다.
다만, 서비스별로 디버깅을 위해 봐야하는 특정 값들이 다를 수 있습니다. 서비스중에 디버깅에 도움이 될 만한 로깅을 일부 서비스에 추가로 남겨보면 어떨까요?

Comment on lines +15 to +16
username: discodeit_user
password: discodeit1234
Copy link
Collaborator

Choose a reason for hiding this comment

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

production 환경에서는 해당 정보들이 노출되지 않도록 환경변수로 주입받도록 하면 좀더 안전해지지 않을까요?

Comment on lines +50 to +53
ErrorCode errorCode = ErrorCode.VALIDATION_FAILED;
return ResponseEntity.badRequest()
.body(new ErrorResponse(Instant.now(), errorCode.getCode(), errorCode.getMessage(),
errors, e.getClass().getSimpleName(), errorCode.getHttpStatus().value()));
Copy link
Collaborator

Choose a reason for hiding this comment

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

ErrorCode를 파라미터로 받아서 ErrorResponse를 생성하는 정적 생성자를 만들어보면 어떨까요?

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

정적 팩터리 메서드의 장점

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

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

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

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

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

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

단점

  • 상속이 어렵다

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

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

대표적인 네이밍 패턴

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

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

@Query("SELECT DISTINCT m FROM Message m JOIN FETCH m.channel WHERE m.channel.id = :channelId")
List<Message> findAllByChannelId(@Param("channelId") UUID channelId);
@Query("SELECT m FROM Message m "
+ "LEFT JOIN FETCH m.author a "
Copy link
Collaborator

Choose a reason for hiding this comment

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

fetch join이 동작하는지 테스트해볼 수 있을까요?

Hibernate.isInitialized(Object proxy) 는 Hibernate에서 프록시 객체나 지연 로딩(lazy loading) 컬렉션이 실제로 초기화(로딩)되었는지 여부를 확인하는 정적 메소드입니다. 이 메소드는 다음과 같은 상황에서 주로 사용됩니다.

  • 지연 로딩(lazy loading) 필드나 컬렉션이 실제 DB에서 로딩되었는지(초기화되었는지) 확인할 때
  • 세션이 닫힌 후 LazyInitializationException이 발생하지 않도록, 접근 전에 초기화 여부를 미리 체크할 때

동작 방식

  • proxy 또는 persistent collection 객체가 전달되면,
    • 이미 초기화(로딩)된 경우 true 반환
    • 아직 초기화되지 않은 경우 false 반환
    • 일반 객체(프록시나 컬렉션이 아닌 경우)는 항상 true 반환

public static boolean isInitialized(java.lang.Object proxy)
Check if the proxy or persistent collection is initialized.
Returns: true if the argument is already initialized, or is not a proxy or collection.

예시 코드

if (!Hibernate.isInitialized(entity.getLazyCollection())) {
    Hibernate.initialize(entity.getLazyCollection());
}
  • 위 예시처럼, 컬렉션이나 연관 엔티티가 초기화되지 않았다면 Hibernate.initialize()로 강제 초기화할 수 있습니다.

활용 시 주의사항

  • Detached 상태(세션이 닫힌 후)에서는 초기화할 수 없으므로, 반드시 세션이 열려 있을 때 사용해야 합니다.
  • 컬렉션의 경우, 컬렉션 객체 자체의 초기화 여부만 판단합니다. 컬렉션 내부의 요소까지 모두 초기화되었는지는 보장하지 않습니다.

요약

  • **Hibernate.isInitialized(obj)**는 해당 객체(프록시, 컬렉션)가 실제로 DB에서 로딩되었는지 확인하는 메소드입니다.
  • Lazy Loading 환경에서 안전하게 객체 접근이 필요한 경우, 이 메소드로 초기화 여부를 확인 후 사용하세요.

User user = userRepository.findByUsername(username)
.orElseThrow(
() -> new UserNotFoundException(ErrorCode.USER_NOT_FOUND,
Map.of("로그인 시도한 사용자의 아이디 정보", username)));
Copy link
Collaborator

Choose a reason for hiding this comment

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

상수로 쓰이는 문자열들은 private static 멤버변수로 분리해서 사용해보면 어떨까요? 아니면 해당 정보를 커스텀한 예외 클래스에서 갖게 하면 어떨까요?

Comment on lines +53 to +89
UUID channelId = messageCreateRequest.channelId();
UUID authorId = messageCreateRequest.authorId();

Channel channel = channelRepository.findById(channelId)
.orElseThrow(
() -> new ChannelNotFoundException(ErrorCode.CHANNEL_NOT_FOUND,
Map.of("생성하고자 하는 메세지의 채널 ID", channelId)));
User author = userRepository.findById(authorId)
.orElseThrow(
() -> new UserNotFoundException(ErrorCode.USER_NOT_FOUND,
Map.of("생성하고자 하는 메세지의 유저 ID", authorId))
);

List<BinaryContent> attachments = binaryContentCreateRequests.stream()
.map(attachmentRequest -> {
String fileName = attachmentRequest.fileName();
String contentType = attachmentRequest.contentType();
byte[] bytes = attachmentRequest.bytes();

BinaryContent binaryContent = new BinaryContent(fileName, (long) bytes.length,
contentType);
binaryContentRepository.save(binaryContent);
binaryContentStorage.put(binaryContent.getId(), bytes);
return binaryContent;
})
.toList();

String content = messageCreateRequest.content();
Message message = new Message(
content,
channel,
author,
attachments
);

messageRepository.save(message);
return messageMapper.toDto(message);
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 +9 to +10
# DB_CLOSE_DELAY: 테스트 종료 후 데이터베이스 연결 유지 시간 (음수 값은 연결 유지)
# DB_CLOSE_ON_EXIT: 프로그램 종료 시 데이터베이스 연결 닫기 여부
Copy link
Collaborator

Choose a reason for hiding this comment

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

사용하지 않는 코드는 과감히 삭제해주세요!

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