프로젝트를 진행하면서 response 형태를 어디서, 어떤식으로 가공하는지에 대해 팀원들과 합을 맞추어야 했다. 누구는 service에서, 누구는 view에서 형태를 맞추면 팀 프로젝트로써의 통일성이 해쳐지게 되고 이후 수정이 번거로워지기 때문에 보통 response는 함수화 하여 처리한다고 한다! 그래서 우리도 그렇게 하기로 했다!
=> 하고 나니 정말 좋았다!
우리 프로젝트상 response의 형태는 다음과 같이 크게 3가지로 나뉜다.
- 조건에 맞는 data가 있을 경우
- 특별히 반환되어야 하는 data가 없을 경우
- error 상황에 걸려 raise 될 경우
대표적으로 GET
methods에 따른 요청이 경우가 있다.
예시로 상품 리스트에 대한 값을 GET method를 통해 요청할 경우 백엔드에서는 해당 리스트에 대한 정보를 반환해 주어야 한다.
최종적으로 반환되어야 할 형태는 아래와 같다.
{
"message": "SUCCESS",
"result": {
"data": <<조건에 맞는 data 반환>>,
"totalCount" : <<조건에 맞는 data 총 갯수 반환 / 필요할 경우에만 반환>>
}
}
예를들면 POST나 PATCH method를 사용했을 경우이다.
data의 update 혹은 create가 발생하면 되기 때문에 반환되어야 할 정보가 필요없는 상황이다.
이런 상황에서 우리가 원하는 반환 형태는 다음과 같다.
{
"message": <<상황에 맞는 메세지 내용>>,
"result": <<상황에 맞는 메세지 내용2>>
}
여러가지 validation에 의해 error상황에 걸려 에러메세지가 반환되도록 구현하고자 한다. 원하는 형태는 다음과 같다.
{
"message" : <<에러상황에 대한 메세지>>
}
flask에는 다양한 HTTP response / request 관련 함수들이 존재한다. 이를 decorator로 활용하는 것!
- before_first_request
- before_request
- after_request
- teardown_request
- teardown_appcontext
이중에서 현 상황에 필요한 것은 after_request
이다.
이름 그대로 request요청이 처리되고 나서 실행되는 것. 즉, HTTP 요청이 진행 된 후 실행되는 것이다.
이 함수가 최종적으로 response가 반환되는 함수에 데코레이터로 작용하면 모든 return이 이 함수로 catch 된다.
이때 코드를 보면
'result': response.json.get('result', response.json),
'message': response.json.get('custom_message', OK)
라고 작성되어 있다. 이는 위의 response 형태 1번과 2번을 동일하게 처리해주기 위함이다!
Service 로직에서 최종적으로 반환되어야 할 data를
{
"data" : <<최종 반환 data>>
}
의 형태로 가공하여 View 로직에서는 이런 Dictionary 형태의 결과값을 그대로 return 해준다.
이러한 경우 message 내용은 default 값으로 SUCCESS라고 반환되도록 하며, status_code는 200이다.
View 로직에서 커스텀하여 메세지를 반환한다.
{
"custom_message" : CREATED,
"result" : "POST"
}
이때 CREATED는 따로 에러메세지를 저장하여 관리하는 file로부터 불러온 변수명으로 실제 내용은 "정상적으로 생성되었습니다" 라는 메세지가 담겨있다.
팀원들과 협의 결과 "message"내용은 커스텀된 내용의 메세지를 담고, "result"에는 해당 로직이 어떤 method로 구현되었는지에 대한 정보를 반환해 주기로 하였다.
View, Service, Dao 상관없이 에러상황이 발생하는 즉시 raise되도록 처리하였다.
에러상황에 대한 처리는 먼저 response 함수화 로직이 작성된 app.py 가 아닌 별도의 responses.py라는 file의 함수에 catching 된다.
해당 file에는 다음과 같은 class가 작성되어 있다.
init 함수를 통해 해당 class가 발동될시 무조건 로직이 구현되도록 작성하였고, parameter에 넘어온 값들을 다시한번 값에 담는다. => app.py 에서 실행되어야 한다.
이때 init 함수의 경우 return을 사용할 수 없다!!!!!!!! 때문에 최종적으로 원하는 형태인 "message"라는 key값에 내용을 담을 수 없다.
이 역시 flask의 함수이다.
@app.errorhandler(ApiException)
def handle_bad_request(e):
return_message = {'error_message': e.error_message, 'status': e.code}
if e.result:
return_message['result'] = e.result
return jsonify(return_message), e.code
이곳에서 responses.py 에 담겨진 message내용과 status를 각각 "error_message"와 "status"라는 key값에 담아 return 한다.
여기서!!! return 된 내용은 또다시 flask의 after_request 함수에 의해 catching 된다.
이거때문에 한참 고생핬다 😭 왜자꾸 에러메세지가 다시 일로와...? 이러면서 ;;ㅎㅎ
따라서 위에서 작성했던 final_return 함수에 에러메세지를 처리할수 있는 조건문을 달아준다.
return되어 들어온 response.json
에 "error_message"라는 key값이 있을 경우에 대한 조건문이다!
또한 json의 형태로 넘겨주기 위해 반드시 json.dumps
를 사용해야 할것! 다른거 쓰면 안되나? 싶을땐 쳐보고, 에러메세지 확인하면 된다! 😆😆
저 if문 걸고 정말.. 뿌듯했다!!!!!!!!!!!
메세지 내용 정복 101.. 그런데 아쉬운 점은 responses.py 에 작성한 ApiException 상황 외의 에러상황에 대한 로직이 없다는 것!!! 이 역시 조건문을 통해 에러상황이 ApiException으로 인해 발생했을때와, 그렇지 않았을 때를 구분하여 처리하면 된다!
위 사진에는 첨부하지 않았지만 잘못된 URI 경로로 진입하여 return 되는것이 아무것도 없을 경우에 대한 return도 처리해주었다!
이때 고민했던것이 아무것도 없이 return을 하게되면 404 NOT_FOUND 라는 default message가 보내지는데 이걸 또 custom_message처럼 가공할지..말지.. 고민이 됐었다!!
하지만 페이지에 잘못 들어갔을 경우 FE에서도 에러상황에 대한 페이지를 표기해 주기 때문에그런 부분은 신경 쓰지 않아도 되었다! 물론 일단 코드는 짜놨지만 ^|^