Armeria(3)
오늘 배운 것
오늘은 Armeria의 자동 API 문서 생성 기능(DocService
)이 Jackson 어노테이션을 사용하여 다형성(Polymorphic) 타입을 어떻게 처리하는지 깊이 파고들었다. 코드를 분석하던 중, 단순히 기능을 구현하는 것을 넘어 코드를 얼마나 견고하고 명확하게 만들 수 있는지에 대한 두 가지 중요한 원칙을 다시금 깨닫게 되었다. 바로 Fail-Fast 원칙과 견고한 폴백(Fallback) 전략이다.
1. requireNonNull
: 계약을 명시하고 빠르게 실패하라 (Fail-Fast)
가장 먼저 눈에 들어온 코드는 DiscriminatorInfo
클래스의 생성자였다.
@UnstableApi
public final class DiscriminatorInfo {
private final String propertyName;
private final Map<String,String> mapping;
/**
* Creates a new instance.
*/
public DiscriminatorInfo(String propertyName, Map<String, String> mapping) {
this.propertyName = requireNonNull(propertyName, "propertyName");
this.mapping = Map.copyOf(requireNonNull(mapping,"mapping"));
}
}
여기서 Objects.requireNonNull()
의 사용은 단순한 null
체크 이상의 의미를 가진다.
-
계약의 명시:
requireNonNull(propertyName, "propertyName")
코드는 “이 생성자를 호출하려면propertyName
은 반드시null
이 아니어야 한다”는 계약(Contract)을 코드 수준에서 명시한다. 이 객체를 생성하는 쪽은 이 계약을 지켜야만 한다. -
Fail-Fast 원칙의 실현: 만약
null
값이 들어오면, 이 객체가 생성되는 시점에 즉시NullPointerException
이 발생한다. 이것이 바로 Fail-Fast 원칙이다. 에러가 발생할 소지가 있다면, 이를 숨기고 프로그램의 다른 부분에서 예기치 않게 터지게 두는 것이 아니라, 문제가 발생한 그 즉시 실패시켜 원인을 명확히 알려주는 것이다. 만약 이 체크가 없었다면,null
인propertyName
필드는 나중에 다른 메서드에서 사용될 때 엉뚱한 곳에서NullPointerException
을 발생시켜 디버깅을 훨씬 어렵게 만들었을 것이다. -
명확한 에러 메시지:
requireNonNull
의 두 번째 인자로"propertyName"
을 넘겨주면, 예외 발생 시 “Cannot invoke method on null object” 같은 모호한 메시지 대신 “propertyName
is null”이라는 명확한 메시지를 전달해 준다. 사소해 보이지만 디버깅 효율을 극적으로 높여주는 중요한 디테일이다.
2. 폴백 전략: 사용자의 실수를 예측하고 유연하게 대처하라
다음으로 인상 깊었던 코드는 다형성 타입을 분석하여 mapping
정보를 구축하는 로직에 있었다.
final Map<String, String> mapping = new LinkedHashMap<>();
Arrays.stream(jsonSubTypes.value()).forEach(subType -> {
final Class<?> subClass = subType.value();
// 이 부분이 바로 '폴백 전략'이다.
final String key = subType.name() != null && !subType.name().isEmpty()
? subType.name()
: subClass.getSimpleName(); // 폴백(Fallback) 전략
final String schemaName = TypeSignature.ofStruct(subClass).name();
mapping.put(key, "#/definitions/" + schemaName);
});
@JsonSubTypes.Type
어노테이션에는 name
속성을 지정할 수 있다. 이 name
은 JSON 페이로드에서 어떤 클래스인지를 구분하는 값(discriminator value)이 된다.
위 코드는 이 name
을 가져와 key
로 사용하는데, 여기서 뛰어난 점은 사용자가 name
을 지정하지 않았을 경우를 대비한 폴백 전략(Fallback Strategy)을 갖추고 있다는 것이다.
- 기본 전략:
@JsonSubTypes.Type
에name
이 명시적으로 지정되어 있으면 그 값을 사용한다 (subType.name()
). 이것이 가장 우선순위가 높은 방법이다. - 폴백 전략: 만약 사용자가
name
을 지정하지 않았다면(null이거나 비어있다면), 프로그램이 비정상 종료되는 대신 하위 클래스의 간단한 이름(subClass.getSimpleName()
)을 대신 사용한다.
이러한 폴백 전략은 코드를 훨씬 더 견고하고 사용자 친화적으로 만들어준다. 사용자의 사소한 실수나 설정 누락에도 시스템은 합리적인 기본 동작을 제공하여 유연하게 대처할 수 있다. 개발자에게 모든 것을 완벽하게 설정하도록 강요하는 대신, 시스템이 어느 정도 지능적으로 대처해주는 것이다.
오늘의 정리
오늘 Armeria 코드를 통해 배운 것은 명확하다. 잘 설계된 코드는 단순히 기능만 구현하는 데 그치지 않는다.
requireNonNull
과 같은 도구를 통해 코드의 계약을 명_하고, 문제가 생겼을 때 빠르게 실패시켜 안정성을 높인다.- 다양한 사용자 시나리오를 예측하고 합리적인 폴백 전략을 마련하여 유연성과 견고함을 동시에 확보한다.
이 두 가지 원칙이 조화롭게 적용될 때 비로소 신뢰할 수 있고 사용하기 편한 소프트웨어가 만들어진다는 것을 다시 한번 느낄 수 있었다.