OpenAI → Gemini 전환이 코드 한 줄 안 건드리고 설정값만 바꿐서 끝난 이유를 이해하려면 두 가지 개념이 필요하다.
application.yaml에 값을 적어도, k3s에 배포할 때는 코드/yaml 수정 없이 그대로 덮어쓸 수 있다:
env:
- name: SPRING_DATASOURCE_URL
valueFrom:
secretKeyRef: { name: target-tracking-secrets, key: db-url }
Spring Boot는 여러 곳에서 설정값을 읽고, 그 중 우선순위가 높은 쪽이 이긴다. 높은 순서: (1) 커맨드라인 인자 (2) 환경변수 (3) application.yaml (4) @Value 기본값.
환경변수 이름 SPRING_DATASOURCE_URL이 프로퍼티 키 spring.datasource.url과 다르게 생긴 건 "relaxed binding" 규칙 때문이다 — Spring이 .을 _로, camelCase를 _로 자동 변환해서 매칭해준다.
반면 ${GEMINI_API_KEY:PLACEHOLDER}처럼 명시적으로 ${...} 플레이스홀더를 박아둔 값은 정확히 그 이름의 환경변수로만 오버라이드된다 — 이건 relaxed binding이 아니라 단순 문자열 치환이라, k8s manifest의 env 이름도 application.yaml에 적힌 플레이스홀더 이름과 정확히 같아야 한다.
private final VectorStore vectorStore;
private final ChatModel chatModel;
...
String sitrep = chatModel.call(prompt);
List<Document> similar = vectorStore.similaritySearch(...);
ChatModel, VectorStore는 Spring AI가 제공하는 인터페이스다. 실제 구현체(OpenAiChatModel, GoogleGenAiChatModel 등)는 Spring Boot의 자동설정이 클래스패스에 어떤 스타터가 있는지 보고 알아서 빈으로 등록해준다.
implementation 'org.springframework.ai:spring-ai-starter-model-google-genai'
이 한 줄이 클래스패스에 있으면 Spring Boot가 GoogleGenAiChatModel을 ChatModel 빈으로 등록하고, 서비스 코드는 그게 뭐지도 모른 채 chatModel.call(prompt)만 호출한다. **의존성 역전(Dependency Inversion)**의 실전 사례 — 구체 구현이 아니라 추상화(인터페이스)에 의존하게 짜여있어, 구현체를 통째로 갈아끼워도(spring-ai-starter-model-openai → spring-ai-starter-model-google-genai) 그 인터페이스를 쓰는 코드는 무사하다.
이번에 실제로 바꿔 건 때 곳: (1) build.gradle 의존성 아티팩트 (2) application.yaml의 spring.ai.* 설정 키 (3) @Value("${spring.ai.openai.api-key:...}") → @Value("${spring.ai.google.genai.api-key:...}") (플레이스홀더 문자열 자체라 이건 어질 수 없이 코드 수정).
<table header-row="true"><tr><td></td><td>Gemini Developer API(AI Studio)</td><td>Vertex AI</td></tr><tr><td>인증</td><td>API 키 하나</td><td>GCP 서비스 계정/ADC</td></tr><tr><td>요금</td><td>무료 티어 있음</td><td>GCP 결제 필요</td></tr><tr><td>Spring AI 설정</td><td>api-key만</td><td>project-id + location 필요</td></tr></table>
Spring AI의 google-genai 스타터는 둘 다 지원하는데, 설정에 project-id나 location이 하나라도 있으면 자동으로 Vertex AI 모드로 전환되고, 그 순간 API 키 인증은 무시되고 400 에러가 난다. 그래서 application.yaml에는 이 두 키를 절대 넣지 않는 게 무료 티어를 유지하는 핵심 조건이다.
이런 함정은 직접 확인하는 습관이 유용하다:
unzip -p spring-ai-autoconfigure-model-google-genai-1.1.8.jar \
META-INF/spring-configuration-metadata.json | python3 -m json.tool