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 원칙이다. 에러가 발생할 소지가 있다면, 이를 숨기고 프로그램의 다른 부분에서 예기치 않게 터지게 두는 것이 아니라, 문제가 발생한 그 즉시 실패시켜 원인을 명확히 알려주는 것이다. 만약 이 체크가 없었다면, nullpropertyName 필드는 나중에 다른 메서드에서 사용될 때 엉뚱한 곳에서 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)을 갖추고 있다는 것이다.

  1. 기본 전략: @JsonSubTypes.Typename이 명시적으로 지정되어 있으면 그 값을 사용한다 (subType.name()). 이것이 가장 우선순위가 높은 방법이다.
  2. 폴백 전략: 만약 사용자가 name을 지정하지 않았다면(null이거나 비어있다면), 프로그램이 비정상 종료되는 대신 하위 클래스의 간단한 이름(subClass.getSimpleName())을 대신 사용한다.

이러한 폴백 전략은 코드를 훨씬 더 견고하고 사용자 친화적으로 만들어준다. 사용자의 사소한 실수나 설정 누락에도 시스템은 합리적인 기본 동작을 제공하여 유연하게 대처할 수 있다. 개발자에게 모든 것을 완벽하게 설정하도록 강요하는 대신, 시스템이 어느 정도 지능적으로 대처해주는 것이다.

오늘의 정리

오늘 Armeria 코드를 통해 배운 것은 명확하다. 잘 설계된 코드는 단순히 기능만 구현하는 데 그치지 않는다.

  • requireNonNull과 같은 도구를 통해 코드의 계약을 명_하고, 문제가 생겼을 때 빠르게 실패시켜 안정성을 높인다.
  • 다양한 사용자 시나리오를 예측하고 합리적인 폴백 전략을 마련하여 유연성과 견고함을 동시에 확보한다.

이 두 가지 원칙이 조화롭게 적용될 때 비로소 신뢰할 수 있고 사용하기 편한 소프트웨어가 만들어진다는 것을 다시 한번 느낄 수 있었다.

results matching ""

    No results matching ""