Spring에서 Patch Api를 구현 하는 방법?

Picbel·2023년 4월 15일
1

Spring

목록 보기
1/1
post-thumbnail

Restful Api를 설계하다보면 애매한 상황이 발생하는데 그중 하나인 수정에 관한 애기입니다.
보통 수정을 뜻하는 Http Method를 떠올린다면 크게 2개를 떠올립니다.
바로 PUT, PATCH 이죠. 이 둘의 차이점은 무엇일까요?

PUT : 대상 리소스의 모든 현재 표현을 요청 페이로드로 바꿉니다.
PATCH : 리소스에 부분 수정을 적용합니다.
출처 https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods

PUT은 전체 수정, PATCH는 부분 수정으로 정리 할 수 있겠습니다.

문제

유저를 하나의 도메인을 정의하여보겠습니다.
(언어는 코틀린으로 하겠습니다.)

data class User(
	val name: String,
    val age: Int?, // age는 민감정보라 실제 도메인에서도 nullable이라 가정하겠습니다.
    val address: String?
)

유저를 수정한다는 api를 만들겠습니다.
전체 수정은 간단하게 모든 user 필드를 받으면 됩니다.
Method는 PUT이 되겠군요
그런데 유저 정보를 수정하는데 만약 name, age, address 3개를 항상 클라이언트가 받아서 처리해야합니다.
지금이야 3개지면 user가 훨씬더 복잡해지면? 혹은 필드가 굉장히 많은 복잡한 객체라면?
그래도 조회 후 가져와 특정부분만 수정하고 다시 보내면 단 한번 클라이언트가 로직을 고생해서 수정하면됩니다.
문제는 user가 확장되면?

data class User(
	val name: String,
    val age: Int?,
    val address: String?,
    val country: String, // 유저의 출신 국가 정보가 확장되었습니다.
)

그럼 기존 유저 수정 API를 확장해야하는지 말아야하는지 문제가 생깁니다.
먼저 확장한다쪽으로 간다면 하위 호환성이 걱정됩니다.
가령 확장되기 전의 클라이언트를 사용하는 유저들(app update를 안하는 유저)같은 경우가 있죠

아니면 확장하고 country의 값을 임의로 채워주는 방법도 있으나 이건 더욱 유지보수성을 떨어트리는 방법이라 생각됩니다.
만약 업데이트 안할 필드를 그냥 null로 하게한다면 age처럼 실제로 nullable한 도메인 정보를 수정 할 때 표현하기 애매합니다.
실제로 null로 입력하고 싶은건지 기존의 값을 그대로 사용하고 싶은건지 판단이 안됩니다.

그럼 기존 유저 수정 API를 확장하지 않고 User 국가 정보 수정이라는 API를 작성하였다 해보겠습니다.
국가 수정 이후에 새로운 필드가 확장되면 그때마다 API 갯수가 늘어나고
클라이언트는 이제 각 필드가 어떤 API를 통해 수정해야하는지 확인해야합니다.

네 굉장히 생산성이 떨어집니다.
하위호환성을 생각안해도 되는 상황이라면 그냥 기존 API의 Requst를 확장시키는 것도 좋은 방법입니다만 아쉽습니다.

그래서 PATCH의 구현에 맞게
1. 부분적으로 수정하면서
2. API가 확장되어도 하위호환성을 보장 할 수 있는 방법
위 두가지 조건에 맞는 방법을 소개합니다. 제 개인적인 생각일뿐이니 더 좋은 방법이 있다면 소개 부탁드립니다

해결

먼저 Jackson Optional Serializer/Deserializer를 적용하여 줍니다.

data class EditUserRequest {
    val name: Optional<String>? = null
	val age: Optional<Int>? = null
    val address: Optional<String>? = null
}

위 방식처럼 모든 필드에 Optional을 사용하고 nullable로 해줍니다.
그리고 해당 api에 다음과 같이 요청을 보냅니다

Request method:	PATCH
Request URI:    http://localhost:8080/api/v1/user/1
Body:
{
    "age": 33,
    "address": null
}

그럼 EditUserRequest의 맵핑된 정보는 다음과 같습니다

EditUserRequest {
  name : null
  age : Optional[33]
  address: Optional.empty
}

데이터를 보시면 실제로 요청시 데이터를 보내지 않은 name의 경우엔 null입니다.
age는 Optional로 감쌓여진걸 확인 할 수 있습니다.
또한 실제로 null을 입력한 address는 Optional.empty로 맵핑되어있습니다.

이점을 이용하면 마치 js에서 undefined를 흉내 낼 수 있습니다.
EditUserRequest에서 null이면 업데이트 요청이 없는 것입니다.
실제로 들어온 요청은 Optional내부의 값을 보고 판단하면 됩니다.

실제 요청이 null인지 단순히 업데이트를 하지않는 다는 표현인지 구분이 가능하여집니다.
또한 필드가 확장되어도 추가적인 api를 만들지 않아도됩니다.
위에서 말한 문제점 중 대부분이 해결되었습니다.

위 방법말고도 다른 방법이 있다면 추천 부탁드립니다.

profile
Software Developer

0개의 댓글