Skip to content
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

[BSVR-63] custom exception & global exception handler 추가 #15

Merged
merged 5 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -373,4 +373,5 @@ gradle-app.setting
# Java heap dump
*.hprof

# End of https://www.toptal.com/developers/gitignore/api/macos,windows,intellij,intellij+iml,intellij+all,visualstudiocode,java,gradle,kotlin
# End of https://www.toptal.com/developers/gitignore/api/macos,windows,intellij,intellij+iml,intellij+all,visualstudiocode,java,gradle,kotlin
/db/
1 change: 1 addition & 0 deletions application/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
dependencies {
implementation(project(":common"))
implementation(project(":domain"))
implementation(project(":usecase"))
implementation(project(":infrastructure:jpa"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.depromeet.spot.application.config;
package org.depromeet.spot.application.common.config;

import org.depromeet.spot.jpa.config.JpaConfig;
import org.depromeet.spot.usecase.config.UsecaseConfig;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.depromeet.spot.application.common.exception;

import org.depromeet.spot.common.exception.BusinessException;

public record ErrorResponse(String code, String message) {

public static ErrorResponse from(BusinessException e) {
return new ErrorResponse(e.getCode(), e.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.depromeet.spot.application.common.exception;

import org.depromeet.spot.common.exception.BusinessException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestControllerAdvice
public class SpotAppExceptionHandler {

private static final String EXCEPTION_LOG_TEMPLATE = "code = {}, message = {}";

@ExceptionHandler(BusinessException.class)
protected ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
var code = e.getCode();
var message = e.getMessage();
var httpStatus = e.getHttpStatus();

log.error(EXCEPTION_LOG_TEMPLATE, code, message);
var response = ErrorResponse.from(e);

return ResponseEntity.status(httpStatus).body(response);
}
}
42 changes: 42 additions & 0 deletions common/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/

### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

### VS Code ###
.vscode/

### Mac OS ###
.DS_Store
6 changes: 6 additions & 0 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
}

tasks.jar { enabled = true }
tasks.bootJar { enabled = false }
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.depromeet.spot.common.exception;

import org.springframework.http.HttpStatus;

import lombok.Getter;

@Getter
public class BusinessException extends RuntimeException {

private final HttpStatus httpStatus;
private final String code;

public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.httpStatus = errorCode.getStatus();
this.code = errorCode.getCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.depromeet.spot.common.exception;

import org.springframework.http.HttpStatus;

public interface ErrorCode {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

모든 도메인의 errorCode가 하나의 enum class에서 관리되면, 클래스가 너무 커져서 관리하기 힘들거라고 생각했어요.
(e.g., 200개의 error code 중에서 member와 관련된 error들을 찾는 상상..)

따라서 공통 포맷을 인터페이스로 만들고, 도메인별로 errorCode를 구현하게 처리해 응집도를 높였어요.


HttpStatus getStatus();

String getCode();

String getMessage();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.depromeet.spot.common.exception.member;

import org.depromeet.spot.common.exception.ErrorCode;
import org.springframework.http.HttpStatus;

import lombok.Getter;

@Getter
public enum MemberErrorCode implements ErrorCode {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

custom error를 만들어야 한다면, 이런식으로 ErrorCode를 구현하는 enum 클래스를 새로 생성하면 돼요.

MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "M001", "요청 유저가 존재하지 않습니다."),
;

private final HttpStatus status;
private final String code;
private String message;

MemberErrorCode(HttpStatus status, String code, String message) {
this.status = status;
this.code = code;
this.message = message;
}

public MemberErrorCode appended(Object o) {
message = message + " {" + o.toString() + "}";
return this;
}
Comment on lines +23 to +26
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

정해진 메세지 외에 추가로 정보를 제공하고 싶을 수 있어요. 그때 사용하는 메서드에요.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.depromeet.spot.common.exception.member;

import org.depromeet.spot.common.exception.BusinessException;

public abstract class MemberException extends BusinessException {

protected MemberException(MemberErrorCode errorCode) {
super(errorCode);
}

public static class MemberNotFoundException extends MemberException {
public MemberNotFoundException() {
super(MemberErrorCode.MEMBER_NOT_FOUND);
}

public MemberNotFoundException(Object o) {
super(MemberErrorCode.MEMBER_NOT_FOUND.appended(o));
}
Comment on lines +12 to +18
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

단순 예시..!
no args constructor를 사용하면 enum code에 지정해둔 message가 할당될 것이고,
appended() 메서드를 활용하면 원하는 정보를 추가로 전달할 수 있어요.

(e.g., 특정 name에 해당하는 유저가 없을 경우, name : {string}을 message에 추가하는 등)

}
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ include("infrastructure")
include("infrastructure:jpa")
findProject(":infrastructure:jpa")?.name = "jpa"
include("usecase")
include("common")
1 change: 1 addition & 0 deletions usecase/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
dependencies {
implementation(project(":common"))
implementation(project(":domain"))

// spring
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

import java.util.List;

import org.depromeet.spot.common.exception.member.MemberException.MemberNotFoundException;
import org.depromeet.spot.domain.member.Member;
import org.depromeet.spot.usecase.port.in.MemberUsecase;
import org.depromeet.spot.usecase.port.out.MemberRepository;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import lombok.val;

@Service
@RequiredArgsConstructor
Expand All @@ -18,12 +18,16 @@ public class MemberService implements MemberUsecase {

@Override
public Member create(final String name) {
val member = new Member(null, name);
var member = new Member(null, name);
return memberRepository.save(member);
}

@Override
public List<Member> findByName(final String name) {
return memberRepository.findByName(name);
var members = memberRepository.findByName(name);
if (members.isEmpty()) {
throw new MemberNotFoundException("name : " + name);
}
return members;
}
}
Loading