본문으로 건너뛰기

Core Config 모듈 분리 및 앱별 구성 전략

배경​

  • 현재 모든 API 서버는 @core/config를 글로벌 모듈로 불러오고 동일한 스키마/네임스페이스 세트를 강제합니다.
  • 기능(특히 국가별 KR/DE 전용 기능)이 증가하면서 CoreConfig 스키마가 무한정 커지고, 필요 없는 환경 변수까지 각 서비스에 요구되는 문제가 발생했습니다.
  • 신규 기능을 추가할 때마다 공통 모듈을 수정해야 하므로 변경 범위가 넓고, 서비스 간 배포 독립성이 저하됩니다.

목표​

  1. 앱별 최소 설정만 로드하여 환경 변수 의존성을 줄인다.
  2. 도메인/기능 팀이 자체 config 계약을 소유하도록 하여 책임을 분리한다.
  3. 타입 안전성, 테스트 용이성을 유지하면서도 설정 조합을 선언적으로 관리한다.

아키텍처 개요​

┌──────────────────────┐
│ Apps (API / Web) │ ── manifest에서 필요한 feature config만 선택
└──────────┬───────────┘
│
┌──────────▼───────────┐
│ Core Config Layer │ ── 최소 공통 (app/cloud/db/log/auth)
├───────────────────────┤
│ Feature Config Layer │ ── KR/DE 등 도메인별 네임스페이스, 스키마, 로더
└───────────────────────┘

핵심 원칙​

  1. Core 축소: 공통적으로 반드시 필요한 네임스페이스만 유지한다.
  2. Feature 모듈화: 각 기능/국가 라이브러리가 자신의 설정 스키마와 로더를 제공한다.
  3. Manifest 조합: 앱은 선언적인 manifest에서 필요한 feature 묶음만 선택한다.
  4. 검증 범위 축소: 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/WebCore 유지CoreConfig 유지. manifest 기본 포함.
cloudGCP 프로젝트/리전, PubSub, Firestore, Storage 등 클라우드 인프라cloud.schema.ts모든 API/WebCore 유지공통 인프라 의존이므로 Core에 잔류.
databasePostgres/Redis 연결 정보, 스키마/풀 설정database.schema.ts모든 API/WebCore 유지커넥터들이 공통 사용.
logging로그 레벨, BigQuery sink, 알림 설정logging.schema.ts대부분 서비스Core 유지관통 concern.
authOAuth(Kakao) 등 인증 클라이언트 설정auth.schema.ts인증 사용 서비스Core 유지 (당분간)추후 국가별 OAuth가 diverge하면 feature 모듈화 고려.
externalApiOpenAI/Gemini/Sendgrid/Atlassian/결제 등 외부 통합external-api.schema.ts선택적Feature 이관세분화 필요: LLM/Communication/Payment 등 도메인별 config 모듈 작성.
privacyGDPR 보존 정책, 익명화 규칙스키마 없음 (추가 필요)특정 데이터 처리 서비스Feature 이관libs/feature/privacy/config (또는 규제 도메인)으로 이동, 스키마 추가.
vectorstoreVertex AI 벡터스토어/임베딩 설정(스키마 미존재)LLM 기반 기능Feature 이관libs/feature/ai/config 계열로 이동, 스키마 정의 후 manifest opt-in.
notificationNHN 알림톡, 유저 참여 Webhook 등 채널(스키마 미존재)동일 채널 사용하는 앱Feature 이관libs/feature/notification/config 신설.
solPredictionSOL 예측/LLM 튜닝/Rate limit(스키마 미존재)SOL 관련 서비스Feature 이관libs/feature/sol-prediction/config로 이동, LLM config와 dependency 정리.

스키마 파일이 없는 네임스페이스(privacy, vectorstore, notification, solPrediction)는 Feature 레이어로 이동하면서 defineConfigNamespace 기반 스키마를 반드시 추가한다.

인벤토리에서 드러난 액션 아이템​

  1. Feature 이관 대상 네임스페이스 4종 + externalApi 세분화를 우선 진행한다.
  2. Core에 남을 5종(app/cloud/database/logging/auth)은 ConfigFeatureDefinition 인터페이스 도입 후에도 기본으로 로드되도록 getDefaultSchema() 구성.
  3. 스키마 미존재 항목은 이관을 위해 먼저 스키마를 작성하거나, 새 Feature 모듈에서 스키마와 로더를 동시에 재정의한다.
  4. cloudrun-deploy/{app}/secret/{env}/{app}.json 파일 중 불필요한 키는 manifest 업데이트와 함께 정리할 계획을 세운다.

Feature 모듈 이관 영향 분석​

대상 Namespace주요 소비자 (Nx lib/app)제안 Feature Config 모듈Cloud Run Secret 영향
externalApilibs/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-qalibs/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
privacylibs/feature/user-wir, libs/feature/data-export-wirlibs/feature/privacy-wir/config (또는 libs/feature/regulation/config)cloudrun-deploy/dta-wir-api-universal/secret/*/*.json, cloudrun-deploy/dta-wir-agent-studio/secret/*/*.json
vectorstorelibs/core/vectorstore, apps/dta-wide-agent-flow (document store 기능)libs/feature/vectorstore/config (LLM/검색 기능용)cloudrun-deploy/dta-wide-agent-flow/secret/*/*.json
notificationlibs/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
solPredictionlibs/feature/sol-prediction-wir, libs/feature/auth(PromptAuth guard)libs/feature/sol-prediction/configcloudrun-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을 검증하도록 합니다.

마이그레이션 플랜​

  1. Core 슬림화: 기존 Core에 포함된 도메인 네임스페이스 중 공통이 아닌 항목(solPrediction, notification, privacy 등)을 해당 라이브러리로 이동.
  2. 헬퍼 도입: defineConfigNamespace, composeConfig, configInjectionToken 유틸 구현.
  3. 앱 Manifest 작성: 각 앱에 app-config.manifest.ts 추가 후 CoreConfigModule.forRoot(manifest)로 교체.
  4. ENV/시크릿 재정비: manifest에 포함되지 않은 ENV 키를 서비스 시크릿에서 제거하고, 기능별 시크릿 파일을 정리.
  5. 검증 파이프라인: verify:config 스크립트 추가 및 CI 단계에 포함.
  6. 가이드 업데이트: 신규 기능은 반드시 Feature config 모듈을 통해 ENV 요구사항을 정의하도록 개발 가이드 개정.

기대 효과 및 리스크​

  • 효과
    • 서비스별 최소 ENV만 필요 → 배포 환경 설정 부담 감소.
    • 도메인 팀이 독립적으로 설정 계약을 관리하므로 변경 영향 범위 축소.
    • 테스트/로컬 개발에서 필요한 네임스페이스만 손쉽게 목 가능.
  • 리스크 및 대응
    • 초기 분리 작업 시 레거시 키 누락 가능 → manifest 기반 체크리스트로 검증.
    • Feature config가 난립할 수 있음 → libs/feature/<domain>/config 네이밍 규칙과 코드 리뷰 체크리스트에 포함.
    • 공통 스키마 변경이 필요한 경우? Core layer에서만 처리하되, 문서화와 버전 태깅으로 호환성 관리.

다음 단계​

  1. CoreConfig 모듈에 feature 정의 인터페이스와 compose 헬퍼 추가.
  2. KR/DE 주요 기능부터 파일을 분리해 시범 적용.
  3. README/개발 가이드에 manifest 패턴과 ENV 검증 플로우를 추가.

이 문서는 환경 구성 책임을 기능 단위로 분리하여 모노레포의 복잡도를 완화하고, 국가별/서비스별 요구사항을 유연하게 수용하기 위한 기준으로 삼습니다.