본문 바로가기
Web

Create / Update 시 응답에 변경된 리소스를 포함해야 할까?

by Vintz 2023. 12. 17.
반응형

Photo by Waldemar - https://unsplash.com/ko/@waldemarbrandt67w

최근 회사에서 리액트를 사용해 어드민 서비스를 개발하면서, POST 요청의 처리 방식에 대해 생각하게 되었다. '왜 POST 요청 후에 변경된 데이터가 응답으로 오지 않을까?' 라는 의문이 든 것이다.

 

현재의 방식에서, 서버는 HTTP 상태 코드와 간단한 결과 메시지만을 응답한다. 예를 들어, "처리되었습니다."와 같은 메시지다. 그런데 만약 API 응답에 변경된 리소스가 포함되어 온다면 그 데이터를 즉시 사용할 수 있어서, 클라이언트는 추가적인 GET 요청을 보낼 필요가 없어진다. 또한, 별도의 데이터 리패치(refetch) 함수를 만들 필요도 없다. 그래서 좀 더 효율적이고, 깔끔하게 구현할 수 있을거라 생각했다.

다른 개발자들의 생각은 어떨까?

그래서 궁금했다. 다른 개발자들은 어떻게 구현하고 있을까. 지인, 커뮤니티, 오픈 채팅방 등 다양한 곳에서 물어봤다. 예상과 달리, 대다수의 개발자들은 POST 요청 이후 별도의 GET 요청을 통해 구현을 한다고 답했다. 몇몇의 상황을 제외하고, 많은 경우에 이렇게 구현한다는 것이었다.

 

그 주된 이유로는 HTTP 메서드의 '역할'을 명확히 지키려는 의도에서 비롯되었다. Create / Update 시에 '조회'의 의미까지 섞인다면 서비스의 기능이나 범위가 확장됨에 따라 제약이 있을 수 있다는 것이다.

 

예를 들어, A 컴포넌트에서 생성되는 데이터가 나중에 B 컴포넌트에서도 생성될 필요가 생겼다고 가정해 보자. 이 경우 같은 POST API를 호출하게 되는데, 응답으로 받는 데이터가 B 컴포넌트에서는 필요하지 않을 수도, 또는 그 반대의 상황도 발생할 수 있다. Create / Update 시 관련 데이터를 절대 포함하면 안된다는 것은 아니지만, 서비스가 언제 어떻게 변화할 지 알 수 없기 때문에 확장성을 고려하여 API를 설계해야 한다는 것이 일반적인 의견이었다. 나도 어드민 서비스 개발을 하면서 비슷한 상황을 겪었기 때문에, '현재 방식이 HTTP 메서드의 의미에 맞게, 그리고 적절하게 구현하고 있는거구나.'라는 생각을 했었다.

DB에서 발생하는 문제

그렇다면, 이제 아무 문제가 없을까? 사실 이렇게 Create / Update 시 변경된 리소스를 응답에 포함하지 않고 성공 여부만 반환하는 구현은 틀린 구현이다. '틀렸다.'라는 표현을 하기 위해서는 상황 정의를 명확하게 해볼 필요가 있다. HTTP 메서드의 역할이 아닌 데이터베이스의 데이터 일관성(data consistency)의 관점에서, 그리고 단일 서버가 아닌 여러 서버가 동작하는 분산 시스템에서의 상황을 가정한다.

데이터 일관성(Data consistency)과 복제 지연(Replication lag)

데이터 일관성은 데이터베이스에서 중요한 개념 중 하나로, 이는 데이터가 항상 균일하고 예측 가능한 상태를 유지해야 함을 의미한다. 예를 들어, 데이터에 변경을 가하는 모든 쓰기(write) 작업은 즉시 모든 사용자에게 반영되어야 한다. 하지만, 이 과정에서 지연이 발생하면, 내가 방금 실행한 쓰기(write) 작업이 읽기(read) 작업에 즉시 반영되지 않을 수 있다. 이것은 내가 추가한(insert) 데이터를 바로 조회(select)하려 할 때, 아직 데이터가 완전히 반영되지 않아 보이지 않을 수 있다는 것을 의미한다.

 

여기서 개발자는 선택에 있어 트레이드 오프(trade-off)가 발생한다. 예를 들어, 수많은 요청이 빠르게 들어오는 상황에서 단일 서버만으로는 이를 감당하기가 어렵다. 이를 해결하기 위해 데이터베이스의 복제본을 만들어 읽기 전용 서버를 추가적으로 운영한다. 이를 "읽기 서버의 수평 확장"이라고 부른다. 이 읽기 서버들은 복제본이기 때문에, 원본 쓰기 서버에서 발생한 변경사항이 읽기 서버에 반영되기까지 약간의 시간 차이, 즉 "복제 지연(replication lag)"이 발생할 수 있다. 이는 여러 데이터 서버가 있을 때, 한 서버 이상이 최신 상태와 동기화되지 않는 현상을 말한다.

 

따라서 분산 데이터베이스를 사용한다면 당연히 GET 요청은 읽기 서버에 요청하게 될 것이다. 이 경우 직전에 있던 수정 사항이 반영이 안되어 있을 확률이 있다. 99.99%의 일관성을 제공하더라도, 10만번에 한 번은 없을 수 있다. 이것을 해결하는 올바른 방법은 두 가지가 있다:

  1. Create / Update 요청 후에 API 응답에 변경된 리소스를 포함시킨다. 이렇게 하면 클라이언트는 최신 데이터를 즉시 확인할 수 있다.
  2. 이벤트 기반 설계를 적용하여, 생성 또는 수정 결과가 모든 서버에 전파된 후에 클라이언트에게 이벤트를 푸시한다.

내 경우에는 첫 번째 방법이 적합하다. 내가 개발하고 있는 어드민 서비스는 사용자에게 정확하고 신뢰할 수 있는 데이터를 제공해야 하기 때문에, Create / Update 시 API 응답에 변경된 리소스를 포함시키는 것이 좋은 방식이다.

단일 RDBMS를 사용한다면 괜찮지 않을까?

만약 RDBMS(관계형 데이터베이스 관리 시스템)만 사용해 본 경험이 있다면, 이 개념이 다소 낯설 수 있다. 보통 데이터를 삽입하고 조회하면 바로 결과를 볼 수 있다고 생각하지만, 이는 쓰기 작업과 조회 작업이 동일한 서버에서 이루어질 때에만 해당된다. 데이터를 삽입한 후 다른 읽기 서버에서 조회를 하면, 복제 지연으로 인해 아직 데이터가 반영되지 않아 보이지 않을 수 있다. 실제로 이런 상황은 꽤 흔하게 발생한다.

 

강력한 일관성(strong consistency)을 사용하는 환경, 즉 읽기 전용 복제본(read replica) 없이 단일 RDBMS만을 사용하는 경우에도, API 응답에 변경된 리소스를 포함시켜야 할까? 현재는 읽기 전용 복제본이 없을 수 있지만, 미래에는 언제든 추가 될 수 있다. 작은 설계 변경이 큰 수정 사항을 예방할 수 있다. 또한, 어떤 경우에는 API 응답에 리소스를 포함시키고, 다른 경우에는 포함시키지 않는 일관성 없는 설계를 피하는 것이 좋다. API를 사용하는 입장에서는 응답에 항상 리소스를 포함하는 일관된 방식이 이해하기 쉽고 사용하기 편리하다.

 

백엔드 시스템 디자인은 분산 시스템 설계의 중요한 부분이다. 서버, 데이터베이스, 그리고 다른 의존 컴포넌트들이 분산 환경에서 운영될 수 있다는 가정 하에 설계를 하는 것이 중요하다. 이는 시스템의 확장성과 유연성을 증대시키는 데 도움이 된다.

응답에 변경된 리소스를 포함하지 않는 시나리오 두 가지

Create / Update 시 응답에 변경된 리소스를 포함하지 않을 때는 언제일까?

1. UI 지향 API를 설계했고, 생성 결과를 UI에 표시하지 않을 때

우리가 흔히 사용하는 REST API는 대개 리소스 중심으로 설계된다. 이는 여러 화면에서 동일한 리소스를 생성할 때 같은 API를 사용하는 것을 의미한다. 예를 들어, 화면 A와 B에서 모두 리소스 C를 생성한다면, 두 화면 모두 동일한 API를 사용한다. 이러한 설계에서는 API를 구축할 때 응답 스키마를 데이터베이스에 저장된 형태를 기준으로 하며, UI에서는 필요한 데이터를 여기저기서 끌어다 쓰는 방식으로 조합하게 된다.

 

하지만 UI 중심의 API 설계에서는 상황이 달라진다. 같은 리소스 C를 생성하더라도, 화면 A와 B가 요구하는 입력 필드나 결과로 보여주는 UI가 다를 수 있기 때문에 각 화면에 맞는 별도의 API를 설계하게 된다. 이 경우 BFF(Backend For Frontend) 아키텍처를 사용한다면, BFF는 UI 중심의 API를 제공하고 백엔드에서는 데이터 중심의 API로 마이크로서비스를 설계하는 것이 일반적이다.

 

그럼 이제 UI 중심 아키텍처를 사용하는 특정 UI 시나리오를 살펴보자. 인스타그램과 같은 소셜미디어에서 포스트를 업로드하는 과정을 가정한다:

  1. 별도의 페이지나 모달에서 포스트를 업로드한다.
  2. 업로드가 완료되면 메인 피드로 돌아간다.
  3. 새로고침을 하면, 내 포스트가 피드의 상단에 자연스럽게 나타난다.

이 경우 업로드 후 UI에서는 별도로 피드를 새로고침하게 되므로, API 응답에 생성된 포스트를 포함하지 않을 수 있다. 하지만 앞서 언급한 복제 지연 문제로 인해 새로고침 시에도 업로드한 포스트가 바로 나타나지 않을 수 있다. 이는 사용자가 새로고침을 하면 해결되는, 발생 빈도가 낮은 문제로 간주할 수 있다. 그러나 이러한 문제가 비즈니스에 심각한 영향을 미칠 경우, 이와 같은 설계 방식은 적절하지 않을 수 있다.

 

따라서, UI 중심의 API 설계에서는 응답에 리소스를 포함하지 않는 경우가 있을 수 있지만, 이는 특정 상황에 한정된 결정이다.

2. 실제로 데이터가 언제 생성될 지 알 수 없는 비동기 설계를 지향할 때

커머스 API의 경우를 생각해 보자. 사용자가 UI를 통해 주문을 요청하면, 이후에는 주문서 제출, 재고 확인, 카드사 결제 요청, 주문 결과 저장, 로깅 등 수많은 후속 작업이 진행된다. 이 모든 과정을 API가 처리하고 나서야 응답을 주게 되면 응답 속도가 상당히 느려질 수 있다. 웹 서비스에서 느린 응답은 사용자 경험을 저하시키고 다양한 부작용을 일으킬 수 있다.

 

이벤트 기반 설계에서는 요청이 들어오면 내부 이벤트 버스를 통해 요청을 처리하고, 사용자에게는 즉시 "주문 요청 성공"이라는 응답을 보낸다. 이 시점에서는 실제 주문이 생성된 상태는 아니며, 단지 요청만 한 상태이다. 만약 이후에 어떤 오류가 발생하거나, 카드 결제가 실패한다면 사용자는 이를 알 수 없다. 이 문제를 해결하기 위해 모든 처리 과정이 완료되면 서버는 클라이언트에게 완료 이벤트를 푸시한다. 이 이벤트를 통해 클라이언트는 최종적으로 완성된 주문의 상태를 확인할 수 있게 된다. 즉, 이벤트의 생성과 처리를 분리하는 것이다. 이러한 방식에서 생성 API의 응답은 실제 리소스가 포함되지 않는다.

왜 응답에 변경된 리소스를 포함시키지 않는 걸까?

이렇게 생각할 수 있다: '그렇다면 왜 Create / Update 시 응답에 변경된 리소스를 포함하지 않는 경우가 많은 걸까?' 모든 상황에서 무조건 변경된 리소스를 응답에 포함시켜야 한다는 것은 아니지만, 위 내용을 이해하고 있다면 대부분의 경우에 변경된 리소스를 응답에 포함시키는 것이 바람직할 것이다.

 

하지만, 실제 회사 환경에서는 이미 정해진 개발 방식이나 진행 중인 프로젝트의 특성상 변경이 쉽지 않을 수 있다. 특히 팀원들이 이에 대해 충분히 이해하고 있지 않거나, 중요성을 공감하지 못한다면 이를 설득하는 일은 더욱 어려울 수 있다. 또한, 크리티컬한 이슈가 아니라고 생각되어 우선 순위에서 뒤로 계속 미뤄질 수도 있는거고, 사용자가 새로고침을 하면 되니 어느 정도 책임을 넘길 수도 있다.

 

현재 내가 근무하고 있는 회사에서는 경험이 많은 5년 차와 10년 차 이상의 백엔드 개발자 두 분과 함께 일하고 있다. 아무래도 프론트엔드 개발자가 나 한 명이고, 프로젝트가 이미 상당 부분 진행된 상태에서 API 변경을 요청하는 것은 바쁜 시기에 많은 리소스를 요구하는 일이 될 수 있어서 망설여지는 게 사실이다.

 

그러나 만약 내가 나중에 팀 리더가 되거나 새 프로젝트를 시작한다면, 내가 배운 내용을 바탕으로 자신감 있게 제안 할 것이다. 이제 어떤 상황에서 어떻게 API를 설계하는 것이 적합할지 조금은 알게 되었기 때문이다.

참고

POST 요청의 응답에 변경된 데이터를 포함하시나요? - 커리어리 개발자 Q&A(본인 질문)
Replication lag - Wikipedia
Data Consistency 101: Causes, Types and Examples - Atlan
DB 인스턴스 읽기 전용 복제본 작업 - AWS
반응형