TIL 3일차

HanEol~·어제
post-thumbnail

개요

오늘은 대용량 처리에 대해 배운것을 실습을 통해 구현한 것과 문제가 있었던 부분을 기록합니다.

구조

Producer: Order (주문 서비스)
Exchange: exchange
Queue: product-q, payment-q
Consumer: Product (상품 서비스 - 2개 인스턴스), Payment (결제 서비스 - 1개 인스턴스)

이 구조로 RabbitMQ의 기본적인 사용을 학습했다.

문제 상황

product 어플리케이션에서 소비를 할때 메시지 헤더의 TypeId를 매핑하는 과정에서 문제가 생겼다는 에러가 발생

TypeId 문제로 확인을 해보니 Order 어플리케이션의 DeliveryMessage의 헤더와 Product 어플리케이션의 DeliveryMessage 헤더가 달라 컨버팅을 하는데 무리가 있던것으로 판단하여 Configuration에 설정을 했다.

   @Bean
    public JacksonJsonMessageConverter jacksonJsonMessageConverter() {
        JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
        converter.setTypePrecedence(INFERRED);
        return converter;
    }

setTypePrecedence 메소드를 들어가보면
By default, if the type is concrete (not abstract, not an interface), this will be used ahead of type information provided in the TypeId and associated headers provided by the sender.
라는 주석이 있다. 해석하면
기본적으로 메서드 파라미터 타입이 구체적인(concrete) 타입이라면, 송신자가 TypeId 헤더에 넣어준 타입 정보보다 메서드 파라미터 타입을 우선해서 사용한다.

추가를 안해도 되는 문제였지만 추가를 해도 똑같은 문제가 발생

생각을 해본 것은 converter가 제대로 동작을 안하는구나로 생각했고 보니 @Configuration을 빼먹었다...
추가를 하니 이제는 빌드가 안된다..

또 다른 문제 Jackson2 vs Jackson3 충돌

빌드 실패로그를 확인해보니 아래와 같은 런타임 에러가 발생하고 있었습니다.

원인 파악

  1. 현재 프로젝트는 Spring Boot 4 환경입니다.
  2. Spring Boot 4 / Spring AMQP의 JacksonJsonMessageConverter는 Jackson 3 (tools.jackson.core.*) 패키지 체계를 표준으로 요구합니다.
  3. 하지만 build.gradle에 구버전 라이브러리인 Jackson 2 (com.fasterxml.jackson.core.*) 의존성이 들어가 있었던 것이 원인이었습니다.
 	implementation 'com.fasterxml.jackson.core:jackson-databind'
    implementation 'com.fasterxml.jackson.core:jackson-core'
    implementation 'com.fasterxml.jackson.core:jackson-annotations'

Converter는 tools.jackson 체계의 클래스를 찾으려 하는데, 프로젝트에는 com.fasterxml.jackson 클래스만 존재하여 NoClassDefFoundError 및 TYPE_ID 변환 에러가 발생했던 것입니다.

해결

Jackson 3 규격인 tools.jackson.core 계열로 의존성 및 패키지 체계를 통일해 주었습니다.

 	implementation 'tools.jackson.core:jackson-core'
    implementation 'tools.jackson.core:jackson-databind'

의문점: Order 서비스는 왜 직렬화가 잘 되었을까?

"그렇다면 Order 애플리케이션은 explicit하게 Jackson 3 의존성을 선언하지 않고도 어떻게 exchange로 메시지를 잘 직렬화해서 던질 수 있었을까?"라는 의문이 생겼습니다.

답은

 	implementation 'org.springframework.boot:spring-boot-starter-web'

Order 서비스는 REST Controller 작성을 위해 spring-boot-starter-web을 포함하고 있었고, 이 Starter 라이브러리가 전이 의존성(Transitive Dependency)으로 Spring Boot 4 버전에 맞는 Jackson 3 (tools.jackson)을 이미 제공하고 있었기 때문입니다.

./gradlew dependencyInsight \
--dependency jackson-core \
--configuration runtimeClasspath

터미널에 입력후 확인

tools.jackson.core:jackson-core:3.1.5
+--- tools.jackson:jackson-bom:3.1.5
|    +--- tools.jackson.core:jackson-core:3.1.5 (*)
|    \--- tools.jackson.core:jackson-databind:3.1.5
|         +--- org.springframework.boot:spring-boot-jackson:4.1.1
|         |    \--- org.springframework.boot:spring-boot-starter-jackson:4.1.1
|         |         \--- org.springframework.boot:spring-boot-starter-web:4.1.1
|         |              \--- runtimeClasspath (requested org.springframework.boot:spring-boot-starter-web)
|         \--- tools.jackson:jackson-bom:3.1.5 (*)
\--- tools.jackson.core:jackson-databind:3.1.5 (*)

배운점

  1. Spring Boot 4의 Jackson 3 마이그레이션: Jackson 2 (com.fasterxml.jackson)와 Jackson 3 (tools.jackson)는 완전히 다른 패키지 네임스페이스를 가지므로 메이저 버전 변경 시 패키지 충돌 여부를 먼저 확인해야 합니다.
  2. RabbitMQ 메시지 역직렬화 전략: TypeId 헤더 기반의 타입 매핑 방식과, 메서드 파라미터 타깃 기반의 INFERRED 매핑 방식의 동작 차이를 검토했습니다.
  3. Starter 의존성 전이 구조 확인: gradlew dependencyInsight를 활용해 클래스패스 상에 포함된 라이브러리의 출처를 추적하는 노하우를 습득했습니다.
profile
기록을 통해 앞으로 나아가자!

0개의 댓글