Exception Handling

Exception 처리는 웹 어플리케이션 개발에 있어서 흔히 간과되는 중요한 사항중의 하나로, 일반적으로 개발자는 method내에서 Exception이 발생하면 try, catch 구문을 사용하여 Stack Trace를 출력하거나, 발생한 Exception을 throw하는 method를 호출한다. 일반적으로 운영시스템에서 Exception은 사용자의 요청을 시스템이 처리할 수 없을 때 발생하며, 이러한 Exception이 발생했을 때 사용자가 기대하는 바는 다음과 같다.
  • 에러가 발생했음을 표시하는 메시지
  • 고객 지원 요청시 사용할 수 있는 고유한 에러 식별자
  • 문제에 대한 빠른 해결책
이를 위해, Anyframe Framework에서는 다음과 같이 간략한 구조의 Exception 처리 방법을 소개하고자 한다.
위의 그림에서와 같이, Client의 요청을 처리하기 위해 해당 서비스의 비즈니스 오퍼레이션을 호출하는 Action, 비즈니스 오퍼레이션을 구현하고 있는 서비스, 데이터 액세스 로직을 구현하고 있는 DAO에 대한 Exception 처리가 필요하다. Exception 처리시에는 불필요한 try ~ catch 절이 반복되지 않도록 주의해야 한다. 따라서, 발생된 예외에 대한 처리는 서비스 레이어에서만 처리하며, 발생한 예외에 대한 사용자 메시지를 보여주기 위한 코드 변환 작업을 수행한다. Presentation Layer에서는 별도의 예외 처리 작업 없이 선언적으로 예외 상황을 처리하고 Tag Library를 활용하여 오류에 대한 메시지를 화면에 보여준다. 위 그림에서 보이는 각 영역별로 Exception을 처리하는 방법을 자세히 살펴보면 다음과 같다.

서비스 구현 부분에서의 Exception 처리

1. Exception Log
Exception이 발생하였을 경우에는 해당 Exception 상황을 에러 레벨의 로그로 남겨 향후 확인이 가능하도록 하는 것이 좋다.
catch (Exception e) {
	logger.error(messageSource.getMessage("error.get.codelist.reason",
			new String[] {}, Locale.getDefault()), e);
	throw new EmpException(messageSource, "error.get.codelist", e);
}
2. throw Business Exception
비즈니스 로직을 처리하는데 있어서 발생 가능한 Exception 및 DAO 에서 throw한 Exception를 catch하여, 사용자가 정의한 비즈니스 Exception으로 전환한 후, throw한다. 이때, 해당 비즈니스 Exception은 Anyframe Framework에서 제공하는 BaseException을 상속받아 정의하고,
public EmpException(MessageSource messageSource, String messageKey,
		Object[] messageParameters, Throwable wrappedException) {
	super(messageSource, messageKey, messageParameters, wrappedException);
}
비즈니스 Exception 생성시 MessageSource Bean과 관련된 에러 메시지 Key를 전달하여, Anyframe Framework에서 제공하는 ExceptionHandler를 통해 해당 Message Key에 맞는 에러 메시지 정보를 찾도록 한다.
public class CodeServiceImpl implements CodeService, ApplicationContextAware {
	private Log logger = LogFactory.getLog(CodeServiceImpl.class);

	private MessageSource messageSource = null;
	
	// 중략

	public ArrayList getCodeList(String codeType) throws EmpException {
		try {
			return codeDAO.getCodeList(codeType);
		}
		catch (Exception e) {
			logger.error(messageSource.getMessage("error.get.codelist.reason",
					new String[] {}, Locale.getDefault()), e);
			throw new EmpException(messageSource, "error.get.codelist", e);
		}
	}


	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		messageSource = (MessageSource) applicationContext
				.getBean("messageSource");
	}
}
에러 메시지는 메시지를 확인하는 대상자에 따라 유형을 구분해 볼 수 있다. 예를 들어, 어플리케이션 사용자의 입장에서는 해당 에러의 원인 및 해결책이 필요하고 어플리케이션 개발자의 입장에서는 시스템적인 메시지를 필요로 하게 된다. Anyframe Framework에서는 에러 메시지 정의시 해당 에러에 대해 유형별(기본,해결책,원인) 메시지 값을 정의하도록 가이드하고 있으며 Exception 발생시 상위 Exception인 BaseException에서 ExceptionHandler를 이용하여 기본 메시지 키와 관련된 메시지를 모두 추출한 후 Message 객체에 담게 된다. 다음은 에러 메시지를 정의한 Resource 파일의 내용이다.
error.get.codelist=Fail to get code list.
error.get.codelist.solution=Fail to get code list. Please check if DB is started.
error.get.codelist.reason=Fail to get code list. DB may be shutdowned.
3. Rollback 처리
Transaction 제어가 필요한 비즈니스 오퍼레이션에 대해서는 반드시 rollback 처리가 수행되어야 하는데 Anyframe Framework의 선언적인 Transaction 서비스를 사용하는 경우에는 Transaction 서비스 속성 정의시 Rollback Rule을 정의하는 것만으로도 Transaction 제어가 가능하다.

Data Access 부분에서의 Exception 처리

DAO(Data Access Object)에서는 QueryService를 사용하여, 데이터를 조작하는 역할을 수행하게 되어, Query문 수행시 발생되는 Exception만 발생되므로 기본적으로 별도 에러 처리를 수행하지 않고, DAO에서 발생한 모든 Exception은 그대로 throw한다. 단, 논리적인 이유에서 Exception 발생이 필요한 경우에는 비즈니스 Exception throw도 가능하다.
public ArrayList getCodeList(String codeType) throws Exception {
	return (ArrayList) queryService.find("getCodeList",
			new Object[] { codeType });
}

Exception 처리 비용

JVM(자바 가상 머신)에서 Exception처리를 위해 동작하는 방식을 살펴보면, JVM은 method호출의 역순으로 현재 thread에서 호출된 method를 대표하는 stack 프레임을 stack에 담고 있다가 Exception가 발생하면 Exception에 대한 첫 번째 처리(try catch)를 한 method를 stack에서 찾아낸다. 이러한 작업은 JVM의 많은 리소스가 소요된다.
그러므로 Exception은 반드시 Exception발생이 의미있는 작업일 경우에만 발생해야 한다. 만일 이러한 상황이 의미 있는 형태로 프로그램적으로 처리될 수 있으면 Exception 발생을 피해야 한다. System Exception의 예는 ConfigurationException (시작 시에 Data Load를 실패한 경우) 같은 것이 대상이 될 수 있다. 이러한 Exception는 문제를 수정하고 재 구동하는 것 이외에는 사용자가 처리할 수 있는 것이 없다. 이러한 예가 시스템 Exception 이며, RuntimeException으로 처리될 수 있다. SQLException같은 경우는 상황에 따라 시스템 오류 혹은 어플리케이션 문제를 지칭한다. 각 경우에 일반적으로 SQLException은 어플리케이션 로직에서 던지지 않는 checked exception으로 모델링한다.