반응형
해당 글에서는 business Layer에서 발생하는 오류에 대해서 공통 처리를 위한 Business Exception 대한 구성 방법에 대해 이해하고 구성하는 방법에 대해서 공유합니다.
[참고] 이전에 작성한 Global Exception & Controller Exception 글을 읽고 오시면 크게 도움이 됩니다.
1) 개발 환경
💡 Business Exception 개발 환경을 구성하기 위해 사용한 개발환경입니다.
개발 환경 | 버전 |
java | 1.8 |
Spring Boot | 2.7.4 |
빌드관리도구 | Gradle 7.5 |
개발 툴 | IntelliJ IDEA 2022.3 |
2) 비즈니스 로직 내에서 발생하는 Exception 처리의 흐름
💡 비즈니스 로직이란?
- 일반적으로 Java내에서 *Service.java(interface) 파일 혹은 *ServiceImpl.java(interface implements) 파일에서 처리되는 비즈니스를 처리하기 위한 로직이 구현된 부분을 의미합니다.
- HTTP Status 코드로 정상코드가 아닌 '오류코드'로 반환 하였을 시 실제 '에러'가 발생하기에 이를 위해 중간에 GlobalExcpetion을 통해 Exception 발생 시에도 HTTP Status 코드로 '정상 코드'를 보내고 커스텀한 코드를 보냄으로써 실제 Client 내에서 이를 처리할 수 있게 돕기 위함입니다.
1. API 정상 동작 처리 과정
- 클라이언트에서 API로 데이터를 전송하면 API에서는 @RequestBody, @RequstParam, @PathVariable Annotation을 이용하여 클라이언트에서 전송한 데이터를 받습니다.
- Controller 내에서 해당 데이터 값을 받아서 비즈니스 로직 레이어로 전달합니다.
- 비즈니스 로직 레이어에서는 로직 처리를 수행합니다.
- 정상적으로 처리가 완료된 경우
- 로직 처리에 대한 반환 값을 Controller로 전달합니다.
- 비즈니스 로직 처리가 완료되면 Controller에서 클라이언트로 ‘성공 응답(Response)’ 데이터를 전송합니다.
- 정상적으로 처리가 완료된 경우
- 클라이언트와 API와의 통신이 완료되었습니다.
2. API 정상 동작 처리 과정
- 클라이언트에서 API로 데이터를 전송하면 API에서는 @RequestBody, @RequstParam, @PathVariable Annotation을 이용하여 클라이언트에서 전송한 데이터를 받습니다.
- Controller 내에서 해당 데이터 값을 받아서 비즈니스 로직 레이어로 전달합니다.
- 비즈니스 로직 레이어에서는 로직 처리를 수행합니다.
- 정상적으로 처리가 되지 않고 Exception이 발생한 경우
- 비즈니스 로직 레이어에서 데이터 처리를 수행하는 도중에 예외처리가 발생합니다.
- 예외 처리가 발생할 만한 구간에 throw new BusinessExceptionHandler()로 에러 처리를 수행합니다.
- 이에 대해 @ExceptionHandler(BusinessExceptionHandler.class)에서 이를 캐치합니다.
- 캐치한 메서드에서 이를 처리합니다.
- 선언한 어노테이션에서 메서드가 수행이 되면서 직접적인 오류가 아닌 응답값을 반환하도록 처리합니다.
- ExceptionHandler에서 Response 응답 데이터 (result, resultCode, resultMsg) - ‘ErrorCode’를 구성합니다.
- 구성된 데이터를 클라이언트에게 전송합니다.
- 정상적으로 처리가 되지 않고 Exception이 발생한 경우
- 클라이언트와 API와의 통신이 완료되었습니다.
💡 Bussiness Excpetion 요약
-> Client(API Request)
-> Controller -> Service -> ServiceImpl(Exception 발생)
-> BusinessExceptionHandler(해당 클래스로 리턴)
-> GlobalExceptionHandler(구성한 응답 값을 구성하여 Client로 리턴)
-> Client(API Response)
3) 비즈니스 로직 예외처리(Business Exception)
💡 비즈니스 로직 예외처리(Business Exception)란?
- 자바 계층 구조 중 Business Layer에서 발생하는 에러를 캐치하여 오류를 발생시키지 않고 응답 메시지로 클라이언트에게 전달해주는 기능을 의미합니다. 해당 기능을 통해서 고유한 코드를 구성하여 어떠한 에러가 어디에서 발생한지에 대해서 추적 또한 가능합니다.
- 예로 클라이언트에서 Promise 형태로 요청을 하여 .then, .catch로 응답 값을 받을때 모든 응답 값을 .then으로 응답을 받고 .catch내로 에러를 발생시키지 않기 위함으로 구성합니다.
💡 비즈니스 로직 예외처리(Business Exception)를 사용하는 가장 큰 목적
- Client와 Server의 데이터 통신을 수행하는데 'API 통신 자체'는 성공으로 반환해주고 통신은 성공했지만 트랜잭션이나 기타 서버에서 발생하는 에러에 대해서는 Client로 커스텀 코드를 보냄으로써 웹 자체의 에러를 발생 시키지 않고 오류에 대한 추적이 가능하게끔 사용하기 위함입니다.
[출처] https://www.oreilly.com/library/view/software-architecture-patterns/9781491971437/ch01.html
[ 더 알아보기 ]
💡 HTTP Status란?
- 클라이언트가 서버에 요청을 보낼 때, 서버가 클라이언트에 응답하는 상태 코드를 말합니다.
💡 자바 계층 구조란?
- 자바 내에서는 크게 Presentation Layer, Business Layer, Persistence Layer, Database Layer로 나뉘어 있습니다. API 요청이 발생하였을 시 각각의 계층을 순차적으로 전달하여 데이터베이스에서 데이터를 처리 이후에 다시 반환되는 각각의 목적으로 구분이 되어 있습니다.
💡 비즈니스 레이어란?
- 실제적인 로직 처리를 수행하는 레이어입니다. 해당 레이어에서는 일반적으로 인터페이스(interface)로 *Service와 구현체(Implements)로 *ServiceImpl에 해당되는 파일이며, 실제적인 구현체에서 이 비즈니스 로직처리를 수행합니다.
💡 [참고] 이전에 Spring Boot - SQL Mapper에서 사용하였던 구조에서 각각의 레이어를 나누어 보았습니다.
💡 해당 사진은 각각의 레이어에 해당하는 구조를 구분하였습니다.
- Presentation Layer : Controller, View(Thymeleaf)
- Business Layer: Service, ServiceImpl
- Data Access Layer(=Persistence Layer): Mapper(DAO/xml)
- Database Layer: PostgreSQL
4) 환경 구성하기
💡 선행적으로 Global Exception 부분이 구성되었다는 가정에서 이어서 구성을 합니다.
1. 프로젝트 디렉터리 구조 확인
💡 이전 내용이 구성되었다는 전제로 해당 패키지 중 'GlbalExceptionHandler'와 'BusinessExceptionHandler' 파일에 대해서 주목합니다.
2. GlobalExceptionHandler.java
💡 handleCustomException 메서드에서는 @ExceptionHandler(BusinessExceptionHandler.class)로 Exception을 다루는 클래스를 지정하였습니다.
/**
* Controller 내에서 발생하는 Exception 대해서 Catch 하여 응답값(Response)을 보내주는 기능을 수행함.
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* BusinessException에서 발생한 에러
*
* @param ex BusinessException
* @return ResponseEntity
*/
@ExceptionHandler(BusinessExceptionHandler.class)
public ResponseEntity<ErrorResponse> handleCustomException(BusinessExceptionHandler ex) {
log.debug("===========================================================");
log.debug("여기로 오는가?!");
log.debug("===========================================================");
final ErrorResponse response = ErrorResponse.of(ErrorCode.BUSINESS_EXCEPTION_ERROR, ex.getMessage());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
}
3. BusinessExceptionHandler.java
💡 해당 클래스에서는 에러가 발생하였을 경우 반환할 응답값에 대해서 구성을 합니다.
package com.adjh.multiflexapi.config.exception;
import com.adjh.multiflexapi.common.codes.ErrorCode;
import lombok.Builder;
import lombok.Getter;
/**
* 에러를 사용하기 위한 구현체
*/
public class BusinessExceptionHandler extends RuntimeException {
@Getter
private final ErrorCode errorCode;
@Builder
public BusinessExceptionHandler(String message, ErrorCode errorCode) {
super(message);
this.errorCode = errorCode;
}
@Builder
public BusinessExceptionHandler(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
}
4. **ServiceImpl.java
💡 Service 구현체 내에서 위에서 구성한 BusinessExceptionHandler를 강제로 에러를 발생시킵니다.
@Slf4j
@Service
public class CodeServiceImpl implements CodeService {
private final SqlSession sqlSession;
public CodeServiceImpl(SqlSession ss) {
this.sqlSession = ss;
}
/**
* 코드 INSERT
*
* @param codeDto 코드
*/
@Override
@Transactional
public Integer insertCode(CodeDto codeDto) {
CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
int result = 0;
try {
result = cm.insertCode(codeDto);
// 강제로 에러를 발생 시킵니다.
throw new BusinessExceptionHandler(ErrorCode.INSERT_ERROR.getMessage(), ErrorCode.INSERT_ERROR);
} catch (Exception e) {
throw new BusinessExceptionHandler(e.getMessage(), ErrorCode.INSERT_ERROR); // errorCode : 9999
}
}
}
5. 강제 에러 발생 시 출력되는 코드 정의
💡 에러 발생에 따라서 구분을 위한 divisionCode를 포함하였습니다.
package com.adjh.multiflexapi.common.codes;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* [공통 코드] API 통신에 대한 '에러 코드'를 Enum 형태로 관리를 한다.
* Global Error CodeList : 전역으로 발생하는 에러코드를 관리한다.
* Custom Error CodeList : 업무 페이지에서 발생하는 에러코드를 관리한다
* Error Code Constructor : 에러코드를 직접적으로 사용하기 위한 생성자를 구성한다.
*
* @author lee
*/
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public enum ErrorCode {
BUSINESS_EXCEPTION_ERROR(200, "B999", "Business Exception Error"),
/**
* ******************************* Custom Error CodeList ***************************************
*/
// Transaction Insert Error
INSERT_ERROR(200, "9999", "Insert Transaction Error Exception"),
// Transaction Update Error
UPDATE_ERROR(200, "9999", "Update Transaction Error Exception"),
// Transaction Delete Error
DELETE_ERROR(200, "9999", "Delete Transaction Error Exception"),
; // End
/**
* ******************************* Error Code Constructor ***************************************
*/
// 에러 코드의 '코드 상태'을 반환한다.
private int status;
// 에러 코드의 '코드간 구분 값'을 반환한다.
private String divisionCode;
// 에러 코드의 '코드 메시지'을 반환한다.
private String message;
// 생성자 구성
ErrorCode(final int status, final String divisionCode, final String message) {
this.status = status;
this.divisionCode = divisionCode;
this.message = message;
}
}
6. 결과 확인
1. 클라이언트에서 데이터 전송
💡 해당 Controller로 정상적인 데이터를 전송합니다.
💡 Postman을 이용하여서 강제 에러를 발생시킨 지점에 값을 넣어서 요청을 합니다.
2. GlobalExceptionHandler내에서 이를 확인
💡 하단의 로그를 확인하면 GlobalExceptionHandler.java 파일 내에@ExceptionHandler(BusinessExceptionHandler.class)로 지정해둔 부분 함수에서 적었던 로그가 출력이 되었습니다.
3. Client의 응답값 확인
💡 최종적으로 결과값으로 상태로 200으로 받지만 구분 코드(DivisionCode)로 지정한 ‘B999’라는 비즈니스 Exception이 발생하였습니다.
5) 참고
💡 Controller 단의 Exception 처리가 우선적으로 진행되기에 하단의 글을 참고하시면 크게 도움이 됩니다.
반응형
'Java > Spring Boot' 카테고리의 다른 글
[Java] Spring Boot Security 이해하기 -2 : Spring Boot 2.x 버전 환경 구성하기 (0) | 2022.12.18 |
---|---|
[Java] Spring Boot Security 이해하기 -1 : 2.7.x 버전 구조 및 파일 이해 (0) | 2022.12.18 |
[Java] Global Exception 이해하고 구성하기 : Controller Exception (5) | 2022.11.13 |
[Java/Library] Spring Boot Validation 이해하기 : 데이터 유효성 검증 (4) | 2022.11.08 |
[Java/Library] Thymeleaf, Thymeleaf Layout 적용하기 (6) | 2022.10.30 |