계속해서 Spring Rest API 문서를 만들던 중 boolean형의 변수에서 문제가 생겼다.
먼저 근본적인 문제와 여러 해결 방안들을 정리하고, 적용한 방안에 대해 적어보았다.
그 이후 수정하면서 오류가 났던 순으로, mapstruct에서 생긴 문제와 테스트 코드에서 발생한 문제를 해결한 것을 적어보았다.
boolean 타입 변수에 is prefix(접두사)를 붙였을 때, mapstruct와 API 문서 작성을 위한 테스트 코드에서 문제가 발생했다.
mapstruct 연동과 편의를 위해 lombok 라이브러리를 사용했다.
이때, lombok에서 제공하는 @Getter
어노테이션은 boolean 형일 경우 앞에 get
이 아닌 is
를 자동으로 붙여준다고 한다.
그래서 isDefault
라고 쓰면 해당 객체의 변수를 가져올 때 default
로 인식해버리는 문제가 있었다.
케이스 별로 보기 전에 공통적으로 적용할 수 있는 해결 방안부터 정리했다.
가장 간단한 해결 방법으로, 여러 사이트에서 이 방법을 권고하고 있다.
private boolean active;
// lombok이 자동 생성해준다.
public boolean isActive() {
return active;
}
내 경우에는 boolean 타입의 이름이 isDefault
여서 default
가 변수 명이 되므로 적용할 수 없었다.
달리 적합한 이름을 찾아 변경하면 되지만, 이미 스펙을 boolean형은 앞에 ‘is’를 붙이기로 결정해서 이 방법은 넘어갔다.
lombok이 자동 생성하는 getter 메서드가 아니라, 직접 메서드를 정의하는 방법이다.
private boolean isActive;
// 직접 생성
public boolean getIsActive() {
return isActive;
}
나는 lombok에 의존하는 mapstruct를 사용하는데, 이 경우 @Getter
를 필수로 적용해야 해서 이 방법도 적용할 수 없었다.
Primitive type인 boolean
이 아니라, Wrapper Class인 Boolean
을 사용하면 변수 이름을 그대로 사용할 수 있다.
Boolean
은 Refrence type이어서 그대로 사용하는 듯하다.
private Boolean isActive;
// lombok이 자동 생성해준다.
public boolean getIsActive() {
return isActive;
}
다만, 이러면 모든 boolean 변수를 Boolean 객체로 사용해야 하는데, 다음 문제들이 우려되었다.
따라서 이 방법을 적용할 땐 request/response DTO만 Boolean으로 적어놓고, 도메인 계층으로 넘길 때는 boolean으로 사용하게끔 했었다.
@Mapping
을 이용해 boolean과 Boolean을 매핑lombok 설정: @Getter and @Setter
lombok.config
설정 파일을Lombok 설정 파일에서 lombok.getter.noIsPrefix
속성의 값을 변경한다.
lombok.getter.noIsPrefix
은 @Getter
어노테이션으로 생성된 getter 메서드 이름에서 is
접두사를 생략하도록 설정한다.
lombok.getter.noIsPrefix = true
단, lombok 설정을 변경하면 아래와 같은 문제가 발생할 수 있다. (AI Gemini 이용)
내가 하고 싶은 것은 다음과 같았다.
그리고 최종적으로 lombok 설정 파일을 삽입하기로 결정했다.
lombok.config
파일을 추가한다.lombok.getter.noIsPrefix = true
를 적는다.💡 아래에선 오류와 오류를 해결하기 위해 찾아봤던 과정(lombok 설정 파일 적용 방법 제외)을 정리했습니다.
MapStruct를 이용해 mapper를 자동으로 생성해주고 있었는데, 이때 매핑이 제대로 되지 않는 문제가 있었다.
찾아본 결과 해결 방안은 아래와 같았다.
is 접두사가 붙은 필드를 수동으로 매핑하도록 @Mapping
을 사용한다.
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface MemberDeliveryAddressMapper {
MemberDeliveryAddressMapper INSTANCE = Mappers.getMapper(MemberDeliveryAddressMapper.class);
@Mapping(source = "default", target = "isDefault")
DeliveryAddress toDeliveryAddress(MemberDeliveryAddressRegisterRequest deliveryAddressRegisterRequest);
@Mapping(source = "default", target = "isDefault")
MemberDeliveryAddressRegisterResponse toMemberDeliveryAddressRegisterResponse(DeliveryAddress deliveryAddress);
}
앞서 설명한 것처럼, boolean 대신 Boolean을 사용하는 방식이다.
@Mapping
으로 일일이 설정하지 않아도 된다.분명 매핑은 잘 된 것 같은데, 테스트 코드를 돌리는데 계속해서 오류가 났다.
빨간색은 테스트 코드의 예상 결과가 다르다는 것이니 위에 오류 메시지를 다시 읽어봤다.
JSON parse error: Unrecognized field "default" (class com.flab.funding.infrastructure.adapters.input.data.request.MemberDeliveryAddressRegisterRequest), not marked as ignorable]
즉, JSON parse 오류가 났다는 건데 문제는 아래 코드에 있었다.
public class MemberDeliveryAddressRestAdapterTest {
...
@Test
void registerDeliveryAddress() throws Exception {
// given
MemberDeliveryAddressRegisterRequest request = MemberDeliveryAddressRegisterRequest.builder()
.userKey("1L")
.isDefault(true)
.zipCode("01234")
.address("서울특별시 강서구")
.addressDetail("OO 아파트 xxx동 xxxx호")
.recipientName("홍길동")
.recipientPhone("010-1111-2222")
.build();
//when
//then
this.mockMvc.perform(post("/deliveryAddresses")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("{class-name}/{method-name}",
requestFields(
..
),
responseFields(
...
)));
}
}
바로 여기서, JSON으로 변환할 때 isDefault
가 default
로 변경되어서 문제였다.
JsonProperty는 Java 객체와 JSON 간의 매핑을 위한 Jackson 라이브러리에서 제공하는 어노테이션이다.
Java 객체의 필드와 JSON 객체의 속성을 연결하는 역할을 하며, 아래와 같이 Json 속성 명을 직접 지정할 수 있다.
@JsonProperty("isDefault")
private Boolean isDefault;
boolean을 Boolean으로 변경한다. 이때, Boolean을 사용하는 것이 마음이 들지 않았던 터라 한 가지 생각을 했다.
@Mapping
으로 Boolean과 boolean을 매핑 (이름은 동일하게 선언했다)@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface MemberDeliveryAddressMapper {
MemberDeliveryAddressMapper INSTANCE = Mappers.getMapper(MemberDeliveryAddressMapper.class);
// 도메인으로 변환할 때는 둘 다 isDefault
@Mapping(source = "isDefault", target = "isDefault")
DeliveryAddress toDeliveryAddress(MemberDeliveryAddressRegisterRequest deliveryAddressRegisterRequest);
@Mapping(source = "default", target = "isDefault")
MemberDeliveryAddressRegisterResponse toMemberDeliveryAddressRegisterResponse(DeliveryAddress deliveryAddress);
}
즉 외부에서 값을 받아와 JSON으로 변환할 때만 Boolean을 사용했다.
이러면 도메인 계층으로 넘길 때 값이 null이면 false로 들어갈 거고, 모든 변수를 Boolean으로 사용하는 것이 아니니 문제도 어느 정도 해결되는 게 아닐까?
이유는 찾았는데 문제를 어떻게 해결해야 할 지가 고민돼서, 몇 주에 걸쳐 계속 조사하면서 수정했다.
모든 boolean를 Boolean으로 썼다가, 도메인 계층에선 boolean으로 쓰게 수정했다가, 다시 lombok 설정을 발견해서 최종 수정까지 거쳤다.
이랬다가 저랬다가 한 커밋 내역을 보면 좀 심란해진다. 실무에서는 is
를 사용하지 않는 편이 좋을까?
이건 계속 고민될 거 같다.
MapStruct가 is 접두사를 가진 boolean 필드를 매핑하지 못하는 문제
https://github.com/mapstruct/mapstruct/issues/1943
[Error] Response JSON에서 Boolean의 is가 생략되는 문제
RequestBody Annotation 사용 시 boolean 변수 바인딩 에러