레벨 3에서 진행한 예외처리 적용기
💡 Intro https://krrong.tistory.com/entry/예외처리 위 글을 작성하며 프로젝트에서 예외처리를 어떻게 하면 잘 할 수 있을까 고민을 많이 했고 제이슨의 도움으로 이론적인 해결의 실마리를 찾았다. 이
krrong.tistory.com
아래 내용을 적용한 글의 내용이 궁금하다면 참고해보자.
💡 제이슨의 특급강의
❓ 나의 질문
현재 오류처리에 대해 진행하고 있다. 서버에서 http 응답 코드 4xx대로 내려주는 응답들이 body를 가지고 있고, 이 body는 각각 서버에서 정의한 errorCode와 message를 가지고 있는 형태다.
ex)
{
errorCode : 2xx,
message : messageString
}
이전에 제이슨이 말씀해주신 방법처럼 layer별로 예외처리를 나누는 것을 적용하려고 해봤다. 그래서 데이터 레이어에서 사용하는 예외는 DataThrowable, UI 레이어에서 사용하는 예외는 PresentationThrowable이라고 이름을 지었다.
그런데 적용하려고 보니 서버에서 내려주는 에러들 중 그냥 넘겨도 되는 예외(따로 처리하지 않아도 되는)들도 있지만 사용자에게 보여줘야 하는 예외들도 있지않나? 라는 의문이 들었다. 그래서 내 생각의 종착지는 데이터 레이어에서 UI 레이어에서 사용하기 위해 정의한 PresentationThrowable를 사용해야 하지 않나? 였다. 이게 맞는 것인가? 이러면 레이어에서 따로 사용하기 위해 예외를 나눈 이유가 무엇이지? 그래서 다시 제이슨을 찾아갔다.
❗️ 제이슨의 답변
❓레이어별 에러
아래 그림을 그려주시면서 바보인 나에게 친절하게 설명해주셨다.
데이터 레이어에서 try catch 혹은 runCatching 등의 방법으로 예외를 잡을 수 있을 것이다. 그렇다면 잡은 예외에 대해 3~4가지 일을 할 수 있을 것이다.
- 잡은 예외를 무시한다.
- 분기처리를 통해 다시 요청을 보낸다.
- 처리할 수 있는 예외로 변환한다.
- 상위 레이어로 보내 그 쪽에서 처리한다. (throw)
결국 내가 원하는 것은 3번이다.
3번을 수행하기 위한 방법도 여러 방법이 있을 수 있다.
- UI 레이어용 Exception을 만든다.
- 데이터 레이어용 Exception을 만든다.
- UI와 데이터 레이어를 모두 관통하는 Exception을 만든다.
UI 레이어용 Exception을 만들게 되면 데이터 레이어에서 try catch를 통해 잡은 Exception을 UI 레이어용 Exception으로 변환하여 사용하게 될 것이다. 이 때 양방향 의존관계가 될 수 있는데, 이는 UI 레이어에 있는 Exception을 데이터 레이어에서 참조하기 때문에 데이터 레이어가 UI 레이어를 의존하게 되고 결국 양방향 의존 관계가 되는 것이다.
만약 이러한 양방향 의존관계가 맘에들지 않는다면 데이터 레이어용 Exception을 UI 레이어로 throw하고 이 Exception을 UI 레이어에서 분기처리 하여 처리하는 방식을 택할 수도 있다. 이 방법을 사용한다면 여전히 UI 레이어만 데이터 레이어에 의존하고 있기 때문에 단방향 의존 관계를 유지할 수 있다.
UI와 데이터 레이어를 모두 관통하는 Exception을 만들거나 혹은 이를 포함하고 도메인도 함께 관통하는 Exception을 만드는 방법도 있을 수가 있겠다. 이렇게 모든 레이어를 관통하는 Exception을 만드는 것의 이점은 레이어별로 따로 Exception을 만들지 않아도 된다는 것. 이를 통해 코드의 양이 줄어든다는 이점이 있을 수 있겠다. 근데 딱히 이 방법은 끌리지 않는다.
데이터 레이어에서 발생한 예외 중 사용자에게 보여주고 싶은 예외들만 UI 레이어에서 다시 처리하는 방법이 괜찮아 보인다. 그런데 그 전에 외부세계(애플리케이션 외부, 예를 들면 서버, 라이브러리, 프레임워크 등이 있을 수 있다.)로부터 어떻게 예외를 받아오게 되는걸까? 나는 이 부분을 이해하는게 어려웠다. 그래서 다시 여쭤봤다.
❓ 외부와의 연결
또 새로운 그림을 그려주시며 눈높이 교육을 해주셨다. 아주 좋다.
우리의 애플리케이션과 서버, 프레임워크, 라이브러리 등과 이어주는 것은 API(Application Programming Interface)다. 그리고 이 친구는 마법의 짬통(Adapter, Translator, Converter, 외교관?)으로 볼 수 있다. 정확히는 API가 마법의 짬통이라기보다 외부에서 내부로 들어오는 과정에서 거치는 친구들이 마법의 짬통이다. 이 친구들이 외부 세계의 데이터를 우리 도메인(애플리케이션)에 맞게 변환해주는 역할을 하기 때문이다. 그리고 짬통이라는 표현을 사용한 이유는 변환 과정에서 생기는 더러운 코드들이 잔뜩 들어갈 수 있기 때문이다. 어감은 좋지 않지만 무슨 일을 하는지 바로 알아들을 수 있는 단어인듯 하다.
우리가 카카오나 네이버 맵의 기능을 사용할 때 정상적으로 동작되지 않는다면 카카오나 네이버 맵은 발생한 에러를 http 응답 코드로 구분하여 우리에게 알려준다. 라이브러리나 프레임워크에서 정상적으로 동작되지 않는다면 발생한 에러에 대해 물리적인 Exception을 발생시켜 우리에게 알려준다.
이 마법의 짬통 친구는 라이브러리에서 발생한 Exception이든 카카오맵, 네이버맵에서 발생한 에러든 상관없이 우리의 애플리케이션에 맞도록 변환해줄 수 있다. 우리에게 맞도록 변환해주는 녀석이 필요하고 이 녀석은 필히 더러워질 수 밖에 없다. 왜? 외부에서 제공하는 정보들, 혹은 예외들을 우리에게 맞도록 변환하거나 우리가 처리할 수 있는 형태로 변환해줘야 하기 때문이다. 그리고 이 친구가 더러워진 만큼 우리의 애플리케이션이 순수하게 유지될 수 있는 것이다. 그래서 마법의 짬통이다.
다음의 상황을 가정해보자.
안드로이드에서는 페이지네이션 처리를 해야한다는 요구사항이 있다. 그리고 서버에서 페이지네이션 한 응답을 잘 내려준다면 바로 적용할 수 있는 상태다.(한마디로 구현완.) 하지만 서버에서는 시간이 없어 페이지네이션을 구현하지 못한 상태고 요청에 대한 응답으로 1000개의 데이터를 내려줄 수밖에 없는 상황이라고 생각해보자. 서버에서 오는 1000개의 데이터에 대해 리포지터리 구현체에서 offset을 활용하여 데이터를 잘라서 가져오도록 구현하면 마치 서버가 페이지네이션을 구현하여 필요한 데이터를 주는 것처럼 하여 데이터를 받아올 수 있다.
그리고 나중에 서버에서 여유가 생겨 페이지네이션을 구현했다면, 새로운 API가 생긴 것이라고 볼 수 있다. 기존에 사용하던 1000개의 데이터를 모두 받아오는 API를 사용하여 리포지터리 구현체에서 처리해주는 대신 페이지네이션 API로 갈아끼우면 우리 애플리케이션에는 수정사항 없이 바로 반영이 가능하다. 즉 도메인에 변경이 일어나지 않는다는 것이다.
이것은 SOLID 원칙 중 D(Dependency Inversion Principle / 의존관계 역전)에 해당한다. 애플리케이션이 서버에서 오는 데이터에 의존하지만 실제로 리포지터리 구현체는 페이지네이션이 필요한 도메인에 맞게 구현이 되어있기 때문에 의존관계가 역전되었다고 할 수 있다.
👻 정리
예외를 처리하는 것이 처음이었는데, 또 잘하고 싶은 마음도 있고 길이 완전 전혀 하나도 절대로 보이지 않았는데 이러한 제이슨의 답변에서 진짜 실낱같은 희망을 보았다. 실제로 이것을 앱에 적용하는 것은 또 다른 말이겠고 또 다른 산이겠지만 그래도 넘어갈 수 있는 무너지기 일보직전의 돌다리이겠지만 이를 발견해서 아주아주 기분이 좋다.
참고