최근 참여한 프로젝트의 개발 단계가 마무리되었고, 현재는 서비스가 운영 중입니다.
처음 서비스를 배포하면서 깨달은 중요한 점은 바로 에러 코드의 필요성이었습니다. 개발 단계에서 코드를 완벽하게 작성하여 에러가 발생하지 않도록 하는 것이 최우선이겠지만, 사람이 하는일에 완벽함을 기대하기는 어렵습니다.
서비스 운영 중 서버에서 발생한 에러로 긴급 대응이 필요했던 상황이 있었습니다. 특히 운영 중인 서버의 환경(주로 데이터베이스 서버)이 개발 서버와 다르기 때문에, 문제를 진단하는 데 예상보다 많은 시간이 소요되었습니다.
이 문제를 해결하기 위해, 최근에는 기능을 개발할 때 어느 부분에서 오류가 발생했는지 쉽게 파악할 수 있도록 에러 코드를 반환하고 있습니다.

서비스 레이어에서 비즈니스 로직을 수행하다 특정 조건을 만족하지 못할 경우, 예외(Exception)를 발생시키고, catch 블록에서 발생한 오류의 세부 사항을 명시한 후 해당 코드를 반환합니다.
컨트롤러 레이어에서는 이 코드를 분석하여 오류에 맞는 결과를 담은 response 객체를 브라우저로 반환합니다. 뷰(view) 레이어에서는 치명적이지 않은 오류의 경우 ResponseDTO.success로 객체를 반환받아 다음 단계를 진행하며, 치명적인 오류가 발생하면 fail로 객체를 반환받아 오류 메시지를 출력합니다.


그러던 중 예상치 못한 문제가 발생했습니다. 프로젝트의 AOP설정에서 트랜잭션 관리를 위해 rollbackFor=Exception.class를 지정했기 때문에, try-catch 블록을 사용하여 발생한 예외(Exception)를 처리함으로써, 해당 비즈니스 로직의 트랜잭션에 대한 롤백이 실행되지 않는 상황이 발생한 것입니다.
try-catch 블록 내에서 예외를 처리하고 있기 때문에, AOP를 통한 트랜잭션 관리 시스템이 예외가 발생했음을 인식하지 못하고, 따라서 자동으로 롤백을 수행하지 않는 문제였습니다.
예외를 잡아내어 로직을 처리하더라도, 필요한 경우에는 명시적으로 롤백을 수행해야 할 필요가 있었습니다.
롤백을 수행하기 위해 두 가지 방법을 생각했습니다.
throw new Exception("원래 반환하려했던 resultCode");
이런 방식으로 Exception을 고의적으로 발생시키며 에러 메세지를 담아 컨트롤러에 반환하는 방식으로, 핸들러 메소드에서 error.getMessage()를 통해 메세지를 받는 방법입니다.
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 메소드를 호출해 직접 롤백을 수행하는 것 입니다.
두 가지 방법중 저는 2번 방법을 사용해 문제를 해결했습니다. 명시적으로 롤백을 수행하여 트랜잭션을 관리하도록 코드를 수정했습니다.
이번 문제를 해결하며 몇가지 사실을 알게되었습니다.
저는 트랜잭션 관리가 필요한 메소드에 대해서 어노테이션을 작성하여 트랜잭션을 관리했지만, 프로젝트가 끝난 지금에서야 특정 패키지 내에 있는 모든 클래드와, 메소드들에 대해 AOP설정으로 트랜잭션이 설정 되어있었음을 알게되었습니다.(rollbackFor = Exception.class)
그것도 모르고 열심히 Transactional 어노테이션을 작성했습니다.
또한, 트랜잭션의 전파속성을 모르고, 하나의 메소드에 Transactional 어노테이션을 작성하고, 해당 메소드가 호출하는 메소드에도 Transactional 어노테이션을 작성했습니다.
이번에 여러 자료들을 조사하며 알게된 사실은 트랜잭션에는 전파레벨이 있고, 내부 트랜잭션에 참여하는 외부 트랜잭션은 내부 트랜잭션에 포함되어 Transactional을 작성할 필요가 없다는 사실입니다. (트랜잭션의 전파레벨에 대해서는 새로 포스팅을 작성할 예정입니다.)