수정과 관련된 HTTP 메서드는 PUT 과 PATCH 메서드가 있다.
먼저 PUT 메서드는 사용자가 요청한 정보로 해당 자원의 정보를 모두 대체하는 것이다.
PUT 메서드를 이용하는 사용자는 해당 자원의 정보를 정확하게 알고 있어야 하고, 요청에 포함하지 않은 필드는 null 로 간주되어 해당 자원의 특정 정보도 null 로 변경하게 된다.
반면 PATCH 메서드는 사용가 요청에 포함한 정보만 부분 수정 이 가능하다.
사용자가 요청에 전달하지 않은 필드가 있다면, 해당 필드의 정보는 수정되지 않는다.
사용자 입장에선 수정하려는 자원의 모든 정보를 알지 않아도 보다 편리하게 API 를 사용 가능하다.
개념적으로는 PUT, PATCH 메서드는 이러한 차이가 있다. 사용자에게는 보다 편리한 PATCH 메서드이지만, 구현시 null 값을 다루는 다루는 데에 불편함이 있다.
예를 들어 다음 두 json 값을 통해 사용자가 수정 기능을 이용할 때 문제가 발생한다.
{
"name": "User1",
"phoneNumber": null
}
{
"name": "User1",
}
각 요청 payload는 사용자 입장에선 명확한 의도로 구성되었지만,
해당 객체를 dto를 통해 관리하는 java 코드에선 모두 phoneNumber 필드가 null 인 것만 알 뿐 사용자가 어떤 의도로 요청을 전달하였는지 알 수 없다.
즉 해당 필드를 null 로 변경 해야 할지, 또는 수정하지 않고 현재 상태를 유지 해야 할지 결정할 수 없다.
사용자에겐 편리한 PATCH 메서드이지만, 구현시에 이러한 맹점이 존재한다.
이러한 PATCH 메서드의 맹점을 해결하기 위한 대표적인 방법으로 JsonNullable 라이브러리가 있다.
해당 라이브러리를 이용하면 JsonNullable 이 적용된 필드를 dto 로 역직렬화 할 때
사용자가 null 로 전달했는지, 사용자가 수정 요청에 포함하지 않았는지 명확하게 구분이 가능하다.
JsonNullable 의 구현을 간단히 살펴보자.
public class JsonNullable<T> implements Serializable {
private static final long serialVersionUID = 1L;
private static final JsonNullable<?> UNDEFINED = new JsonNullable<>(null, false);
private final T value;
private final boolean isPresent;
private JsonNullable(T value, boolean isPresent) {
this.value = value;
this.isPresent = isPresent;
}
...
JsonNullable 내부에는 value 와 isPresent 필드가 존재한다.
아래와 같이 JsonNullable 을 phoneNumber 필드에 적용해보자.
public class UserUpdateRequestDto {
private String name;
private JsonNullable<String> phoneNumber;
}
이렇게 구성된 dto 클래스를 통해 위의 json 예시를 역직렬화 해보자.
{
"name": "User1",
"phoneNumber": null
}
위 요청은 다음과 같은 값을 갖게 된다.
phoneNubmer.value = null;
phoneNumber.isPresent = true;
{
"name": "User1",
}
phoneNumber = UNDEFINED;
phoneNubmer.value = null;
phoneNumber.isPresent = false;
따라서 구현하는 코드에서 phoneNumber 값을 어떻게 수정해야 할 지 명확하게 사용할 수 있다.
JsonNullable 적용을 통해 사용자가 요청한 필드 중 null 값을 가진 것들을 명확하게 처리할 수 있다.
하지만 해당 필드를 이용할 때 마다 isPresent 값을 확인하고 존재한다면 value 값에 접근하여 수정 기능을 구현해야 한다. 이전보다 코드가 조금 길어지고 복잡해 질 수 있다.
만약 구현 프로젝트에서 MapStruct 라이브러리를 이용하고 있다면, 이를 활용하여 JsonNullable 이용을 위한 반복적인 코드를 제거할 수 있다.
Swagger 와 Spring Validation 을 함께 사용할 경우, Spring Validation 애노테이션을 필드에 사용하면, 해당 제약사항도 모두 Swagger 문서에 포함하여 노출해준다.
하지만 JsonNullable 을 적용한 필드는 Spring Validation 애노테이션이 문서화에 적용되지 않기 때문에 직접 필드에 Swagger(SpringDoc) 애노테이션을 통해 제약사항을 명시해야 한다.
public class UserUpdateRequestDto {
@NotBlank // Java Validation 애노테이션. 스웨거 문서에도 포함
private String name;
@Size(10)
// Java Validation 애노테이션. JsonNullable이 적용되었기에 스웨거 문서에 포함되지 않음
private JsonNullable<@Max(10) String> phoneNumber; // 직접 SpringDoc의 @Max(10) 를 적용해야 스웨거 문서에 해당 조건 노출
}