Files

11 KiB

언어 감지·번역 공급자 공통화 PRD

항목 내용
작성일 2026-11-09
상태 Phase 1~3 구현·기능 범위 검증·독립 리뷰 완료
승인 범위 감지·번역 별도 인터페이스, 공통 공급자 선택·실패 전환, 기능별 순서 설정
계획 plan-task.md

목적

파파고에 결합된 언어 감지·번역 호출을 분리한다. 이후 공급자는 해당 인터페이스 구현체와 빈·인증·순서 설정을 추가하면 사용할 수 있어야 하며, 콘텐츠·댓글·커뮤니티 등 도메인 코드에 공급자 분기를 추가하지 않는다.

범위와 제외

  • 현재 실행되는 실제 구현체는 파파고뿐이다. AWS 실제 API·SDK 구현은 다음 작업이며 이번에는 추가하지 않는다.
  • AWS 구현체가 등록된 뒤 기본 운영 의도는 파파고 → AWS이다. 감지와 번역 순서는 각각 독립 설정으로 바꾼다.
  • 기본 실행 설정은 존재하는 파파고만 사용한다. 미구현 AWS를 등록한 것처럼 처리하거나 네트워크 호출하지 않는다.
  • 기존 파파고 5,000 UTF-16 단위 분할, 지원 번역 언어 ko/en/ja, 공개 API, DB 스키마, job 재시도 정책은 유지한다.
  • 감지 결과 저장 대상별 차이, 커뮤니티 원문 revision 검증, 이벤트 after-commit 처리 및 DB 경합 규칙을 유지한다.
  • 실행 중 설정 변경, 관리 화면, 임의 플러그인 로딩, 새 번역 품질 정책, 기존 FAILED 작업 재처리는 제외한다.

요구사항

ID 수용 기준
LP-001 감지와 번역은 독립된 공급자 인터페이스이며, 둘 중 하나만 구현한 공급자도 해당 기능에 등록 가능하다.
LP-002 감지·번역의 공급자 순서를 서로 다른 설정으로 지정한다. 테스트 공급자 2개로 한쪽만 순서를 바꿔도 다른 쪽에 영향이 없음을 증명한다.
LP-003 공급자별 예외 또는 유효 결과 없음이면 다음 공급자를 순서대로 한 번 시도한다. 첫 성공 뒤 후속 공급자를 호출하지 않는다.
LP-004 모든 공급자 실패 시 감지는 기존처럼 결과 없음으로 처리하고 번역은 기존 워커 실패·재시도 경로를 따른다.
LP-005 캐시는 실제 성공 공급자 이름으로 저장한다. AWS 역할의 가짜 공급자 성공을 papago 이름으로 저장하지 않는다.
LP-006 설정된 공급자들의 유효 캐시를 우선 재사용한다. 새 외부 호출의 순서는 별도 설정 순서를 따른다. 워커와 결과 조립의 캐시 선택 정책이 같다.
LP-007 빈 원문·동일 언어·서비스 미지원 번역 언어는 불필요한 공급자 전환을 일으키지 않는다. 유효한 미지원 언어 감지 결과는 감지 장애와 구분한다.
LP-008 빈/중복/미등록 구현체의 공급자 순서는 초기화 단계에서 거부한다. 선택 코드에 공급자 분기를 추가하지 않는다. Phase 3부터 새 공급자는 enum 항목을 추가한다.
LP-009 기존 파파고 캐시·번역 데이터 및 분할 번역 동작과 감지 캐시 동시성 승자 반환을 보존한다.
LP-010 추가 방법과 기능별 순서 설정 예시, 실제 AWS 미구현 상태를 문서화한다.
LP-011 공급자 순서는 LanguageProviderOrder의 독립 val List<String> 두 개로 지정한다. 기본은 각각 listOf("papago")이다.
LP-012 우선순위 환경변수·YAML·문자열 파싱을 제거한다. 기존 우선순위 property/env 값이 있어도 production 순서를 바꾸지 않는다. 인증정보 환경변수는 유지한다.
LP-013 순서 목록·감지/번역 인터페이스·구현체·성공 결과·매칭의 식별자는 LanguageProviderId enum으로 통일한다.
LP-014 DB entity/repository의 provider는 String으로 유지하며 .code로만 변환한다. 기존 "papago" 데이터·캐시 키·중복 방지 계약을 유지한다.
LP-015 enum은 PAPAGO("papago"), AWS("aws")이며 AWS 실제 구현은 추가하지 않는다. 기본 순서는 둘 다 PAPAGO 하나이고 미구현 AWS 선택은 거부한다.

보안·오류 처리

외부 HTTP 응답과 실행 설정이 신뢰 경계다. 부분 번역·빈 응답은 정상 결과로 저장하지 않는다. 원문, 인증정보, 공급자 응답 본문이 신규 로그나 워커 오류 문자열에 노출되지 않도록 한다. 공급자 식별자·순서 외에 임의 URL을 사용자 요청으로 받지 않는다. 유료 API 테스트는 하지 않는다.

검증 시나리오

  1. 파파고 단독 기본 설정에서 기존 감지·번역·캐시·분할·커뮤니티 동작이 유지된다.
  2. 두 가짜 공급자로 감지/번역 순서 독립 변경, 첫 성공 중단, 실패 전환·전체 실패를 확인한다.
  3. 가짜 보조 공급자의 감지/번역 결과가 실제 provider로 DB에 저장되고 다음 요청과 materializer가 재사용한다.
  4. 잘못된 설정의 초기화 실패, 원문 정책 가드, 캐시 동시성·revision 회귀를 검증한다.

검증 범위

변경 전 관련 focused 테스트 60건과 추가 end-to-end 4건 통과를 확인했다. 구현 후 새 계약 테스트를 포함한 85건 및 부모 런타임·HTTP 검증을 통과했다. 상세 증거는 계획 문서에 기록한다. 전체 테스트는 저장소 정책에 따라 사용자 실행(./gradlew test)이며, 운영 DDL·실제 AWS/Papago 계정 호출은 제외한다. LSP 도구가 제공되지 않으면 Kotlin 컴파일과 ktlint로 보완하고 그 한계를 기록한다.

확정 구현 계약

아래는 Phase 1 구현 계약이며, 순서 설정 방식은 하단의 Phase 2 정정 계약이 우선한다. 기존 실행 기록은 보존한다.

  • 기존 TranslationProvider와 raw TranslateResult는 유지한다.
  • LanguageDetectionProvider: providerName: String, detect(query: String): String?.
  • DetectedLanguage: detectedLanguage: String, providerName: String.
  • ProviderTranslationResult: result: TranslateResult, providerName: String, providerVersion: String.
  • 기존 LanguageDetectionCacheService.detectWithCache(query: String): DetectedLanguage?가 감지 선택·캐시·전환을 담당한다. 기존 callback overload는 남기지 않고 모든 테스트 fixture도 새 계약에 맞춘다.
  • TranslationCoordinator.findCached(sourceHash, sourceLanguage, targetLanguage): TranslationMemory?를 worker와 materializer가 함께 사용한다. 캐시 선택만 통일하며 DB 저장 트랜잭션은 기존 worker에 남긴다.
  • TranslationCoordinator.translate(request: TranslateRequest): ProviderTranslationResult가 실제 성공 공급자를 반환한다. 결과 개수 불일치·blank 결과·공급자 예외는 다음 공급자로 전환한다. 부분 결과는 섞지 않고 전체 요청을 넘긴다.
  • 새 번역 coordinator의 빈 입력/blank 텍스트/동일 언어/미지원 언어 요청은 고정 메시지의 IllegalArgumentException으로 외부 호출 전에 거부한다. 기존 Papago raw adapter 가드는 그대로 유지한다.
  • 전체 번역 공급자 실패는 원문·원시 예외를 포함하지 않는 IllegalStateException("all translation providers failed")이다. 감지 전체 실패는 null이다. catch는 adapter 호출에만 적용하고 DB 실패를 공급자 실패로 처리하지 않는다.
  • sodalive.language-providers.detection-order와 translation-order는 각각 기본 papago이다. 순서는 재시작/재배포 시 반영한다. 미등록 공급자는 설정 오류로 거부하며 AWS 구현 전에는 aws를 활성화하지 않는다.
  • 감지 캐시의 정규화/hash 규칙과 insertIfAbsent 다음 findDetectedLanguageForUpdate 승자 반환을 유지한다. adapter에는 기존처럼 원래 query를 전달한다. 유효한 ko/en/ja 밖 감지 결과는 공급자 실패로 재해석하지 않는다.
  • 공통 TranslationLanguages로 서비스 언어 정책만 옮긴다. Papago 분할은 Papago adapter에 유지한다.
  • 현재 entity에는 공급자별 cache key가 있으므로 DDL 변경 없이 실제 공급자를 기록한다. 캐시 두 개가 있으면 설정상 앞선 결과를 선택하며 특정 job의 공급자를 영구 고정하는 새 모델은 만들지 않는다.

Phase 2 결정 정정 — 2026-11-09

사용자는 YAML·환경변수 대신 코드의 상수 목록으로 감지·번역 순서를 지정하도록 승인했다.

  • 유일한 production 순서 편집 지점은 i18n/translation/LanguageProviderOrder.kt의 detectionOrder = listOf("papago"), translationOrder = listOf("papago")이다. Kotlin 목록은 const val이 아닌 val이다.
  • 목록의 왼쪽부터 사용한다. 감지·번역은 각각 수정할 수 있으며 변경 후 빌드·배포한다.
  • 기존 LanguageProviderProperties는 typed-list 생성자와 명시적 Spring 기본 생성자로 코드 목록을 읽는다. 선택 코드·호출자의 이름 변경을 확산시키지 않는다. 문자열 생성자·@Value·split/trim은 제거한다.
  • 테스트는 전역 목록을 바꾸지 않고 테스트 전용 bean에 typed list를 주입하여 독립 순서와 invalid order를 검증한다.
  • 과거 우선순위 environment/property override는 무시한다. 다른 환경변수나 Spring 설정 체계는 변경하지 않는다.
  • 실패 전환·공급자 검증·캐시·DB·인증·AWS 미구현 정책은 Phase 1을 그대로 유지한다.

Phase 3 결정 정정 — 2026-11-09

사용자는 공급자 enum 도입과 식별자 용도의 papago 리터럴 변경을 승인했다. 타입 관련 항목은 이 계약이 앞선 Phase보다 우선한다.

  • LanguageProviderId(val code: String)에 PAPAGO("papago"), AWS("aws")를 정의한다.
  • LanguageProviderOrder, LanguageProviderProperties의 두 순서는 List<LanguageProviderId>이다. 기본 목록은 각각 listOf(LanguageProviderId.PAPAGO)이며 코드에서만 순서를 변경한다.
  • 두 공급자 인터페이스, 두 실제 Papago adapter, DetectedLanguage, ProviderTranslationResult는 providerName: String 대신 providerId: LanguageProviderId를 사용한다. 버전은 기존 String을 유지한다.
  • 선택/검증/coordinator는 enum으로 매칭한다. DB 조회/저장 접점에서만 .code로 변환한다. entity와 repository, DDL은 변경하지 않는다. .name, .ordinal, valueOf 저장/변환과 불필요한 fromCode는 추가하지 않는다.
  • API URL·인증 설정 키·원문/번역 fixture·DB 문자열 기대값·과거 환경변수 무시 테스트는 enum 전환 대상이 아니다.
  • 기존 DB 값 호환 테스트는 기대값을 enum에서 계산하지 않고 독립 문자열 "papago"로 검증한다. 가짜 보조 공급자는 AWS enum과 저장값 "aws"를 사용하며 test-only enum 항목을 추가하지 않는다.
  • 빈 문자열 ID는 타입으로 표현할 수 없어진다. 빈 목록·중복 순서·중복 구현체·해당 capability 구현체 부재 검증은 유지한다.
  • 새로운 공급자 추가 시 enum 항목·구현체·코드 순서 목록을 추가한다. 도메인 호출자와 선택 알고리즘은 변경하지 않는다.