재검토중 이 글에서 설명하는 방법으로 이슈가 해결되지 않음을 확인했습니다. 하나의 탐구과정으로 봐주시면 감사하겠습니다.
문제의 발생
ObjectMapper의 NamingStrategies를 여러개 쓰이게 되는 상황이 발생했다.
여러 종류의 외부 API를 반영하는 서비스였기에 외부 API 마다의 json 네이밍 컨벤션이 달랐다.
특히, 하나의 NamingStrategies가 늘어날 때 마다 ObjectMapper에 대한 빈을 하나 더 등록하면서 주입 받을 때도 빈이 여러개라서 신경을 써야 하는 등의 불편함이 발생했다.
이 문제를 해결하는 방법을 찾던 중, 디자인 패턴인 Composite 패턴을 통해 해결하기로 했다.
Composite 패턴이란?
Component 라 불리는 최상위 클래스와 Leaf, Composite 라 불리는 하위 클래스가 협력하는 패턴이다.
Leaf는 Composite에서 사용될 클래스로 Composite에 여러 다른 구현체가 등록되어 사용된다.
클라이언트에서는 다양한 구현체를 한 번에 사용 가능하다.
뭔 소리지? 다이어그램을 살펴보자
구조는 알겠으니 이제 코드를 통해 알아보자
아래 코드에서 클래스의 각 역할은 다음과 같다.
PropertyNamingStrategy = Component
CompositePropertyNamingStrategy = Composite
ComponentPropertyNamingStrategy의 생성자로 받는 PropertyNamingStrategy = Leaf
objectMapper 메서드 = 클라이언트 (Component를 사용하는 주체)
CompositePropertyNamingStrategy
public class CompositePropertyNamingStrategy extends PropertyNamingStrategy {
private final PropertyNamingStrategy[] strategies;
public CompositePropertyNamingStrategy(final PropertyNamingStrategy... strategies) {
this.strategies = strategies;
}
@Override
public String nameForField(final MapperConfig<?> config, final AnnotatedField field, final String defaultName) {
String name = defaultName;
for (final PropertyNamingStrategy strategy : strategies) {
name = strategy.nameForField(config, field, name);
}
return name;
}
@Override
public String nameForGetterMethod(final MapperConfig<?> config, final AnnotatedMethod method, final String defaultName) {
String name = defaultName;
for (final PropertyNamingStrategy strategy : strategies) {
name = strategy.nameForGetterMethod(config, method, name);
}
return name;
}
@Override
public String nameForSetterMethod(final MapperConfig<?> config, final AnnotatedMethod method, final String defaultName) {
String name = defaultName;
for (final PropertyNamingStrategy strategy : strategies) {
name = strategy.nameForSetterMethod(config, method, name);
}
return name;
}
}
objectMapper 메서드
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
.setPropertyNamingStrategy(
new CompositePropertyNamingStrategy( // 여기서 사용된다.
PropertyNamingStrategies.SNAKE_CASE, // 첫 번째 Leaf
PropertyNamingStrategies.LOWER_CAMEL_CASE)); // 두 번째 Leaf
}
결론
위와 같은 코드를 통해 ObjectMapper를 하나만 사용할 수 있게 되었다.
또한, 여러개의 PropertyNamingStrategy를 한 번에 사용할 수 있게 되었다.