8.8 KiB
8.8 KiB
📋 Phase 3.12: ExternalCallConfigService Raw Query 전환 계획
📋 개요
ExternalCallConfigService는 8개의 Prisma 호출이 있으며, 외부 API 호출 설정 관리를 담당하는 서비스입니다.
📊 기본 정보
| 항목 | 내용 |
|---|---|
| 파일 위치 | backend-node/src/services/externalCallConfigService.ts |
| 파일 크기 | 612 라인 |
| Prisma 호출 | 0개 (전환 완료) |
| 현재 진행률 | 8/8 (100%) ✅ 전환 완료 |
| 복잡도 | 중간 (JSON 필드, 복잡한 CRUD) |
| 우선순위 | 🟡 중간 (Phase 3.12) |
| 상태 | ✅ 완료 |
🎯 전환 목표
- ⏳ 8개 모든 Prisma 호출을
db.ts의query(),queryOne()함수로 교체 - ⏳ 외부 호출 설정 CRUD 기능 정상 동작
- ⏳ JSON 필드 처리 (headers, params, auth_config)
- ⏳ 동적 WHERE 조건 생성
- ⏳ 민감 정보 암호화/복호화 유지
- ⏳ TypeScript 컴파일 성공
- ⏳ Prisma import 완전 제거
🔍 예상 Prisma 사용 패턴
주요 기능 (8개 예상)
1. 외부 호출 설정 목록 조회
- findMany with filters
- 페이징, 정렬
- 동적 WHERE 조건 (is_active, company_code, search)
2. 외부 호출 설정 단건 조회
- findUnique or findFirst
- config_id 기준
3. 외부 호출 설정 생성
- create
- JSON 필드 처리 (headers, params, auth_config)
- 민감 정보 암호화
4. 외부 호출 설정 수정
- update
- 동적 UPDATE 쿼리
- JSON 필드 업데이트
5. 외부 호출 설정 삭제
- delete or soft delete
6. 외부 호출 설정 복제
- findUnique + create
7. 외부 호출 설정 테스트
- findUnique
- 실제 HTTP 호출
8. 외부 호출 이력 조회
- findMany with 관계 조인
- 통계 쿼리
💡 전환 전략
1단계: 기본 CRUD 전환 (5개)
- getExternalCallConfigs() - 목록 조회
- getExternalCallConfig() - 단건 조회
- createExternalCallConfig() - 생성
- updateExternalCallConfig() - 수정
- deleteExternalCallConfig() - 삭제
2단계: 추가 기능 전환 (3개)
- duplicateExternalCallConfig() - 복제
- testExternalCallConfig() - 테스트
- getExternalCallHistory() - 이력 조회
💻 전환 예시
예시 1: 목록 조회 (동적 WHERE + JSON)
변경 전:
const configs = await prisma.external_call_configs.findMany({
where: {
company_code: companyCode,
is_active: isActive,
OR: [
{ config_name: { contains: search, mode: "insensitive" } },
{ endpoint_url: { contains: search, mode: "insensitive" } },
],
},
orderBy: { created_at: "desc" },
skip,
take: limit,
});
변경 후:
const conditions: string[] = ["company_code = $1"];
const params: any[] = [companyCode];
let paramIndex = 2;
if (isActive !== undefined) {
conditions.push(`is_active = $${paramIndex++}`);
params.push(isActive);
}
if (search) {
conditions.push(
`(config_name ILIKE $${paramIndex} OR endpoint_url ILIKE $${paramIndex})`
);
params.push(`%${search}%`);
paramIndex++;
}
const configs = await query<any>(
`SELECT * FROM external_call_configs
WHERE ${conditions.join(" AND ")}
ORDER BY created_at DESC
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
[...params, limit, skip]
);
예시 2: JSON 필드 생성
변경 전:
const config = await prisma.external_call_configs.create({
data: {
config_name: data.config_name,
endpoint_url: data.endpoint_url,
http_method: data.http_method,
headers: data.headers, // JSON
params: data.params, // JSON
auth_config: encryptedAuthConfig, // JSON (암호화됨)
company_code: companyCode,
},
});
변경 후:
const config = await queryOne<any>(
`INSERT INTO external_call_configs
(config_name, endpoint_url, http_method, headers, params,
auth_config, company_code, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())
RETURNING *`,
[
data.config_name,
data.endpoint_url,
data.http_method,
JSON.stringify(data.headers),
JSON.stringify(data.params),
JSON.stringify(encryptedAuthConfig),
companyCode,
]
);
예시 3: 동적 UPDATE (JSON 포함)
변경 전:
const updateData: any = {};
if (data.headers) updateData.headers = data.headers;
if (data.params) updateData.params = data.params;
const config = await prisma.external_call_configs.update({
where: { config_id: configId },
data: updateData,
});
변경 후:
const updateFields: string[] = ["updated_at = NOW()"];
const values: any[] = [];
let paramIndex = 1;
if (data.headers !== undefined) {
updateFields.push(`headers = $${paramIndex++}`);
values.push(JSON.stringify(data.headers));
}
if (data.params !== undefined) {
updateFields.push(`params = $${paramIndex++}`);
values.push(JSON.stringify(data.params));
}
const config = await queryOne<any>(
`UPDATE external_call_configs
SET ${updateFields.join(", ")}
WHERE config_id = $${paramIndex}
RETURNING *`,
[...values, configId]
);
🔧 기술적 고려사항
1. JSON 필드 처리
3개의 JSON 필드가 있을 것으로 예상:
headers- HTTP 헤더params- 쿼리 파라미터auth_config- 인증 설정 (암호화됨)
// INSERT/UPDATE 시
JSON.stringify(jsonData);
// SELECT 후
const parsedData =
typeof row.headers === "string" ? JSON.parse(row.headers) : row.headers;
2. 민감 정보 암호화
auth_config는 암호화되어 저장되므로, 기존 암호화/복호화 로직 유지:
import { encrypt, decrypt } from "../utils/encryption";
// 저장 시
const encryptedAuthConfig = encrypt(JSON.stringify(authConfig));
// 조회 시
const decryptedAuthConfig = JSON.parse(decrypt(row.auth_config));
3. HTTP 메소드 검증
const VALID_HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
if (!VALID_HTTP_METHODS.includes(httpMethod)) {
throw new Error("Invalid HTTP method");
}
4. URL 검증
try {
new URL(endpointUrl);
} catch {
throw new Error("Invalid endpoint URL");
}
✅ 전환 완료 내역
전환된 Prisma 호출 (8개)
getConfigs()- 목록 조회 (findMany → query)getConfigById()- 단건 조회 (findUnique → queryOne)createConfig()- 중복 검사 (findFirst → queryOne)createConfig()- 생성 (create → queryOne with INSERT)updateConfig()- 중복 검사 (findFirst → queryOne)updateConfig()- 수정 (update → queryOne with 동적 UPDATE)deleteConfig()- 삭제 (update → query)getExternalCallConfigsForButtonControl()- 조회 (findMany → query)
주요 기술적 개선사항
- 동적 WHERE 조건 생성 (company_code, call_type, api_type, is_active, search)
- ILIKE를 활용한 대소문자 구분 없는 검색
- 동적 UPDATE 쿼리 (9개 필드)
- JSON 필드 처리 (
config_data→JSON.stringify()) - 중복 검사 로직 유지
코드 정리
- import 문 수정 완료
- Prisma import 완전 제거
- TypeScript 컴파일 성공
- Linter 오류 없음
📝 원본 전환 체크리스트
1단계: Prisma 호출 전환 (✅ 완료)
getExternalCallConfigs()- 목록 조회 (findMany + count)getExternalCallConfig()- 단건 조회 (findUnique)createExternalCallConfig()- 생성 (create)updateExternalCallConfig()- 수정 (update)deleteExternalCallConfig()- 삭제 (delete)duplicateExternalCallConfig()- 복제 (findUnique + create)testExternalCallConfig()- 테스트 (findUnique)getExternalCallHistory()- 이력 조회 (findMany)
2단계: 코드 정리
- import 문 수정 (
prisma→query, queryOne) - JSON 필드 처리 확인
- 암호화/복호화 로직 유지
- Prisma import 완전 제거
3단계: 테스트
- 단위 테스트 작성 (8개)
- 통합 테스트 작성 (3개)
- 암호화 테스트
- HTTP 호출 테스트
4단계: 문서화
- 전환 완료 문서 업데이트
- API 문서 업데이트
🎯 예상 난이도 및 소요 시간
- 난이도: ⭐⭐⭐ (중간)
- JSON 필드 처리
- 암호화/복호화 로직
- HTTP 호출 테스트
- 예상 소요 시간: 1~1.5시간
상태: ⏳ 대기 중
특이사항: JSON 필드, 민감 정보 암호화, HTTP 호출 포함