Skip to content

[김민준] sprint7 #209

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 1 commit into
base: 김민준
Choose a base branch
from

Conversation

adjoon1
Copy link
Collaborator

@adjoon1 adjoon1 commented Jun 29, 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 클래스를 정의하세요.

  • DiscodeitException을 상속하는 주요 도메인 별 메인 예외 클래스를 정의하세요.

  • 도메인 메인 예외 클래스를 상속하는 구체적인 예외 클래스를 정의하세요.

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

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

  • 앞서 정의한 ErrorResponse와 @RestControllerAdvice를 활용해 예외를 처리하는 예외 핸들러를 구현하세요.

멘토에게

  • 셀프 코드 리뷰를 통해 질문 이어가겠습니다.
  • 아직 미완성이라 추후에 완성하도록 하겠습니다!

@adjoon1 adjoon1 added 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. 미완성🛠️ 스프린트 미션 제출일이지만 미완성했습니다. 죄송합니다. labels Jun 29, 2025
@adjoon1 adjoon1 requested a review from IDontHaveBrain June 29, 2025 19:45
@adjoon1 adjoon1 added the 지각제출⏰ 제출일 이후에 늦게 제출한 PR입니다. label Jun 29, 2025
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.

바쁘신 수업 와중에 미션하느라 고생하셨습니다!
나중에 시간이 되시면 꼭 미션 미구현 부분 진행하여 보시길 바랍니다!


dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'de.codecentric:spring-boot-admin-starter-server:3.2.5'
Copy link
Collaborator

Choose a reason for hiding this comment

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

3.2.5 버전은, 존재 안하는 버전입니다!
아래 링크 참조하여 주세요!
https://mvnrepository.com/artifact/de.codecentric/spring-boot-admin-starter-server

annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'org.springframework.boot:spring-boot-starter-validation'
Copy link
Collaborator

Choose a reason for hiding this comment

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

빌드가 안됩니다 ㅠㅠ,,
아래 기존 의존성들이 필요합니다!
수정되기 전 구성에서 'org.springframework.boot:spring-boot-starter-validation' 만 추가하시는게 더 안정적인 설정으로 보입니다!

	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.4'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

Channel channel = channelRepository.findById(channelId)
.orElseThrow(() -> new NoSuchElementException("Channel with id " + channelId + " not found"));
if (!channelRepository.existsById(channelId)) {
throw new NoSuchElementException("Channel with id " + channelId + " not found");
Copy link
Collaborator

Choose a reason for hiding this comment

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

커스텀 예외 사용 누락(여기 외에도 여러곳에서), 나중에 시간 되시면 GlobalExceptionHandler가 일관된 ErrorResponse를 만들어주게 커스텀 예외 사용 추천드립니다.

.body("[서버 내부 오류] 알 수 없는 에러가 발생했습니다.");
}
@ExceptionHandler(DiscodeitException.class)
public ResponseEntity<ErrorResponse> handleDiscodeitException(DiscodeitException e) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

현재 모든 DiscodeitException을 HttpStatus.BAD_REQUEST로 고정하여 응답하고 있습니다.
하지만 ErrorCode에는 NOT_FOUND, FORBIDDEN 등 다양한 상태 코드가 정의되어 있습니다.
또한, ErrorResponse DTO를 새로 만드셨지만, 핸들러의 응답 본문(body)은 다른 ErrorResponse 객체(org.springframework.web.ErrorResponse)를 사용하고 있습니다.

import com.sprint.mission.discodeit.dto.ErrorResponse; // 직접 만든 DTO를 import 해야 합니다.
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice // @ControllerAdvice + @ResponseBody, API 응답에 적합합니다.
public class GlobalExceptionHandler {

    @ExceptionHandler(DiscodeitException.class)
    public ResponseEntity<ErrorResponse> handleDiscodeitException(DiscodeitException e) {
        ErrorCode errorCode = e.getErrorCode();
        ErrorResponse errorResponse = new ErrorResponse(
            errorCode.getCode(),
            errorCode.getMessage(),
            e.getDetails(),
            e.getClass().getSimpleName(),
            errorCode.getStatus().value(),
            e.getTimestamp()
        );

        return ResponseEntity
            .status(errorCode.getStatus()) // ErrorCode에 정의된 HttpStatus
            .body(errorResponse);
    }

@Autowired
private MessageRepository messageRepository;
@Autowired
private ReadStatusRepository readStatusRepository;
Copy link
Collaborator

Choose a reason for hiding this comment

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

현재 아키텍처 레이어를 크게 4가지로 나누어서 보면 Controller, Service, Repository, Mapper 입니다.

레이어를 나누는 이유는 뭘까요?
Mapper, Service 각각에 역할 내지 책임은 무엇일까요 ?
Mapper에서 Repository 를 주입받아서 사용하는 상황에서 Service 레이어와의 차이점은 무엇일까요 ?

MapStruct나 일반적인 Mapper의 주된 책임은 객체의 데이터를 다른 형태의 객체로 "변환"하는 것입니다.

Optional.ofNullable(user.getProfileId())
.ifPresent(binaryContentRepository::deleteById);
userStatusRepository.deleteByUserId(userId);
if (userRepository.existsById(userId)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

delete 메서드에서 사용자가 존재할 때 (existsById가 true일 때) 예외를 던지고 있습니다.
!(NOT) 연산자를 활용하시면 됩니다.

    @Transactional
    @Override
    public void delete(UUID userId) {
        if (!userRepository.existsById(userId)) { // 사용자가 존재하지 않으면
            throw new UserNotFoundException(userId);
        }

        userRepository.deleteById(userId);
    }

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