본문으로 건너뛰기

외부 API 설정 (ExternalApiConfig)

개요

ExternalApiConfig는 애플리케이션에서 사용하는 외부 API 서비스의 연결 설정을 관리합니다. OpenAI, Google API, SendGrid, ConnectDTX 등 외부 서비스 연동에 필요한 API 키, 엔드포인트, 타임아웃 등의 설정을 정의합니다.

구성 요소

OpenAI 설정

설정 키환경 변수타입필수 여부기본값설명
openai.apiKeyOPENAI_API_KEYstring필수*-OpenAI API 키
openai.modelOPENAI_MODELstring선택'gpt-4'사용할 OpenAI 모델
openai.timeoutOPENAI_TIMEOUTnumber선택30000API 요청 타임아웃(ms)
openai.maxTokensOPENAI_MAX_TOKENSnumber선택2048최대 토큰 수

* OpenAI 기능을 사용하는 경우 필수

Google API 설정

설정 키환경 변수타입필수 여부기본값설명
google.apiKeyGOOGLE_API_KEYstring필수*-Google API 키
google.searchEngineIdGOOGLE_SEARCH_ENGINE_IDstring필수*-Google Custom Search Engine ID

* Google 검색 기능을 사용하는 경우 필수

SendGrid 설정

설정 키환경 변수타입필수 여부기본값설명
sendgrid.apiKeySENDGRID_API_KEYstring필수*-SendGrid API 키
sendgrid.fromEmailSENDGRID_FROM_EMAILstring필수*-발신자 이메일 주소
sendgrid.templateIdSENDGRID_TEMPLATE_IDstring선택-기본 이메일 템플릿 ID

* 이메일 발송 기능을 사용하는 경우 필수

결제 시스템 설정

설정 키환경 변수타입필수 여부기본값설명
payment.providerPAYMENT_PROVIDERstring선택'stripe'결제 제공자
payment.apiKeyPAYMENT_API_KEYstring필수*-결제 API 키
payment.secretKeyPAYMENT_SECRET_KEYstring필수*-결제 시크릿 키
payment.webhookSecretPAYMENT_WEBHOOK_SECRETstring선택-웹훅 인증 시크릿

* 결제 기능을 사용하는 경우 필수

Atlassian 설정

설정 키환경 변수타입필수 여부기본값설명
atlassian.baseUrlATLASSIAN_BASE_URLstring필수*-Atlassian 인스턴스 URL
atlassian.usernameATLASSIAN_USERNAMEstring필수*-사용자 이름
atlassian.apiTokenATLASSIAN_API_TOKENstring필수*-API 토큰

* Atlassian 서비스(Jira, Confluence)를 사용하는 경우 필수

ConnectDTX 설정

설정 키환경 변수타입필수 여부기본값설명
connectDtx.baseUrlCONNECT_DTX_BASE_URLstring필수*-ConnectDTX API 기본 URL
connectDtx.apiKeyCONNECT_DTX_API_KEYstring필수*-ConnectDTX API 키
connectDtx.timeoutCONNECT_DTX_TIMEOUTnumber선택30000API 요청 타임아웃(ms)

* ConnectDTX 서비스를 사용하는 경우 필수

사용 예시

구성 정의

// libs/core/config/src/lib/namespaces/external-api.config.ts
import { registerAs } from '@nestjs/config';

export const externalApiConfig = registerAs('externalApi', () => ({
openai: {
apiKey: process.env.OPENAI_API_KEY,
model: process.env.OPENAI_MODEL || 'gpt-4',
timeout: parseInt(process.env.OPENAI_TIMEOUT || '30000', 10),
maxTokens: parseInt(process.env.OPENAI_MAX_TOKENS || '2048', 10),
},
google: {
apiKey: process.env.GOOGLE_API_KEY,
searchEngineId: process.env.GOOGLE_SEARCH_ENGINE_ID,
},
sendgrid: {
apiKey: process.env.SENDGRID_API_KEY,
fromEmail: process.env.SENDGRID_FROM_EMAIL,
templateId: process.env.SENDGRID_TEMPLATE_ID,
},
payment: {
provider: process.env.PAYMENT_PROVIDER || 'stripe',
apiKey: process.env.PAYMENT_API_KEY,
secretKey: process.env.PAYMENT_SECRET_KEY,
webhookSecret: process.env.PAYMENT_WEBHOOK_SECRET,
},
atlassian: {
baseUrl: process.env.ATLASSIAN_BASE_URL,
username: process.env.ATLASSIAN_USERNAME,
apiToken: process.env.ATLASSIAN_API_TOKEN,
},
connectDtx: {
baseUrl: process.env.CONNECT_DTX_BASE_URL,
apiKey: process.env.CONNECT_DTX_API_KEY,
timeout: parseInt(process.env.CONNECT_DTX_TIMEOUT || '30000', 10),
},
}));

유효성 검증 스키마

// libs/core/config/src/lib/schemas/external-api.schema.ts
import * as Joi from 'joi';

export const externalApiConfigSchema = {
// OpenAI 설정
OPENAI_API_KEY: Joi.string().optional(),
OPENAI_MODEL: Joi.string().default('gpt-4'),
OPENAI_TIMEOUT: Joi.number().default(30000),
OPENAI_MAX_TOKENS: Joi.number().default(2048),

// Google API 설정
GOOGLE_API_KEY: Joi.string().optional(),
GOOGLE_SEARCH_ENGINE_ID: Joi.string().optional(),

// SendGrid 설정
SENDGRID_API_KEY: Joi.string().optional(),
SENDGRID_FROM_EMAIL: Joi.string().email().optional(),
SENDGRID_TEMPLATE_ID: Joi.string().optional(),

// 결제 시스템 설정
PAYMENT_PROVIDER: Joi.string().default('stripe'),
PAYMENT_API_KEY: Joi.string().optional(),
PAYMENT_SECRET_KEY: Joi.string().optional(),
PAYMENT_WEBHOOK_SECRET: Joi.string().optional(),

// Atlassian 설정
ATLASSIAN_BASE_URL: Joi.string().uri().optional(),
ATLASSIAN_USERNAME: Joi.string().optional(),
ATLASSIAN_API_TOKEN: Joi.string().optional(),

// ConnectDTX 설정
CONNECT_DTX_BASE_URL: Joi.string().uri().optional(),
CONNECT_DTX_API_KEY: Joi.string().optional(),
CONNECT_DTX_TIMEOUT: Joi.number().default(30000),
};

OpenAI 서비스에서 사용

// OpenAI 설정 사용 예시
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@core/config';
import { OpenAI } from 'openai';

@Injectable()
export class OpenAiService {
private openai: OpenAI;
private model: string;
private maxTokens: number;

constructor(private readonly configService: ConfigService) {
// OpenAI 설정 로드
const openaiConfig = this.configService.getConfig<{
apiKey: string;
model: string;
timeout: number;
maxTokens: number;
}>('externalApi', 'openai');

// OpenAI 클라이언트 초기화
if (openaiConfig.apiKey) {
this.openai = new OpenAI({
apiKey: openaiConfig.apiKey,
timeout: openaiConfig.timeout,
});
this.model = openaiConfig.model;
this.maxTokens = openaiConfig.maxTokens;
} else {
console.warn('OpenAI API key is not configured');
}
}

async generateText(prompt: string): Promise<string> {
if (!this.openai) {
throw new Error('OpenAI client is not initialized');
}

const response = await this.openai.chat.completions.create({
model: this.model,
messages: [{ role: 'user', content: prompt }],
max_tokens: this.maxTokens,
});

return response.choices[0]?.message.content || '';
}
}

SendGrid 서비스에서 사용

// SendGrid 설정 사용 예시
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@core/config';
import * as SendGrid from '@sendgrid/mail';

@Injectable()
export class EmailService {
private fromEmail: string;
private defaultTemplateId?: string;

constructor(private readonly configService: ConfigService) {
// SendGrid 설정 로드
const sendgridConfig = this.configService.getConfig<{
apiKey: string;
fromEmail: string;
templateId?: string;
}>('externalApi', 'sendgrid');

// SendGrid 클라이언트 초기화
if (sendgridConfig.apiKey) {
SendGrid.setApiKey(sendgridConfig.apiKey);
this.fromEmail = sendgridConfig.fromEmail;
this.defaultTemplateId = sendgridConfig.templateId;
} else {
console.warn('SendGrid API key is not configured');
}
}

async sendEmail(to: string, subject: string, text: string, html?: string): Promise<void> {
if (!this.fromEmail) {
throw new Error('SendGrid is not properly configured');
}

await SendGrid.send({
to,
from: this.fromEmail,
subject,
text,
html: html || text,
});
}

async sendTemplateEmail(to: string, templateData: Record<string, string>, templateId?: string): Promise<void> {
if (!this.fromEmail) {
throw new Error('SendGrid is not properly configured');
}

await SendGrid.send({
to,
from: this.fromEmail,
templateId: templateId || this.defaultTemplateId,
dynamicTemplateData: templateData,
});
}
}

환경별 설정 가이드

로컬 개발 환경

로컬 개발 환경에서는 서비스별 JSON 설정 파일을 통해 외부 API 설정을 구성합니다. 일반적으로 테스트용 API 키와 샌드박스 서비스를 사용합니다.

// cloudrun-deploy/secret/local/dev/dta-wide-api.json
{
"OPENAI_API_KEY": "sk-svcacct-xGwdG0mAWv976uDb8sVFk4ngPYrV4GIiqKA8HRwmTjOi1Yeh56ypILscCIthPhuwNmC-B6BpOH7T3BlbkFJYp9GgcjCYx_OBtUsz5CmKrZyj8sVuOZNLf1IhdLmawklIorfuU0vVdrUABzy474MZl5Gv_2FxMAA",
"OPENAI_MODEL": "gpt-4o",
"OPENAI_TIMEOUT": "60000",

"GOOGLE_API_KEY": "AIzaSyCFTubRV_CyuC2mSFJlWhJ6GFA_zEFgZys",
"GOOGLE_CSE_ID": "dev84214429cc54b4cf8",

"SENDGRID_API_KEY": "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",
"EMAIL_SENDER": "info@weltcorp.com",

"IMP_CODE": "imp67136621",
"IMP_KEY": "0201154055364850",
"IMP_SECRET": "9DcoGeSKzCfYWwSD9IDV0LZJ0eG8gqII1WsWEOVFLrGzCgTe6JAdZJTMZrVGkWV6TO4h6Pqkzgvqmexw",

"ATLASSIAN_API_KEY": "ATATT3xFfGF0tWgcTwY8tYz60vorIzl3FX3-P5_J8mCd83C9VeAS472jITJbGRbpqL0jCvKZeTwY1247RwptRwNH-E0pwy3d2RDDF6q2gpfgDU_qB2FfiF-9LxEbB8VcgnqwMpKlOrJR-s6gQRb4S9WLR32DK-JeapUl-wZq6OiTYm1xPmiY_Ok=5F5C6969",
"ATLASSIAN_WORKSPACE_URL": "https://weltcorp.atlassian.net",
"ATLASSIAN_USER_NAME": "bok@weltcorp.com",

"CONNECTDTX_API_HOST": "test-api.connectdtx.net",
"CONNECTDTX_API_CLIENT_ID": "59f078202354f5df2431",
"CONNECTDTX_API_SECRET": "K3RbQH9v8X_Osu_He71OIg",
"CONNECT_DTX_TIMEOUT": "30000"
}

클라우드 런 환경

클라우드 런 환경에서는 서비스별 JSON 설정 파일을 통해 실제 프로덕션 API 키와 보안 설정을 사용합니다. 이 설정은 배포 프로세스에서 Cloud Run 서비스의 환경 변수로 자동 설정됩니다.

// cloudrun-deploy/secret/cloudrun/prod/dta-wide-api.json
{
"OPENAI_API_KEY": "${SECRET_OPENAI_API_KEY}",
"OPENAI_MODEL": "gpt-4o",
"OPENAI_TIMEOUT": "30000",

"GOOGLE_API_KEY": "${SECRET_GOOGLE_API_KEY}",
"GOOGLE_CSE_ID": "${SECRET_GOOGLE_SEARCH_ENGINE_ID}",

"SENDGRID_API_KEY": "${SECRET_SENDGRID_API_KEY}",
"EMAIL_SENDER": "no-reply@weltcorp.com",

"IMP_CODE": "${SECRET_IMP_CODE}",
"IMP_KEY": "${SECRET_IMP_KEY}",
"IMP_SECRET": "${SECRET_IMP_SECRET}",

"ATLASSIAN_API_KEY": "${SECRET_ATLASSIAN_API_KEY}",
"ATLASSIAN_WORKSPACE_URL": "https://weltcorp.atlassian.net",
"ATLASSIAN_USER_NAME": "${SECRET_ATLASSIAN_USER_NAME}",

"CONNECTDTX_API_HOST": "api.connectdtx.net",
"CONNECTDTX_API_CLIENT_ID": "${SECRET_CONNECTDTX_API_CLIENT_ID}",
"CONNECTDTX_API_SECRET": "${SECRET_CONNECTDTX_API_SECRET}"
}

참고: 프로덕션 환경에서는 민감한 API 키를 직접 JSON 파일에 저장하지 않고, 배포 과정에서 Secret Manager에서 로드하여 환경 변수로 설정하는 방식을 사용할 수 있습니다. 위 예시의 ${SECRET_XXX} 부분은 이러한 치환이 일어날 자리 표시자입니다.

보안 고려 사항

외부 API 설정은 API 키, 시크릿 키 등 민감한 정보를 포함하므로 특별한 보안 조치가 필요합니다:

  1. API 키 관리:

    • 프로덕션 환경에서는 모든 API 키와 시크릿을 Google Cloud Secret Manager에 저장하고 환경 변수로 주입합니다.
    • 개발 환경에서는 .env 파일에 저장하고 이 파일은 버전 관리 시스템에 포함하지 않습니다.
  2. 키 순환:

    • 정기적으로 API 키를 변경하는 정책을 수립합니다.
    • 직원 퇴사, 보안 사고 발생 시 즉시 모든 API 키를 교체합니다.
  3. 최소 권한 원칙:

    • 각 API 서비스에 필요한 최소한의 권한만 부여합니다.
    • 가능한 경우 읽기 전용 API 키를 사용합니다.
  4. API 키 노출 방지:

    • 클라이언트 측으로 API 키를 전송하지 않습니다.
    • 로그에 API 키가 기록되지 않도록 합니다.
    • 오류 메시지에 API 키 정보가 노출되지 않도록 합니다.

주의 사항

  • 의존성 관리: 외부 API에 과도하게 의존하지 않도록 설계하고, API 장애 시 대체 방안을 마련합니다.
  • 요금 모니터링: API 호출 횟수와 비용을 모니터링하여 예상치 못한 비용 증가를 방지합니다.
  • 제한율 관리: API 제한율(Rate Limit)을 고려하여 적절한 호출 빈도를 유지합니다.
  • 타임아웃 설정: 모든 API 호출에 적절한 타임아웃을 설정하여 애플리케이션 성능에 영향을 미치지 않도록 합니다.
  • 에러 처리: 외부 API 오류를 적절히 처리하고, 실패 시 재시도 메커니즘을 구현합니다.