Skip to content

feat :: Debezium Connector Setting#557

Merged
ByunDohwi merged 8 commits intodevelopfrom
539-kafka-설정-자동화
Dec 9, 2025

Hidden character warning

The head ref may contain hidden characters: "539-kafka-\uc124\uc815-\uc790\ub3d9\ud654"
Merged

feat :: Debezium Connector Setting#557
ByunDohwi merged 8 commits intodevelopfrom
539-kafka-설정-자동화

Conversation

@kangeunchan
Copy link
Contributor

@kangeunchan kangeunchan commented Dec 1, 2025

Debezium MySQL Connetcor를 자동으로 등록하도록 구현했습니다.

Summary by CodeRabbit

  • 신규 기능

    • Debezium 설정 바인딩 및 커넥터 요청/설정 생성 기능 추가
    • 애플리케이션 기동 시 커넥터 자동 등록 및 준비 상태 대기 활성화
    • 커넥터 관리(등록·업데이트·삭제·상태조회) 서비스 및 비동기 초기화 구성 추가
  • 개선 사항

    • REST 클라이언트 타임아웃 외부 설정화 및 Debezium 전용 재시도·오류 처리·로깅 강화
    • 비동기 실행·예외 처리 핸들링 개선
  • 잡무

    • Apache HTTP Client 의존성 및 관련 버전 추가

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 1, 2025

Walkthrough

Debezium 설정 바인딩 모델, 클라이언트/타임아웃 속성, 전용 RestTemplate, Debezium 커넥터 관리 서비스·초기화 컴포넌트, 관련 예외·에러코드, 비동기/재시도 설정 및 Apache HTTP Client 의존성이 추가되었습니다.

Changes

Cohort / File(s) 변경 요약
Debezium 설정 모델
src/main/kotlin/dsm/pick2024/global/config/debezium/DebeziumProperties.kt
debezium 바인딩용 public 데이터 클래스 DebeziumProperties 추가(중첩: ConnectorConfig, DatabaseConfig, SchemaConfig, OutboxConfig) 및 toConnectorConfigMap()/toConnectorRequest() 메서드 제공
Debezium 클라이언트/재시도 속성
src/main/kotlin/dsm/pick2024/global/config/debezium/DebeziumClientProperties.kt, src/main/kotlin/dsm/pick2024/global/config/debezium/DebeziumRetryProperties.kt
Debezium HTTP 클라이언트 타임아웃 바인딩(debezium.client.*)과 재시도 설정(debezium.retry.*)용 ConfigurationProperties 추가
Debezium RestTemplate 구성
src/main/kotlin/dsm/pick2024/global/config/debezium/DebeziumRestTemplateConfig.kt
debezium.enabled=true 조건에서 Debezium 전용 RestTemplate 빈 제공(타임아웃 적용, HttpComponentsClientHttpRequestFactory 사용)
RestTemplate 공통 구성/속성
src/main/kotlin/dsm/pick2024/global/config/restemplate/RestTemplateConfig.kt, src/main/kotlin/dsm/pick2024/global/config/restemplate/RestTemplateProperties.kt
RestTemplateConfig가 RestTemplateProperties 주입을 받도록 변경하고 HttpComponentsClientHttpRequestFactory로 RestTemplate 생성(접속/읽기 타임아웃 적용). RestTemplateProperties 추가
비동기/재시도 활성화
src/main/kotlin/dsm/pick2024/global/config/async/AsyncConfig.kt
@EnableAsync@EnableRetry 활성화, 커스텀 ThreadPoolTaskExecutor 및 비동기 예외 핸들러 구성
커넥터 관리 서비스
src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorService.kt
Debezium Connect REST와 통신해 커넥터 존재 확인(GET), 생성(POST), 업데이트(PUT), 상태조회(GET), 삭제(DELETE) 및 대기·재시도 로직을 수행하는 Spring @Service 추가
애플리케이션 초기화 컴포넌트
src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorInitializer.kt
debezium.enabled=true 조건에서 ApplicationReadyEvent 수신 시 비동기로 registerConnector() 호출하고 상태를 폴링/로그하는 조건부 컴포넌트 추가
예외 및 에러코드
src/main/kotlin/dsm/pick2024/infrastructure/debezium/exception/DebeziumConnectorException.kt, src/main/kotlin/dsm/pick2024/infrastructure/debezium/exception/DebeziumConfigurationException.kt, src/main/kotlin/dsm/pick2024/infrastructure/debezium/exception/DebeziumRetryableException.kt, src/main/kotlin/dsm/pick2024/global/error/exception/ErrorCode.kt
Debezium 전용 예외 객체들 추가 및 ErrorCodeDEBEZIUM_CONNECTOR_ERROR(500) 항목 추가
빌드 설정 / 의존성
build.gradle.kts, buildSrc/src/main/kotlin/Dependencies.kt, buildSrc/src/main/kotlin/DependencyVersions.kt
Apache HTTP Client 의존성 및 버전 상수(APACHE_HTTP_CLIENT) 추가

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant App as Application
    participant Init as DebeziumConnectorInitializer
    participant Service as DebeziumConnectorService
    participant Props as DebeziumProperties
    participant RT as Debezium RestTemplate
    participant Connect as Debezium Connect (HTTP)

    App->>Init: ApplicationReadyEvent (비동기)
    Init->>Service: registerConnector()
    Service->>Props: toConnectorRequest() / toConnectorConfigMap()
    Service->>RT: GET /connectors/{name}
    alt exists (200)
        RT-->>Service: 200 OK
        Service->>RT: PUT /connectors/{name}/config
        RT-->>Service: 2xx
    else not found (404)
        RT-->>Service: 404
        Service->>RT: POST /connectors
        RT-->>Service: 2xx
    end
    loop poll until RUNNING/FAILED/timeout
        Service->>RT: GET /connectors/{name}/status
        RT-->>Service: status payload
    end
    Service-->>Init: 완료 / 상태 로그
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • 주의 필요 영역:
    • DebeziumConnectorService: HTTP 응답 상태별 분기(4xx/5xx) 처리, 재시도(backoff) 및 예외 매핑 검증
    • DebeziumProperties: 생성되는 connector 설정 키/값이 Debezium Connect 요구사항과 일치하는지 확인
    • DebeziumConnectorInitializer: 조건부 활성화 및 비동기 등록 시 중복 실행/타이밍 이슈 검토
    • RestTemplateConfig / DebeziumRestTemplateConfig: HttpComponentsClientHttpRequestFactory 설정과 타임아웃 적용 확인
    • AsyncConfig: 스레드풀 크기·거부 정책과 비동기 예외 처리 로직 검토

Poem

🐇 냄새 맡고 달려왔네, 설정 밭에 깡충
POST로 심고 PUT으로 보살펴 싹 틔우네
폴링하며 기다리다 RUNNING이면 깡총
실패면 조심스레 로그 주머니에 담고
당근 들고 배포 들판을 한 바퀴 돌아요 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 Debezium 커넥터 설정 자동화라는 주요 변경사항을 명확하게 설명하고 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 539-kafka-설정-자동화

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorService.kt (2)

17-21: Gson 기본값으로 인해 Spring Bean 주입이 무시될 수 있습니다.

생성자 파라미터에 기본값 Gson()이 있으면 Spring이 Gson Bean을 주입하지 않고 기본값을 사용합니다. 프로젝트에 커스텀 Gson 설정이 있다면 적용되지 않을 수 있습니다.

 @Service
 class DebeziumConnectorService(
     private val debeziumProperties: DebeziumProperties,
-    private val restTemplate: RestTemplate,
-    private val gson: Gson = Gson()
+    private val restTemplate: RestTemplate
 ) {
+    private val gson = Gson()
     private val log = LoggerFactory.getLogger(this::class.java)

또는 프로젝트에 Gson Bean이 정의되어 있다면 기본값을 제거하세요:

-    private val gson: Gson = Gson()
+    private val gson: Gson

66-72: RestTemplate은 4xx/5xx 응답 시 예외를 던지므로 else 분기에 도달하지 않습니다.

RestTemplate은 기본적으로 4xx/5xx HTTP 상태 코드에 대해 HttpStatusCodeException을 던집니다. 따라서 response.statusCode.is2xxSuccessful이 false인 경우 else 분기에 도달하기 전에 예외가 발생합니다.

else 분기를 제거하거나, 커스텀 ResponseErrorHandler를 설정하여 예외 없이 응답을 처리하도록 변경할 수 있습니다:

         if (response.statusCode.is2xxSuccessful) {
             log.info("Debezium 커넥터가 성공적으로 생성됨: {}", debeziumProperties.connector.name)
             log.debug("응답: {}", response.body)
-        } else {
-            log.error("커넥터 생성 실패. 상태: {}, 응답: {}", response.statusCode, response.body)
-            throw DebeziumConnectorException
         }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 840634b and 0ef9606.

📒 Files selected for processing (5)
  • src/main/kotlin/dsm/pick2024/global/config/debezium/DebeziumProperties.kt (1 hunks)
  • src/main/kotlin/dsm/pick2024/global/error/exception/ErrorCode.kt (1 hunks)
  • src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorInitializer.kt (1 hunks)
  • src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorService.kt (1 hunks)
  • src/main/kotlin/dsm/pick2024/infrastructure/debezium/exception/DebeziumConnectorException.kt (1 hunks)
🔇 Additional comments (5)
src/main/kotlin/dsm/pick2024/global/error/exception/ErrorCode.kt (1)

59-60: LGTM!

Debezium 커넥터 오류를 위한 새로운 에러 코드가 적절하게 추가되었습니다. HTTP 500 상태 코드와 한국어 메시지가 기존 에러 코드들과 일관성을 유지합니다.

src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorInitializer.kt (1)

9-38: LGTM!

초기화 로직이 잘 구성되어 있습니다:

  • @ConditionalOnProperty를 통한 조건부 활성화
  • ApplicationReadyEvent를 사용한 적절한 초기화 시점
  • 외부 서비스 의존성에 대한 적절한 예외 처리 (애플리케이션 시작 차단 방지)

주석으로 설계 의도가 잘 문서화되어 있습니다.

src/main/kotlin/dsm/pick2024/global/config/debezium/DebeziumProperties.kt (2)

50-89: LGTM!

Debezium MySQL 커넥터 설정 맵이 올바르게 구성되어 있습니다. Outbox Event Router 변환 설정도 적절히 포함되어 있습니다.


4-7: @ConstructorBinding is required for Spring Boot 2.7.16.

This project uses Spring Boot 2.7.16, where @ConstructorBinding is still necessary and not deprecated. (Note: @ConstructorBinding deprecation only applies to Spring Boot 3.x where constructor binding is automatic for single-constructor classes.)

The @ConfigurationProperties class is already properly activated via @ConfigurationPropertiesScan in Pick2024ServerApplication.kt, so no additional configuration is needed.

src/main/kotlin/dsm/pick2024/infrastructure/debezium/exception/DebeziumConnectorException.kt (1)

6-8: No changes needed. The singleton exception pattern used in DebeziumConnectorException is consistent with the entire codebase—all 48 custom exceptions in the project follow this same object pattern extending PickException. This is the established convention.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorInitializer.kt (1)

33-37: 외부 서비스 의존성에 대한 장애 격리 전략이 적절합니다.

Debezium이 외부 서비스이므로 초기화 실패 시에도 애플리케이션을 시작하도록 구성한 설계가 합리적입니다. 주석에서 대안적인 동작(서비스 중단)을 구현하는 방법도 명확히 설명하고 있습니다.

다만, 커넥터 초기화 실패 시 모니터링 알림을 설정하여 운영팀이 문제를 인지할 수 있도록 하는 것을 권장합니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 0ef9606 and 8021d6b.

📒 Files selected for processing (1)
  • src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorInitializer.kt (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Check Code Quality
🔇 Additional comments (2)
src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorInitializer.kt (2)

9-15: 조건부 빈 등록 설정이 올바릅니다.

@ConditionalOnProperty를 사용하여 debezium.enabled=true일 때만 활성화되도록 구성한 것이 적절합니다. matchIfMissing=false 설정으로 명시적 opt-in을 요구하는 것도 좋은 설계입니다.


16-18: 생성자 주입 방식이 적절합니다.

의존성을 생성자를 통해 주입받고 private val로 불변성을 보장하는 것은 Spring과 Kotlin의 모범 사례입니다.

@ByunDohwi ByunDohwi assigned ByunDohwi and kangeunchan and unassigned ByunDohwi Dec 1, 2025
@ByunDohwi
Copy link
Member

코드래빗 코멘트 해결해주세요

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/main/kotlin/dsm/pick2024/global/config/restemplate/RestTemplateConfig.kt (1)

13-20: RestTemplate 타임아웃 설정은 적절하지만, RestTemplateBuilder를 사용하면 Boot 친화적으로 정리할 수 있습니다.

현재 구현(명시적인 connect/read timeout + HttpComponentsClientHttpRequestFactory)은 기능적으로 문제 없어 보입니다. 다만 Spring Boot 환경이라면 RestTemplateBuilderDuration을 활용하면 설정이 더 명확해지고, 전역적으로 커스터마이징된 빌더 설정(인터셉터, 메시지 컨버터 등)을 그대로 재사용할 수 있습니다.

아래와 같이 리팩터링을 고려해 볼 수 있습니다 (선택 사항):

-    @Bean
-    fun restTemplate(): RestTemplate {
-        val factory = HttpComponentsClientHttpRequestFactory().apply {
-            setConnectTimeout(5000)
-            setReadTimeout(10000)
-        }
-
-        return RestTemplate(factory)
-    }
+    @Bean
+    fun restTemplate(builder: RestTemplateBuilder): RestTemplate =
+        builder
+            .setConnectTimeout(Duration.ofSeconds(5))
+            .setReadTimeout(Duration.ofSeconds(10))
+            .build()

이 방식이 프로젝트의 Spring Boot 버전 및 기존 RestTemplate 사용 방식과 잘 맞는지는 한 번만 확인해 주시면 좋겠습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 8021d6b and bc2e064.

📒 Files selected for processing (1)
  • src/main/kotlin/dsm/pick2024/global/config/restemplate/RestTemplateConfig.kt (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Check Code Quality
🔇 Additional comments (1)
src/main/kotlin/dsm/pick2024/global/config/restemplate/RestTemplateConfig.kt (1)

3-8: Debezium HTTP 호출을 위한 RestTemplate 관련 import 추가는 현재 구현과 잘 맞습니다.

새로 도입된 RestTemplate 설정(시간 제한 및 HttpComponents 사용)을 고려했을 때, 필요한 import 구성이 자연스럽고 일관성 있어 보입니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between bc2e064 and a97a804.

📒 Files selected for processing (1)
  • src/main/kotlin/dsm/pick2024/global/config/restemplate/RestTemplateConfig.kt (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Check Code Quality

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/main/kotlin/dsm/pick2024/global/config/restemplate/RestTemplateProperties.kt (1)

1-11: RestTemplateProperties 구성 클래스 정의는 적절하며, 필요 시 Duration 타입으로의 확장 여지가 있습니다

  • rest-template 프리픽스로 타임아웃 설정을 한 곳에 모아두어 설정 가독성과 관리성이 좋아졌고, 기본값도 기존 하드코딩 값(5000/10000ms)과 동일해 동작이 유지됩니다.
  • 향후 application.yml 에서 5s, 10s 같은 표현으로 타임아웃을 더 유연하게 지정하고 싶다면, Int 대신 Duration 타입으로 선언한 뒤 RestTemplateConfig 에서 toMillis().toInt() 로 변환하는 패턴도 고려해 볼 수 있습니다. 지금 구조도 충분히 실용적이니 꼭 바꿀 필요는 없고, 설정 유연성이 필요해질 때 선택지로 생각해 두시면 될 것 같습니다.
buildSrc/src/main/kotlin/Dependencies.kt (1)

52-54: Dependencies.APACHE_HTTP_CLIENT 상수 추가로 빌드 스크립트 일관성이 좋아졌습니다

APACHE_HTTP_CLIENT 를 별도 상수로 정의하고 DependencyVersions.APACHE_HTTP_CLIENT 와 연동해, build.gradle.kts 쪽에서 중복 문자열 없이 의존성을 선언할 수 있게 된 점이 좋습니다.
추가로, 프로젝트 내에서 HTTP 호출이 RestTemplate+Apache HttpClient 조합 외에 다른 스택(WebClient, Reactor Netty 등)과 혼재되어 있다면, 어느 경로가 어떤 용도로 쓰이는지(예: Debezium용 전용 RestTemplate vs 일반 외부 API용) 간략히 정리해 두시면 장애 분석이나 성능 튜닝 시에 도움이 될 것 같습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between a97a804 and 7749807.

📒 Files selected for processing (5)
  • build.gradle.kts (1 hunks)
  • buildSrc/src/main/kotlin/Dependencies.kt (1 hunks)
  • buildSrc/src/main/kotlin/DependencyVersions.kt (1 hunks)
  • src/main/kotlin/dsm/pick2024/global/config/restemplate/RestTemplateConfig.kt (1 hunks)
  • src/main/kotlin/dsm/pick2024/global/config/restemplate/RestTemplateProperties.kt (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Check Code Quality
🔇 Additional comments (3)
build.gradle.kts (1)

83-85: Apache HTTP Client 의존성 추가로 RestTemplateConfig 런타임 이슈가 해소되었습니다

HttpComponentsClientHttpRequestFactory 를 사용하는 RestTemplateConfig 와 맞게 Dependencies.APACHE_HTTP_CLIENT 를 별도 의존성으로 선언해서, 런타임 ClassNotFoundException 리스크를 잘 제거하셨습니다. 빌드 스크립트 구조(DependencyVersions/Dependencies 경유)와도 일관성이 있습니다.
다만 4.5.14 버전이 현재 인프라의 보안·호환성 기준(예: 사용 중인 Spring Boot/Spring Framework 버전, 사내 CVE 정책 등)에 부합하는지는 한 번 더 점검해 주세요.

buildSrc/src/main/kotlin/DependencyVersions.kt (1)

17-17: APACHE_HTTP_CLIENT 버전 상수 정의가 의존성 관리 흐름과 잘 맞습니다

Dependencies.APACHE_HTTP_CLIENT 와 연결되는 APACHE_HTTP_CLIENT = "4.5.14" 상수를 별도로 둬서 버전 관리가 중앙집중화된 점이 좋습니다. 다른 라이브러리 버전 상수들과도 포맷이 일관적입니다.
운영 환경 보안 정책(CVE 대응, 장기 지원 여부 등)에 맞는 버전인지, 그리고 프로젝트에서 사용하는 Spring 생태계 버전과의 호환성은 주기적으로 한 번씩 점검해 두시면 좋겠습니다.

src/main/kotlin/dsm/pick2024/global/config/restemplate/RestTemplateConfig.kt (1)

5-20: RestTemplate 타임아웃 외부화와 HttpComponents 팩토리 적용이 깔끔하게 정리되었습니다

  • RestTemplateConfigRestTemplateProperties 를 생성자 주입받고, HttpComponentsClientHttpRequestFactoryconnectTimeoutMs/readTimeoutMs 를 적용하는 구조라서 환경별 타임아웃 조정이 용이해졌습니다.
  • 기존과 동일한 시그니처의 @Bean fun restTemplate(): RestTemplate 를 유지하고 있어, Debezium 연동을 포함한 기존 RestTemplate 주입 코드와도 호환성이 좋아 보입니다.
  • 다만 RestTemplateProperties 가 실제로 바인딩되려면 어딘가에서 @ConfigurationPropertiesScan 또는 @EnableConfigurationProperties(RestTemplateProperties::class) 가 선언되어 있어야 합니다. 이미 글로벌 설정에 포함되어 있다면 그대로 두셔도 되고, 아니라면 해당 애너테이션 추가를 한 번 검토해 주세요.

이 변경으로 이전 코드래빗 코멘트에서 지적되었던 httpclient 의존성 누락 및 타임아웃 하드코딩 이슈는 모두 해결된 것으로 보입니다.

  handling

  - Add retry logic with exponential backoff (max 5 attempts)
  - Implement async initialization to prevent app startup blocking
  - Add dedicated RestTemplate with extended timeout (60s)
  - Separate retryable and non-retryable exceptions
  - Improve status check with polling mechanism (30s max)
  - Enable @EnableRetry and @EnableAsync configurations
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
src/main/kotlin/dsm/pick2024/infrastructure/debezium/exception/DebeziumConfigurationException.kt (1)

6-8: 동일한 ErrorCode 사용 (중복 지적)

이 파일도 DebeziumRetryableException과 동일한 ErrorCode.DEBEZIUM_CONNECTOR_ERROR를 사용합니다. 앞서 지적한 것과 동일한 이슈입니다.

src/main/kotlin/dsm/pick2024/global/config/debezium/DebeziumClientProperties.kt (1)

6-11: @ConstructorBinding 사용 확인 필요 (중복 지적)

DebeziumRetryProperties와 동일하게 @ConstructorBinding 어노테이션 사용에 대한 Spring Boot 3.x 호환성 확인이 필요합니다.

🧹 Nitpick comments (2)
src/main/kotlin/dsm/pick2024/infrastructure/debezium/exception/DebeziumRetryableException.kt (1)

6-8: DebeziumConfigurationException과 동일한 ErrorCode 사용

DebeziumRetryableExceptionDebeziumConfigurationException 모두 ErrorCode.DEBEZIUM_CONNECTOR_ERROR를 사용하고 있습니다. 재시도 가능한 오류와 설정 오류를 구분하기 위해 별도의 ErrorCode를 사용하는 것이 디버깅과 에러 추적에 더 유용합니다.

예: DEBEZIUM_CONNECTOR_RETRYABLE_ERROR, DEBEZIUM_CONNECTOR_CONFIGURATION_ERROR

src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorService.kt (1)

141-165: JSON 파싱 개선 권장 및 블로킹 동작 확인

현재 구현에서 두 가지 개선 사항을 고려할 수 있습니다:

  1. 문자열 기반 JSON 파싱: 149, 152번 라인에서 status.contains("\"state\":\"RUNNING\"")와 같은 문자열 검색을 사용하고 있습니다. 이미 Gson 인스턴스가 있으므로, 응답을 적절한 데이터 클래스로 역직렬화하여 타입 안전하게 처리하는 것이 더 견고합니다.

  2. Thread.sleep 사용: 159번 라인의 Thread.sleep()은 현재 스레드를 블로킹합니다. 애플리케이션 시작 시에만 호출된다면 문제없지만, 요청 처리 스레드에서 호출될 경우 성능 문제가 발생할 수 있습니다.

개선 예시 (JSON 파싱):

data class ConnectorStatus(
    val name: String,
    val connector: ConnectorState,
    val tasks: List<TaskState>
)

data class ConnectorState(val state: String)
data class TaskState(val state: String)

// 사용:
val statusObj = gson.fromJson(status, ConnectorStatus::class.java)
if (statusObj.connector.state == "RUNNING") {
    // ...
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 7749807 and e6fc8a6.

📒 Files selected for processing (8)
  • src/main/kotlin/dsm/pick2024/global/config/async/AsyncConfig.kt (1 hunks)
  • src/main/kotlin/dsm/pick2024/global/config/debezium/DebeziumClientProperties.kt (1 hunks)
  • src/main/kotlin/dsm/pick2024/global/config/debezium/DebeziumRestTemplateConfig.kt (1 hunks)
  • src/main/kotlin/dsm/pick2024/global/config/debezium/DebeziumRetryProperties.kt (1 hunks)
  • src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorInitializer.kt (1 hunks)
  • src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorService.kt (1 hunks)
  • src/main/kotlin/dsm/pick2024/infrastructure/debezium/exception/DebeziumConfigurationException.kt (1 hunks)
  • src/main/kotlin/dsm/pick2024/infrastructure/debezium/exception/DebeziumRetryableException.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorInitializer.kt
🔇 Additional comments (5)
src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorService.kt (3)

33-41: 재시도 설정 구성 확인

@Retryable 어노테이션이 올바르게 구성되어 있습니다. SpEL 표현식을 사용하여 DebeziumRetryProperties 빈의 속성을 참조하는 방식은 적절합니다.


68-80: 커넥터 존재 여부 확인 로직 양호

404 상태 코드를 통해 커넥터 존재 여부를 판단하는 로직이 올바르게 구현되어 있습니다. 다른 클라이언트 에러는 상위로 전파되어 적절히 처리됩니다.


167-176: Fix the exception throw syntax on line 174

DebeziumConnectorException is being thrown without instantiation. In Kotlin, exceptions must be instantiated with parentheses. Change throw DebeziumConnectorException to throw DebeziumConnectorException() (or pass a message if the constructor accepts one).

src/main/kotlin/dsm/pick2024/global/config/debezium/DebeziumRetryProperties.kt (1)

6-13: > Likely an incorrect or invalid review comment.

src/main/kotlin/dsm/pick2024/global/config/debezium/DebeziumRestTemplateConfig.kt (1)

21-24: No changes needed. The project uses Spring Boot 2.7.16 (Spring Framework 5.3.x), where setConnectTimeout and setReadTimeout are not deprecated. These methods only become deprecated in Spring Framework 6.0+. The current implementation is appropriate for the Spring version in use.

Likely an incorrect or invalid review comment.

Copy link
Member

@ByunDohwi ByunDohwi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ByunDohwi
Copy link
Member

이런; 코드래빗 코멘트 reslove 해주세요

…zation

  - Retry with exponential backoff (5 attempts, 2s-30s)
  - Async init with bounded ThreadPoolTaskExecutor (core=2, max=5)
  - Dedicated 60s timeout for connector operations
  - Exception handling: 4xx (no retry) vs 5xx (retry)
  - Status polling to wait for RUNNING state
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorService.kt (1)

131-139: 문자열 기반 JSON 상태 파싱은 취약합니다.

status.contains("\"state\":\"RUNNING\"")는 JSON 형식이 변경되거나 공백이 추가되면 실패할 수 있습니다. 이미 주입된 Gson을 사용하여 JSON을 파싱하는 것이 안전합니다.

+    private data class ConnectorStatus(
+        val connector: ConnectorState? = null
+    )
+    private data class ConnectorState(
+        val state: String? = null
+    )
+
     fun waitForConnectorReady(maxWaitMs: Long = 30000, pollIntervalMs: Long = 2000): Boolean {
         val startTime = System.currentTimeMillis()
         var attempt = 1

         while (System.currentTimeMillis() - startTime < maxWaitMs) {
             val status = getConnectorStatus()

             if (status != null) {
-                if (status.contains("\"state\":\"RUNNING\"")) {
+                val parsed = runCatching { gson.fromJson(status, ConnectorStatus::class.java) }.getOrNull()
+                val state = parsed?.connector?.state
+                if (state == "RUNNING") {
                     log.info("Debezium 커넥터가 RUNNING 상태로 전환됨 (시도 횟수: {})", attempt)
                     return true
-                } else if (status.contains("\"state\":\"FAILED\"")) {
+                } else if (state == "FAILED") {
                     log.error("Debezium 커넥터가 FAILED 상태로 전환됨: {}", status)
                     return false
                 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between e6fc8a6 and cffbc78.

📒 Files selected for processing (2)
  • src/main/kotlin/dsm/pick2024/global/config/async/AsyncConfig.kt (1 hunks)
  • src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorService.kt (1 hunks)
🔇 Additional comments (5)
src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorService.kt (3)

24-31: 클래스 구조가 적절합니다.

@Qualifier를 사용하여 전용 debeziumRestTemplate을 주입받고, 필요한 의존성들을 잘 구성했습니다. 이전 리뷰에서 지적된 타임아웃 설정도 적용된 것으로 확인됩니다.


69-111: 이전 리뷰 피드백이 잘 반영되었습니다.

createConnector()updateConnector() 메서드에서 도달 불가능한 else 블록이 제거되었고, 예외 처리는 상위 registerConnector() 메서드에서 일관되게 처리됩니다.


150-159: LGTM!

삭제 로직이 간결하고 예외 처리가 적절합니다.

src/main/kotlin/dsm/pick2024/global/config/async/AsyncConfig.kt (2)

26-46: 이전 리뷰 피드백이 잘 반영되었습니다.

AsyncConfigurer를 구현하고 ThreadPoolTaskExecutor를 사용하여 스레드 풀을 적절히 구성했습니다. 거부된 작업에 대한 핸들러와 graceful shutdown 설정도 포함되어 있습니다.


48-58: LGTM!

비동기 작업에서 발생하는 처리되지 않은 예외를 적절히 로깅하고 있습니다.

…zation

  - Retry with exponential backoff (5 attempts, 2s-30s)
  - Async init with bounded ThreadPoolTaskExecutor (core=2, max=5)
  - Dedicated 60s timeout for connector operations
  - Exception handling: 4xx (no retry) vs 5xx (retry)
  - Status polling to wait for RUNNING state
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorService.kt (3)

63-75: ResourceAccessException와 소켓 관련 예외 캐치가 중복될 가능성이 큽니다.

Spring RestTemplate 는 보통 ConnectException, SocketTimeoutException, UnknownHostException 같은 I/O 예외를 ResourceAccessException 으로 래핑해서 던지기 때문에, 현재 구조에서는 뒤쪽의 개별 catch (SocketTimeoutException / ConnectException / UnknownHostException) 블록이 실제로는 거의 실행되지 않을 수 있습니다.

ResourceAccessException 한 곳에서 e.cause 타입을 검사해 세분화해서 로그를 찍거나, 필요 없다면 개별 캐치 블록을 정리해 예외 처리를 단순화하는 쪽을 고려해 보셔도 좋겠습니다.


125-133: 상태 조회 실패 로그에 스택트레이스를 함께 남기는 것이 좋습니다.

현재는 e.message 만 로깅하고 있어서, 장애 시 원인 추적이 어려울 수 있습니다. 아래처럼 exception 객체도 같이 넘기는 편이 디버깅에 유리합니다.

-        } catch (e: Exception) {
-            log.error("커넥터 상태 조회 실패: {}", e.message)
-            null
-        }
+        } catch (e: Exception) {
+            log.error("커넥터 상태 조회 실패: {}", e.message, e)
+            null
+        }

136-160: waitForConnectorReady가 호출 스레드를 블로킹하며, 상태 파싱이 문자열 의존적입니다.

초기화 단계에서만 쓰인다면 괜찮을 수 있지만, 다른 곳에서 재사용될 여지가 있다면 Thread.sleep 기반 루프는 최대 30초 동안 스레드를 점유하므로 주의가 필요합니다. 호출 측에서 이 메서드가 블로킹이라는 점을 명확히 인지하고 사용하는지 한 번 더 점검해 보시는 것을 권장합니다.

또한 상태 응답을 status.contains("\"state\":\"RUNNING\"") 식으로 문자열 검색하는 대신, Gson 등으로 JSON 을 파싱해서 state 필드만 비교하면 향후 응답 포맷 변경에 덜 민감한 구현이 될 것입니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between cffbc78 and 534a123.

📒 Files selected for processing (1)
  • src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorService.kt (1 hunks)
🔇 Additional comments (1)
src/main/kotlin/dsm/pick2024/infrastructure/debezium/DebeziumConnectorService.kt (1)

37-79: 등록 플로우와 재시도 설정 구조는 전반적으로 잘 잡혀 있습니다.

예외 유형별로 재시도 가능/불가능을 명확히 나누고, @Retryable 로 재시도 정책을 외부 설정(DebeziumRetryProperties)에 위임한 부분은 운영/튜닝 관점에서 이해하기 좋은 구성입니다.

@kangeunchan
Copy link
Contributor Author

@ByunDohwi 수정 다했으니까 코드 한번보고 머지해주세요.

@ByunDohwi ByunDohwi merged commit 640e026 into develop Dec 9, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants