docs(i18n): AWS 공급자 구현과 검증 결과를 기록한다
This commit is contained in:
@@ -2,8 +2,9 @@
|
||||
|
||||
## 현재 실행 구조
|
||||
|
||||
운영 구현체는 `PapagoLanguageDetectionProvider`와 `PapagoTranslationService`뿐이다.
|
||||
AWS 감지·번역 adapter, 해당 SDK 및 전용 인증 설정은 추가하지 않았다. 지금 `LanguageProviderId.AWS`를 순서에 넣으면 시작에 실패한다.
|
||||
감지 구현체는 `PapagoLanguageDetectionProvider`와 `AwsLanguageDetectionProvider`,
|
||||
번역 구현체는 `PapagoTranslationService`와 `AwsTranslationService`다.
|
||||
두 기능의 production 기본 순서는 모두 `PAPAGO → AWS`다. AWS는 기존 자격 증명과 리전 설정을 재사용한다.
|
||||
|
||||
감지와 번역은 별도 capability다. 감지만 또는 번역만 제공하는 공급자도 등록할 수 있다.
|
||||
두 capability에서 같은 `providerId`를 쓰는 것은 가능하지만, 한 capability 안의 ID 중복은 시작 시 거부한다.
|
||||
@@ -24,7 +25,7 @@ AWS 감지·번역 adapter, 해당 SDK 및 전용 인증 설정은 추가하지
|
||||
## 새 공급자 추가 순서
|
||||
|
||||
1. `LanguageProviderId`에 공급자 enum 항목과 재배포 후에도 안정적인 DB `code`를 추가한다.
|
||||
현재 항목은 `PAPAGO("papago")`, `AWS("aws")`다. AWS는 이미 ID가 있으므로 실제 adapter만 별도로 구현한다.
|
||||
현재 항목은 `PAPAGO("papago")`, `AWS("aws")`이며 두 공급자의 실제 adapter가 등록돼 있다.
|
||||
2. 필요한 capability 인터페이스를 구현하고 Spring `@Component`/`@Service` 또는 `@Bean`으로 등록한다.
|
||||
예를 들어 Papago adapter의 식별자는 `override val providerId = LanguageProviderId.PAPAGO`다.
|
||||
3. 공급자의 HTTP/SDK 및 인증은 adapter 안에 둔다. 외부 응답 형식과 빈 결과를 검증하고,
|
||||
@@ -34,19 +35,24 @@ AWS 감지·번역 adapter, 해당 SDK 및 전용 인증 설정은 추가하지
|
||||
|
||||
콘텐츠·댓글·커뮤니티 호출자, worker, materializer, scheduler에 공급자 분기를 추가하지 않는다.
|
||||
기존 두 coordinator는 주입된 구현체 목록과 설정만 사용한다. 공급자마다 다른 분할·요청 크기 제한은 adapter 책임이다.
|
||||
Papago의 5,000 UTF-16 단위 분할과 서로게이트 쌍 보존은 `PapagoTranslationService`에 남아 있다.
|
||||
`CodePointTextSplitter.kt`의 internal helper가 code point 경계의 분할·prefix와 UTF-8 길이 계산을 공유한다.
|
||||
Papago는 기존 5,000 UTF-16 코드 단위(`Character.charCount`)로, AWS Translate는 10,000 UTF-8 바이트로 분할한다.
|
||||
각 adapter는 조각을 순차 호출해 원문별로 구분자 없이 결합하며, 조각 하나라도 실패하면 전체 요청의 부분 결과를 버린다.
|
||||
AWS Comprehend는 앞 최대 100,000 UTF-8 바이트만 감지한다. prefix는 원문 전체 hash/cache key나 번역 원문을 바꾸지 않는다.
|
||||
혼합 언어 입력은 prefix와 전체 본문의 감지 결과가 다를 수 있다. blank 입력은 호출하지 않으며 짧은 비어 있지 않은 입력은 전달한다.
|
||||
응답은 첫 후보만 사용하고 첫 코드가 blank/누락이면 뒤 후보를 대신 선택하지 않는다. 별도 confidence threshold는 없다.
|
||||
ko/en/ja 서비스 정책은 `TranslationLanguages`에 있으며 공급자를 추가해도 자동 확장하지 않는다.
|
||||
|
||||
## 기능별 독립 코드 목록
|
||||
|
||||
production 순서의 유일한 편집 지점은
|
||||
`src/main/kotlin/kr/co/vividnext/sodalive/i18n/translation/LanguageProviderOrder.kt`다.
|
||||
목록의 왼쪽부터 사용하며 기본값은 두 기능 모두 `listOf(LanguageProviderId.PAPAGO)`다.
|
||||
목록의 왼쪽부터 사용하며 기본값은 두 기능 모두 `listOf(LanguageProviderId.PAPAGO, LanguageProviderId.AWS)`다.
|
||||
|
||||
```kotlin
|
||||
object LanguageProviderOrder {
|
||||
val detectionOrder = listOf(LanguageProviderId.PAPAGO)
|
||||
val translationOrder = listOf(LanguageProviderId.PAPAGO)
|
||||
val detectionOrder = listOf(LanguageProviderId.PAPAGO, LanguageProviderId.AWS)
|
||||
val translationOrder = listOf(LanguageProviderId.PAPAGO, LanguageProviderId.AWS)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -55,8 +61,7 @@ Spring은 `@Autowired` 기본 생성자로 위 목록을 읽는다. 문자열
|
||||
테스트에서는 전역 목록을 변경하지 않고
|
||||
`LanguageProviderProperties(List<LanguageProviderId>, List<LanguageProviderId>)`로 만든 테스트 bean을 주입한다.
|
||||
|
||||
다음은 **실제 AWS 구현체를 두 capability에 등록한 뒤에만** 적용할 수 있는 예시다.
|
||||
현재 AWS ID의 가짜 구현체는 테스트에만 있다.
|
||||
다음은 감지만 AWS 우선으로 바꾸는 예시다. 현재 기본 순서를 뜻하지 않는다.
|
||||
|
||||
```kotlin
|
||||
val detectionOrder = listOf(LanguageProviderId.AWS, LanguageProviderId.PAPAGO)
|
||||
@@ -65,17 +70,31 @@ val translationOrder = listOf(LanguageProviderId.PAPAGO, LanguageProviderId.AWS)
|
||||
|
||||
감지만 순서를 바꾸어도 번역 순서는 바뀌지 않는다. 반대로 번역만 바꿀 수도 있다.
|
||||
변경 후 빌드·배포해야 반영된다. 과거 우선순위 Spring property·환경변수·YAML 값이 남아 있어도 순서는 바뀌지 않는다.
|
||||
공급자 인증정보는 계속 환경변수로 주입하며 이 코드 목록에 넣지 않는다.
|
||||
공급자 인증정보는 기존 설정 경계로 주입하며 이 코드 목록에 넣지 않는다.
|
||||
빈 목록·중복 ID·해당 capability의 구현체 부재·capability 안의 중복 구현체 ID는 초기화에서 거부한다.
|
||||
빈 문자열 ID는 enum 타입으로 표현할 수 없다. 공급자가 번역에만 등록됐다면 그 ID를 감지 순서에 넣을 수 없다.
|
||||
실행 중 설정 변경은 지원하지 않는다. AWS 구현체 등록 전
|
||||
`listOf(LanguageProviderId.PAPAGO, LanguageProviderId.AWS)`는 사용할 수 없다.
|
||||
실행 중 설정 변경은 지원하지 않는다. 테스트의 Papago-only inventory는 두 순서를 명시적으로 `[PAPAGO]`로 제한한다.
|
||||
|
||||
## AWS SDK와 설정
|
||||
|
||||
Comprehend와 Translate만 AWS SDK for Java v2 BOM `2.57.11`을 사용하며 기존 SDK v1 의존성은 유지한다.
|
||||
동기 HTTP runtime은 SDK의 전이 의존성이다. Spring Boot 2.7의 하향 버전 관리를 피하도록
|
||||
`httpclient5.version=5.6.4`, `httpcore5.version=5.4.3`을 SDK POM 기준으로 고정한다.
|
||||
|
||||
`AwsLanguageProviderConfiguration`은 기존 `cloud.aws.credentials.access-key`, `cloud.aws.credentials.secret-key`를
|
||||
`StaticCredentialsProvider`에 연결하고 `cloud.aws.region.static`을 `Region.of`에 전달한다(현재 설정은 `ap-northeast-2`).
|
||||
기본 credentials chain이나 AWS 전용 새 인증 설정을 추가하지 않는다. 두 동기 client는 Spring Bean이며 종료 시 `close`한다.
|
||||
AWS 번역은 source/target을 명시하며 `providerVersion`은 안정 값 `translate-2017-07-01`이다.
|
||||
Papago의 `providerVersion`은 기존 `nmt-v1`이다. 두 값은 SDK 라이브러리 버전과 별개다.
|
||||
|
||||
실 AWS 호출·IAM·품질·과금은 자동 테스트에서 확인하지 않는다. 필요한 IAM 작업은
|
||||
`comprehend:DetectDominantLanguage`, `translate:TranslateText`이며 이 가이드는 권한 변경을 승인하지 않는다.
|
||||
|
||||
## DB code 호환성
|
||||
|
||||
내부 순서·구현체 매칭·성공 결과는 `LanguageProviderId`를 사용한다. entity/repository의 `provider`는 String 그대로다.
|
||||
coordinator/cache/worker의 DB 조회·저장 접점에서만 `providerId.code`로 변환하므로 기존 `"papago"` 캐시를 재사용한다.
|
||||
가짜 AWS의 저장값은 `"aws"`다. enum `.name`/`.ordinal`, `@Enumerated`/`@Convert`, 역변환 helper는 사용하지 않는다.
|
||||
실제 AWS 성공 결과의 저장값은 `"aws"`다. enum `.name`/`.ordinal`, `@Enumerated`/`@Convert`, 역변환 helper는 사용하지 않는다.
|
||||
새 enum 항목을 추가할 때도 기존 code와 DB 스키마·캐시 키는 변경하지 않는다. `providerVersion`은 별도의 String 기록 필드다.
|
||||
|
||||
## 캐시와 오류 계약
|
||||
@@ -104,19 +123,26 @@ adapter 예외·null 감지·빈 감지·번역 결과 개수 불일치·blank
|
||||
|
||||
| 테스트 | 검증 내용 |
|
||||
|---|---|
|
||||
| `LanguageProviderSpringConfigurationTest` | 실제 production 기본 생성자, 과거 property/env 무시, typed-list 테스트 bean의 독립 순서와 잘못된 목록의 시작 실패 |
|
||||
| `LanguageProviderSpringConfigurationTest` | production 기본 생성자와 PAPAGO 우선 성공, 과거 property/env 무시, 독립 순서와 잘못된 목록의 시작 실패; fake AWS context와 실제 adapter + SDK mock context 분리 |
|
||||
| `LanguageProviderIdTest` | 런타임 enum getter 계약, 안정적인 papago/aws code, production 기본값과 실제 adapter ID |
|
||||
| `LanguageProviderPropertiesTest` | 순서 검증 및 선택되지 않은 bean을 포함한 capability 내 중복 ID 거부 |
|
||||
| `LanguageDetectionProviderTest` / `TranslationCoordinatorTest` | 전체 요청 폴백, 첫 성공 중단, cache-first, 안전한 전체 실패, DB 오류 전파 |
|
||||
| `PapagoLanguageDetectionProviderTest` / `PapagoTranslationServiceTest` | 제어된 HTTP form/header/response와 기존 17개 chunk 회귀 |
|
||||
| `LanguageProviderCacheEndToEndTest` | 실제 H2의 AWS 감지/번역 귀속과 문자열 aws, providerVersion, 두 번째 요청 무호출, worker와 materializer의 실제 표시 |
|
||||
| `PapagoLanguageDetectionProviderTest` / `PapagoTranslationServiceTest` | 제어된 HTTP form/header/response와 빈 raw 입력·공백을 포함한 19개 번역 회귀 |
|
||||
| `CodePointTextSplitterTest` | UTF-16/UTF-8 한도, 한글·보조 평면 문자·공백 보존, prefix와 전체 원문의 분리 |
|
||||
| `AwsLanguageDetectionProviderTest` / `AwsTranslationServiceTest` | 실제 adapter + SDK mock의 요청·응답, prefix/분할·결합, 첫 후보, 중간 실패 폐기와 호출 중단, 민감 오류 비노출 |
|
||||
| `AwsLanguageProviderConfigurationTest` | 합성 credentials/region으로 실제 client 생성과 종료, 네트워크 무호출 |
|
||||
| `LanguageProviderCacheEndToEndTest` | 실제 AWS adapters + SDK mock + H2의 5개 시나리오: 동일 prefix·서로 다른 전체 hash, 첫 blank 후보 미저장, aws/version/결합 결과 저장과 무호출 재사용·표시, 부분 실패 미저장과 안전한 worker 오류, 테스트 전용 AWS → PAPAGO의 전체 요청 fallback |
|
||||
|
||||
H2 버전은 MySQL `FOR UPDATE SKIP LOCKED`를 지원하지 않으므로 새 H2 테스트는 claim 조회 한 곳만
|
||||
test spy로 대체한다. 나머지 repository 저장·별도 트랜잭션 재조회·materialization은 실제 H2다.
|
||||
이 테스트가 운영 MySQL claim 동시성을 검증한다는 뜻은 아니다. 기존 감지 cache 경합·revision 테스트는 별도로 유지한다.
|
||||
이 테스트가 운영 MySQL claim 동시성이나 SDK serialization/signing·실 AWS 권한/품질을 검증한다는 뜻은 아니다.
|
||||
SDK mocks는 테스트마다 reset하고 Papago 기록 목록과 cache/job/read model을 비워 context 재사용을 격리한다.
|
||||
기존 감지 cache 경합·revision 테스트는 별도로 유지한다.
|
||||
|
||||
```bash
|
||||
./gradlew --no-daemon --offline test --tests 'kr.co.vividnext.sodalive.i18n.translation.*' --tests 'kr.co.vividnext.sodalive.content.LanguageDetectionCacheServiceTest'
|
||||
./gradlew --no-daemon test --tests 'kr.co.vividnext.sodalive.i18n.translation.LanguageProviderCacheEndToEndTest'
|
||||
./gradlew --no-daemon test --tests 'kr.co.vividnext.sodalive.i18n.translation.*' --tests 'kr.co.vividnext.sodalive.content.LanguageDetectionCacheServiceTest'
|
||||
```
|
||||
|
||||
전체 실행 명령, 결과와 미검증 범위는 [plan-task.md](plan-task.md)의 실행 증거를 따른다.
|
||||
AWS 구현의 실행 명령, 결과와 미검증 범위는 [AWS 구현 계획](../20261109_AWS언어감지번역/plan-task.md)의 실행 증거를 따른다.
|
||||
이전 공통화 기록은 [공통화 계획](plan-task.md)에 보존한다. 전체 테스트는 사용자가 `./gradlew test`로 실행한다.
|
||||
|
||||
Reference in New Issue
Block a user