Core Config 모듈 분리 및 앱별 구성 전략
배경
- 현재 모든 API 서버는
@core/config를 글로벌 모듈로 불러오고 동일한 스키마/네임스페이스 세트를 강제합니다. - 기능(특히 국가별 KR/DE 전용 기능)이 증가하면서 CoreConfig 스키마가 무한정 커지고, 필요 없는 환경 변수까지 각 서비스에 요구되는 문제가 발생했습니다.
- 신규 기능을 추가할 때마다 공통 모듈을 수정해야 하므로 변경 범위가 넓고, 서비스 간 배포 독립성이 저하됩니다.
목표
- 앱별 최소 설정만 로드하여 환경 변수 의존성을 줄인다.
- 도메인/기능 팀이 자체 config 계약을 소유하도록 하여 책임을 분리한다.
- 타입 안전성, 테스트 용이성을 유지하면서도 설정 조합을 선언적으로 관리한다.
아키텍처 개요
┌──────────────────────┐
│ Apps (API / Web) │ ── manifest에서 필요한 feature config만 선택
└──────────┬───────────┘
│
┌──────────▼───────────┐
│ Core Config Layer │ ── 최소 공통 (app/cloud/db/log/auth)
├───────────────────────┤
│ Feature Config Layer │ ── KR/DE 등 도메인별 네임스페이스, 스키마, 로더
└───────────────────────┘
핵심 원칙
- Core 축소: 공통적으로 반드시 필요한 네임스페이스만 유지한다.
- Feature 모듈화: 각 기능/국가 라이브러리가 자신의 설정 스키마와 로더를 제공한다.
- Manifest 조합: 앱은 선언적인 manifest에서 필요한 feature 묶음만 선택한다.
- 검증 범위 축소: Joi 검증은 Core + manifest에 포함된 feature 스키마만 대상으로 한다.
계층별 역할
1. Core Layer (libs/core/config)
CoreConfigModule는 더 이상 모든 스키마를 알지 않고,ConfigFeatureDefinition목록만 받아 조합합니다.- 제공 기능:
- 공통 네임스페이스 로딩 (
app,cloud,database,logging,auth등) defineConfigNamespace,composeConfig헬퍼- Config 검증 파이프라인 + Environment initializer
- 공통 네임스페이스 로딩 (
export interface ConfigFeatureDefinition {
namespace: string;
load: ConfigFactory;
schema: Joi.SchemaMap;
providers?: Provider[]; // Nest 토큰/타입 주입용 (선택)
}
export interface ComposedConfigOptions {
serviceName: string;
features: ConfigFeatureDefinition[];
cache?: boolean;
}
export function composeConfig(options: ComposedConfigOptions): ConfigOptions {
return {
serviceName: options.serviceName,
cache: options.cache ?? true,
featureConfigs: options.features,
};
}
2. Feature Layer (libs/feature/<domain>/config)
- 각 기능 라이브러리는
defineConfigNamespace헬퍼를 사용해 자신의 네임스페이스를 선언합니다. - 예시:
libs/feature/kr-care/config/src/lib/krCareConfig.ts
export const krCareConfig = defineConfigNamespace({
namespace: 'krCare',
schema: {
KR_CARE_GATEWAY_URL: Joi.string().uri().required(),
KR_CARE_FEATURE_FLAG: Joi.boolean().default(false),
},
load: () => ({
krCare: {
gatewayUrl: process.env.KR_CARE_GATEWAY_URL,
enabled: process.env.KR_CARE_FEATURE_FLAG === 'true',
},
}),
providers: [
{
provide: KR_CARE_CONFIG,
useFactory: (cfg: ConfigService) => cfg.getNamespace<KrCareConfig>('krCare'),
inject: [ConfigService],
},
],
});
3. App Layer (apps/*/src/app/app-config.manifest.ts)
- 각 앱은 자신에게 필요한 feature config를 manifest로 선언하고
CoreConfigModule.forRoot에 전달합니다.
import { composeConfig } from '@core/config';
import { krCareConfig } from '@feature/kr-care-config';
import { baseWirConfig } from '@feature/wir-config';
export const dtaWirApiConfig = composeConfig({
serviceName: 'dta-wir-api',
features: [
krCareConfig,
baseWirConfig,
],
});
@Module({
imports: [CoreConfigModule.forRoot(dtaWirApiConfig)],
})
export class AppModule {}
세부 설계 요소
Config 검증
- Core는
getDefaultSchema()로 공통 스키마만 포함. featureConfigs배열을 순회하며schema를Joi.object(feature.schema)로 래핑 후 concat.allowUnknown: true,abortEarly: false설정을 유지해 편의 제공.
Provider 주입/타입 안전성
defineConfigNamespace가providers를 자동 구성하도록 하고, 필요 시configInjectionToken(namespace)헬퍼로 토큰을 생성합니다.- 서비스 코드 예시:
@Injectable()
export class KrBenefitService {
constructor(
@Inject(KR_CARE_CONFIG) private readonly krCare: KrCareConfig,
) {}
}
테스트 전략
- Feature config는 자체 spec에서 ENV 목을 주입하여 로더/스키마를 검증.
- App 단위 테스트에서는 manifest에
mockConfigNamespace를 추가하여 특정 네임스페이스만 오버라이드 가능. - CI에
yarn verify:config <service>스크립트를 추가하고, manifest 기반으로 필수 ENV 정의서를 생성하여 배포 파이프라인에서 검증한다.
환경 변수 소스 구조
- 각 애플리케이션은
cloudrun-deploy/{appName}/secret/{deployEnv}/{appName}.json파일에 실제 런타임에서 사용하는 모든 환경 변수를 선언한다. 예:cloudrun-deploy/dha-sleep-api/secret/dev/dha-sleep-api.json. - Manifest에 포함된 feature config는 해당 JSON 파일의 키 집합과 1:1 대응되어야 하며, manifest를 기준으로 누락된 키를 탐지하는
verify:config스크립트가 필요하다. - 국가별/배포환경별 차이는
deployEnv디렉터리 단계에서 분기하며, 동일 앱이라도 dev/stage/prod 파일을 각각 관리한다. - 신규 feature를 추가할 때는 (1) feature config 스키마 정의, (2) manifest에 feature 등록, (3)
cloudrun-deploy경로 내 각 환경 JSON에 필요한 키를 반영하는 순서를 따른다.
현행 네임스페이스 인벤토리
| Namespace | 주요 목적 | 스키마 파일 | 현재 의존 앱 | 제안 레이어 | 향후 소유 모듈/이관 메모 |
|---|---|---|---|---|---|
app | 서비스 메타 정보, 기본 포트/호스트, 토큰/보안 관련 공통 설정 | app.schema.ts | 모든 API/Web | Core 유지 | CoreConfig 유지. manifest 기본 포함. |
cloud | GCP 프로젝트/리전, PubSub, Firestore, Storage 등 클라우드 인프라 | cloud.schema.ts | 모든 API/Web | Core 유지 | 공통 인프라 의존이므로 Core에 잔류. |
database | Postgres/Redis 연결 정보, 스키마/풀 설정 | database.schema.ts | 모든 API/Web | Core 유지 | 커넥터들이 공통 사용. |
logging | 로그 레벨, BigQuery sink, 알림 설정 | logging.schema.ts | 대부분 서비스 | Core 유지 | 관통 concern. |
auth | OAuth(Kakao) 등 인증 클라이언트 설정 | auth.schema.ts | 인증 사용 서비스 | Core 유지 (당분간) | 추후 국가별 OAuth가 diverge하면 feature 모듈화 고려. |
externalApi | OpenAI/Gemini/Sendgrid/Atlassian/결제 등 외부 통합 | external-api.schema.ts | 선택적 | Feature 이관 | 세분화 필요: LLM/Communication/Payment 등 도메인별 config 모듈 작성. |
privacy | GDPR 보존 정책, 익명화 규칙 | 스키마 없음 (추가 필요) | 특정 데이터 처리 서비스 | Feature 이관 | libs/feature/privacy/config (또는 규제 도메인)으로 이동, 스키마 추가. |
vectorstore | Vertex AI 벡터스토어/임베딩 설정 | (스키마 미존재) | LLM 기반 기능 | Feature 이관 | libs/feature/ai/config 계열로 이동, 스키마 정의 후 manifest opt-in. |
notification | NHN 알림톡, 유저 참여 Webhook 등 채널 | (스키마 미존재) | 동일 채널 사용하는 앱 | Feature 이관 | libs/feature/notification/config 신설. |
solPrediction | SOL 예측/LLM 튜닝/Rate limit | (스키마 미존재) | SOL 관련 서비스 | Feature 이관 | libs/feature/sol-prediction/config로 이동, LLM config와 dependency 정리. |
스키마 파일이 없는 네임스페이스(
privacy,vectorstore,notification,solPrediction)는 Feature 레이어로 이동하면서defineConfigNamespace기반 스키마를 반드시 추가한다.
인벤토리에서 드러난 액션 아이템
- Feature 이관 대상 네임스페이스 4종 +
externalApi세분화를 우선 진행한다. - Core에 남을 5종(app/cloud/database/logging/auth)은
ConfigFeatureDefinition인터페이스 도입 후에도 기본으로 로드되도록getDefaultSchema()구성. - 스키마 미존재 항목은 이관을 위해 먼저 스키마를 작성하거나, 새 Feature 모듈에서 스키마와 로더를 동시에 재정의한다.
cloudrun-deploy/{app}/secret/{env}/{app}.json파일 중 불필요한 키는 manifest 업데이트와 함께 정리할 계획을 세운다.
Feature 모듈 이관 영향 분석
| 대상 Namespace | 주요 소비자 (Nx lib/app) | 제안 Feature Config 모듈 | Cloud Run Secret 영향 |
|---|---|---|---|
externalApi | libs/feature/auth(이메일 발송), libs/feature/agent-treatment-flow(Agent API), libs/feature/support, libs/feature/sol-prediction-wir(Gemini), apps/dta-wide-mcp, apps/dta-wide-agent-qa | libs/feature/integration-llm/config, libs/feature/integration-comm/config, libs/feature/integration-payment/config 등 기능별 분리. Manifest에서 필요한 조합 선택 | cloudrun-deploy/dta-wide-api/secret/*/*.json, cloudrun-deploy/dta-wide-mcp/secret/*/*.json, cloudrun-deploy/dta-wide-agent-qa/secret/*/*.json, cloudrun-deploy/dha-sleep-api/secret/*/*.json |
privacy | libs/feature/user-wir, libs/feature/data-export-wir | libs/feature/privacy-wir/config (또는 libs/feature/regulation/config) | cloudrun-deploy/dta-wir-api-universal/secret/*/*.json, cloudrun-deploy/dta-wir-agent-studio/secret/*/*.json |
vectorstore | libs/core/vectorstore, apps/dta-wide-agent-flow (document store 기능) | libs/feature/vectorstore/config (LLM/검색 기능용) | cloudrun-deploy/dta-wide-agent-flow/secret/*/*.json |
notification | libs/core/notification, apps/dta-wir-api-universal, libs/feature/agent-board, libs/feature/questionnaire(-wir), libs/feature/sleep(-wir) | libs/feature/notification/config (채널별 세분화 가능) | cloudrun-deploy/dta-wir-api-universal/secret/*/*.json, cloudrun-deploy/dta-wide-api/secret/*/*.json |
solPrediction | libs/feature/sol-prediction-wir, libs/feature/auth(PromptAuth guard) | libs/feature/sol-prediction/config | cloudrun-deploy/dta-wir-backtest-web/secret/*/*.json, cloudrun-deploy/dta-wide-api/secret/*/*.json |
- 표에 적힌 Cloud Run secret 경로는 실제 존재 여부와 무관하게 반드시
cloudrun-deploy/{app}/secret/{env}/{app}.json패턴을 따라가야 한다. 예:cloudrun-deploy/dha-sleep-api/secret/dev/dha-sleep-api.json. - 각 앱 manifest가 어떤 feature config를 선택했는지 기반으로
verify:config스크립트가 해당 JSON 파일 키를 검증하게 된다.
구현 현황
@feature/config라이브러리가 추가되어externalApi,notification,privacy,solPrediction,vectorstore네임스페이스가 Core에서 분리되었습니다.composeConfig/defineConfigNamespace헬퍼를 이용해 각 앱(dta-wide-api,dta-wir-api-universal,dta-wide-mcp,dta-wide-agent-qa,dha-sleep-api)이app-config.manifest.ts를 선언하도록 반영했습니다.- 앞으로 신규 기능에서 필요한 설정은
feature-config또는 각 도메인별 config 라이브러리에 정의하고, 해당 앱 manifest에 명시적으로 추가해야 합니다.
국가별/도메인별 Config 모듈화 가이드
- 도메인 단위 세분화: 하나의 도메인 안에서도 기능/국가별 요구가 다르면 네임스페이스를 더 잘게 나누십시오. 예를 들어
auth의 경우auth-base,auth-kakao,auth-google,auth-apple등으로 분리합니다. - 도메인 전용 config 라이브러리:
libs/feature/auth/config처럼 도메인별 config 라이브러리를 만들고, 내부에서 각 provider별defineConfigNamespace를 export 합니다. - Manifest 조합: 앱 manifest에서는 자신이 필요로 하는 조각만 골라 넣습니다. 한국 서비스는
[authBaseConfig, authKakaoConfig], 독일 서비스는[authBaseConfig, authAppleConfig, authGoogleConfig]처럼 구성하면 superset 문제가 사라집니다. - Feature Pack 패턴: 반복되는 조합은
authKoreaPack,authGermanyPack과 같이 배열 헬퍼를 만들어 제공할 수 있습니다. Pack은 결국ConfigFeatureDefinition[]이므로features: [...authKoreaPack]형태로 전달하면 됩니다. - 검증 및 문서화: 각 Pack과 단일 네임스페이스는 어떤 ENV 키를 요구하는지 README/문서에 기재하고,
verify:config스크립트가 manifest ↔ Cloud Run secret JSON을 검증하도록 합니다.
마이그레이션 플랜
- Core 슬림화: 기존 Core에 포함된 도메인 네임스페이스 중 공통이 아닌 항목(
solPrediction,notification,privacy등)을 해당 라이브러리로 이동. - 헬퍼 도입:
defineConfigNamespace,composeConfig,configInjectionToken유틸 구현. - 앱 Manifest 작성: 각 앱에
app-config.manifest.ts추가 후CoreConfigModule.forRoot(manifest)로 교체. - ENV/시크릿 재정비: manifest에 포함되지 않은 ENV 키를 서비스 시크릿에서 제거하고, 기능별 시크릿 파일을 정리.
- 검증 파이프라인:
verify:config스크립트 추가 및 CI 단계에 포함. - 가이드 업데이트: 신규 기능은 반드시 Feature config 모듈을 통해 ENV 요구사항을 정의하도록 개발 가이드 개정.
기대 효과 및 리스크
- 효과
- 서비스별 최소 ENV만 필요 → 배포 환경 설정 부담 감소.
- 도메인 팀이 독립적으로 설정 계약을 관리하므로 변경 영향 범위 축소.
- 테스트/로컬 개발에서 필요한 네임스페이스만 손쉽게 목 가능.
- 리스크 및 대응
- 초기 분리 작업 시 레거시 키 누락 가능 → manifest 기반 체크리스트로 검증.
- Feature config가 난립할 수 있음 →
libs/feature/<domain>/config네이밍 규칙과 코드 리뷰 체크리스트에 포함. - 공통 스키마 변경이 필요한 경우? Core layer에서만 처리하되, 문서화와 버전 태깅으로 호환성 관리.
다음 단계
- CoreConfig 모듈에 feature 정의 인터페이스와 compose 헬퍼 추가.
- KR/DE 주요 기능부터 파일을 분리해 시범 적용.
README/개발 가이드에 manifest 패턴과 ENV 검증 플로우를 추가.
이 문서는 환경 구성 책임을 기능 단위로 분리하여 모노레포의 복잡도를 완화하고, 국가별/서비스별 요구사항을 유연하게 수용하기 위한 기준으로 삼습니다.