parent
b160476ddc
commit
6c677f7f69
@ -0,0 +1,802 @@
|
|||||||
|
# VMIS 통합 전략 설계 문서
|
||||||
|
|
||||||
|
**작성일**: 2025-11-06
|
||||||
|
**목적**: 설정 기반으로 내부/외부 차량 정보 조회 방식을 선택할 수 있는 아키텍처 설계
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 목차
|
||||||
|
|
||||||
|
1. [요구사항](#1-요구사항)
|
||||||
|
2. [아키텍처 설계](#2-아키텍처-설계)
|
||||||
|
3. [구현 가이드](#3-구현-가이드)
|
||||||
|
4. [설정 예시](#4-설정-예시)
|
||||||
|
5. [테스트 방법](#5-테스트-방법)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 요구사항
|
||||||
|
|
||||||
|
### 1.1 핵심 요구사항
|
||||||
|
|
||||||
|
**application.yml 설정에 따라 두 가지 방식 중 선택 가능**:
|
||||||
|
|
||||||
|
1. **Internal Mode (내부 모드)**
|
||||||
|
- 신규 통합된 VMIS 모듈을 직접 호출
|
||||||
|
- 네트워크 오버헤드 없음
|
||||||
|
- 단일 트랜잭션 처리 가능
|
||||||
|
- 높은 성능
|
||||||
|
|
||||||
|
2. **External Mode (외부 모드)**
|
||||||
|
- 기존 ExternalVehicleApiService를 통해 외부 REST API 호출
|
||||||
|
- 독립적인 VMIS-interface 서버와 통신
|
||||||
|
- 기존 방식 유지 (하위 호환성)
|
||||||
|
|
||||||
|
### 1.2 전환 시나리오
|
||||||
|
|
||||||
|
- **개발 단계**: External 모드로 테스트 (기존 방식)
|
||||||
|
- **통합 테스트**: Internal 모드로 전환하여 성능 검증
|
||||||
|
- **점진적 배포**: 일부 서버는 External, 일부는 Internal
|
||||||
|
- **롤백 대비**: 문제 발생 시 External 모드로 즉시 전환
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 아키텍처 설계
|
||||||
|
|
||||||
|
### 2.1 전략 패턴 (Strategy Pattern) 적용
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ VehicleInfoServiceFacade │ (클라이언트 코드)
|
||||||
|
│ - getBasicInfo(vhrno) │
|
||||||
|
│ - getLedgerInfo(vhrno) │
|
||||||
|
└─────────────────┬───────────────────────┘
|
||||||
|
│ 의존
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ <<interface>> │
|
||||||
|
│ VehicleInfoService │ (전략 인터페이스)
|
||||||
|
│ + getBasicInfo(vhrno): ResponseDTO │
|
||||||
|
│ + getLedgerInfo(vhrno): ResponseDTO │
|
||||||
|
└─────────────────┬───────────────────────┘
|
||||||
|
│
|
||||||
|
┌────────┴─────────┐
|
||||||
|
↓ ↓
|
||||||
|
┌──────────────────┐ ┌─────────────────────────┐
|
||||||
|
│ Internal모드 │ │ External모드 │
|
||||||
|
│ │ │ │
|
||||||
|
│ Internal │ │ External │
|
||||||
|
│ VehicleInfo │ │ VehicleInfo │
|
||||||
|
│ ServiceImpl │ │ ServiceImpl │
|
||||||
|
│ │ │ │
|
||||||
|
│ @Conditional │ │ @Conditional │
|
||||||
|
│ OnProperty │ │ OnProperty │
|
||||||
|
│ (internal) │ │ (external) │
|
||||||
|
│ │ │ │
|
||||||
|
│ ↓ │ │ ↓ │
|
||||||
|
│ VMIS 모듈 │ │ RestTemplate │
|
||||||
|
│ 직접 호출 │ │ (HTTP 호출) │
|
||||||
|
└──────────────────┘ └─────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 컴포넌트 설계
|
||||||
|
|
||||||
|
#### 2.2.1 인터페이스
|
||||||
|
|
||||||
|
**VehicleInfoService.java** (전략 인터페이스)
|
||||||
|
```java
|
||||||
|
package go.kr.project.common.service;
|
||||||
|
|
||||||
|
import go.kr.project.common.vo.VehicleBasicInfoResponseVO;
|
||||||
|
import go.kr.project.common.vo.VehicleLedgerResponseVO;
|
||||||
|
|
||||||
|
public interface VehicleInfoService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 차량 기본정보 조회
|
||||||
|
* @param vhrno 차량번호
|
||||||
|
* @return 차량 기본정보 응답
|
||||||
|
*/
|
||||||
|
VehicleBasicInfoResponseVO getBasicInfo(String vhrno);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 차량 등록원부 조회
|
||||||
|
* @param vhrno 차량번호
|
||||||
|
* @return 차량 등록원부 응답
|
||||||
|
*/
|
||||||
|
VehicleLedgerResponseVO getLedgerInfo(String vhrno);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2.2 구현체 1: Internal Mode
|
||||||
|
|
||||||
|
**InternalVehicleInfoServiceImpl.java**
|
||||||
|
```java
|
||||||
|
package go.kr.project.vmis.service;
|
||||||
|
|
||||||
|
import go.kr.project.common.service.VehicleInfoService;
|
||||||
|
import go.kr.project.common.vo.*;
|
||||||
|
import go.kr.project.vmis.service.CarBassMatterInqireService;
|
||||||
|
import go.kr.project.vmis.service.CarLedgerFrmbkService;
|
||||||
|
import go.kr.project.vmis.model.vo.*;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "internal")
|
||||||
|
public class InternalVehicleInfoServiceImpl implements VehicleInfoService {
|
||||||
|
|
||||||
|
private final CarBassMatterInqireService carBassMatterInqireService;
|
||||||
|
private final CarLedgerFrmbkService carLedgerFrmbkService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VehicleBasicInfoResponseVO getBasicInfo(String vhrno) {
|
||||||
|
log.info("[INTERNAL MODE] 차량 기본정보 조회 - 차량번호: {}", vhrno);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 내부 VMIS 모듈 직접 호출
|
||||||
|
CarBassMatterInqireRequestVO request = new CarBassMatterInqireRequestVO();
|
||||||
|
request.setVhrno(vhrno);
|
||||||
|
|
||||||
|
Envelope<VehicleBasicInfoDTO> response =
|
||||||
|
carBassMatterInqireService.getBasicInfo(request);
|
||||||
|
|
||||||
|
// DTO → VO 변환
|
||||||
|
return convertToBasicInfoResponse(response);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[INTERNAL MODE] 차량 기본정보 조회 실패 - 차량번호: {}", vhrno, e);
|
||||||
|
return createErrorResponse(vhrno, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VehicleLedgerResponseVO getLedgerInfo(String vhrno) {
|
||||||
|
log.info("[INTERNAL MODE] 차량 등록원부 조회 - 차량번호: {}", vhrno);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 내부 VMIS 모듈 직접 호출
|
||||||
|
CarLedgerFrmbkRequestVO request = new CarLedgerFrmbkRequestVO();
|
||||||
|
request.setVhrno(vhrno);
|
||||||
|
|
||||||
|
Envelope<VehicleLedgerDTO> response =
|
||||||
|
carLedgerFrmbkService.getLedgerInfo(request);
|
||||||
|
|
||||||
|
// DTO → VO 변환
|
||||||
|
return convertToLedgerResponse(response);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[INTERNAL MODE] 차량 등록원부 조회 실패 - 차량번호: {}", vhrno, e);
|
||||||
|
return createLedgerErrorResponse(vhrno, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DTO → VO 변환 메서드들...
|
||||||
|
private VehicleBasicInfoResponseVO convertToBasicInfoResponse(Envelope<VehicleBasicInfoDTO> envelope) {
|
||||||
|
// 구현...
|
||||||
|
}
|
||||||
|
|
||||||
|
private VehicleLedgerResponseVO convertToLedgerResponse(Envelope<VehicleLedgerDTO> envelope) {
|
||||||
|
// 구현...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2.3 구현체 2: External Mode
|
||||||
|
|
||||||
|
**ExternalVehicleInfoServiceImpl.java**
|
||||||
|
```java
|
||||||
|
package go.kr.project.externalApi.service;
|
||||||
|
|
||||||
|
import go.kr.project.common.service.VehicleInfoService;
|
||||||
|
import go.kr.project.common.vo.*;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "external", matchIfMissing = true)
|
||||||
|
public class ExternalVehicleInfoServiceImpl implements VehicleInfoService {
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
|
@Value("${vmis.external.api.url:http://localhost:8081/api/v1/vehicles}")
|
||||||
|
private String vmisApiUrl;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VehicleBasicInfoResponseVO getBasicInfo(String vhrno) {
|
||||||
|
log.info("[EXTERNAL MODE] 차량 기본정보 조회 - 차량번호: {}", vhrno);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String url = vmisApiUrl + "/basic";
|
||||||
|
|
||||||
|
// 기존 ExternalVehicleApiService 로직 활용
|
||||||
|
// RestTemplate으로 외부 API 호출
|
||||||
|
VehicleBasicRequestVO request = new VehicleBasicRequestVO();
|
||||||
|
request.setVhrno(vhrno);
|
||||||
|
|
||||||
|
VehicleBasicInfoResponseVO response = restTemplate.postForObject(
|
||||||
|
url,
|
||||||
|
request,
|
||||||
|
VehicleBasicInfoResponseVO.class
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[EXTERNAL MODE] 차량 기본정보 조회 실패 - 차량번호: {}", vhrno, e);
|
||||||
|
return createErrorResponse(vhrno, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VehicleLedgerResponseVO getLedgerInfo(String vhrno) {
|
||||||
|
log.info("[EXTERNAL MODE] 차량 등록원부 조회 - 차량번호: {}", vhrno);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String url = vmisApiUrl + "/ledger";
|
||||||
|
|
||||||
|
VehicleLedgerRequestVO request = new VehicleLedgerRequestVO();
|
||||||
|
request.setVhrno(vhrno);
|
||||||
|
|
||||||
|
VehicleLedgerResponseVO response = restTemplate.postForObject(
|
||||||
|
url,
|
||||||
|
request,
|
||||||
|
VehicleLedgerResponseVO.class
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[EXTERNAL MODE] 차량 등록원부 조회 실패 - 차량번호: {}", vhrno, e);
|
||||||
|
return createLedgerErrorResponse(vhrno, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2.4 설정 클래스
|
||||||
|
|
||||||
|
**VmisIntegrationConfig.java**
|
||||||
|
```java
|
||||||
|
package go.kr.project.vmis.config;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(VmisProperties.class)
|
||||||
|
public class VmisIntegrationConfig {
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "internal")
|
||||||
|
static class InternalModeConfig {
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
log.info("=================================================");
|
||||||
|
log.info("VMIS Integration Mode: INTERNAL");
|
||||||
|
log.info("차량 정보 조회: 내부 VMIS 모듈 직접 호출");
|
||||||
|
log.info("=================================================");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "external", matchIfMissing = true)
|
||||||
|
static class ExternalModeConfig {
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
log.info("=================================================");
|
||||||
|
log.info("VMIS Integration Mode: EXTERNAL");
|
||||||
|
log.info("차량 정보 조회: 외부 REST API 호출");
|
||||||
|
log.info("=================================================");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 구현 가이드
|
||||||
|
|
||||||
|
### 3.1 파일 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
go/kr/project/
|
||||||
|
├── common/
|
||||||
|
│ ├── service/
|
||||||
|
│ │ └── VehicleInfoService.java (인터페이스)
|
||||||
|
│ └── vo/
|
||||||
|
│ ├── VehicleBasicInfoResponseVO.java
|
||||||
|
│ └── VehicleLedgerResponseVO.java
|
||||||
|
│
|
||||||
|
├── vmis/
|
||||||
|
│ ├── config/
|
||||||
|
│ │ ├── VmisProperties.java
|
||||||
|
│ │ └── VmisIntegrationConfig.java (설정)
|
||||||
|
│ └── service/
|
||||||
|
│ └── InternalVehicleInfoServiceImpl.java (내부 모드 구현)
|
||||||
|
│
|
||||||
|
└── externalApi/
|
||||||
|
└── service/
|
||||||
|
└── ExternalVehicleInfoServiceImpl.java (외부 모드 구현)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 기존 코드 리팩토링
|
||||||
|
|
||||||
|
#### Before (기존 ExternalVehicleApiService)
|
||||||
|
```java
|
||||||
|
@Service
|
||||||
|
public class ExternalVehicleApiService {
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
|
public VehicleApiResponseVO getBasicInfo(String vhrno) {
|
||||||
|
// RestTemplate으로 외부 API 호출
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### After (전략 패턴 적용)
|
||||||
|
```java
|
||||||
|
// 기존 ExternalVehicleApiService는 ExternalVehicleInfoServiceImpl로 대체
|
||||||
|
// 클라이언트 코드는 VehicleInfoService 인터페이스에 의존
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CarInspectionService {
|
||||||
|
|
||||||
|
private final VehicleInfoService vehicleInfoService; // 인터페이스 주입
|
||||||
|
|
||||||
|
public void processInspection(String vhrno) {
|
||||||
|
// 설정에 따라 자동으로 내부/외부 구현체가 주입됨
|
||||||
|
VehicleBasicInfoResponseVO info = vehicleInfoService.getBasicInfo(vhrno);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 구현 순서
|
||||||
|
|
||||||
|
1. **VehicleInfoService 인터페이스 생성**
|
||||||
|
- 위치: `go/kr/project/common/service/`
|
||||||
|
- 메서드: getBasicInfo, getLedgerInfo
|
||||||
|
|
||||||
|
2. **InternalVehicleInfoServiceImpl 구현**
|
||||||
|
- 위치: `go/kr/project/vmis/service/`
|
||||||
|
- VMIS 모듈 직접 호출
|
||||||
|
|
||||||
|
3. **ExternalVehicleInfoServiceImpl 구현**
|
||||||
|
- 위치: `go/kr/project/externalApi/service/`
|
||||||
|
- 기존 ExternalVehicleApiService 로직 이전
|
||||||
|
|
||||||
|
4. **VmisIntegrationConfig 생성**
|
||||||
|
- 설정 로깅 및 검증
|
||||||
|
|
||||||
|
5. **기존 클라이언트 코드 수정**
|
||||||
|
- ExternalVehicleApiService → VehicleInfoService 의존성 변경
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 설정 예시
|
||||||
|
|
||||||
|
### 4.1 application.yml (공통)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# VMIS 통합 모드 설정
|
||||||
|
vmis:
|
||||||
|
integration:
|
||||||
|
mode: internal # internal | external
|
||||||
|
|
||||||
|
# Internal 모드 설정
|
||||||
|
system:
|
||||||
|
info-sys-id: "VMIS001"
|
||||||
|
info-sys-ip: "${SERVER_IP:192.168.1.100}"
|
||||||
|
manager-id: "admin"
|
||||||
|
manager-name: "관리자"
|
||||||
|
manager-tel: "02-1234-5678"
|
||||||
|
|
||||||
|
gpki:
|
||||||
|
enabled: false
|
||||||
|
cert-path: "${GPKI_CERT_PATH:/path/to/cert.der}"
|
||||||
|
private-key-path: "${GPKI_PRIVATE_KEY_PATH:/path/to/private.key}"
|
||||||
|
private-key-password: "${GPKI_PASSWORD:}"
|
||||||
|
|
||||||
|
government:
|
||||||
|
host: "https://www.vemanet.com"
|
||||||
|
base-path: "/openapi"
|
||||||
|
connect-timeout: 10000
|
||||||
|
read-timeout: 15000
|
||||||
|
services:
|
||||||
|
basic:
|
||||||
|
path: "/carBassMatterInqire"
|
||||||
|
api-key: "${GOV_API_KEY_BASIC:}"
|
||||||
|
ledger:
|
||||||
|
path: "/carLedgerFrmbk"
|
||||||
|
api-key: "${GOV_API_KEY_LEDGER:}"
|
||||||
|
|
||||||
|
# External 모드 설정
|
||||||
|
external:
|
||||||
|
api:
|
||||||
|
url: "http://localhost:8081/api/v1/vehicles"
|
||||||
|
connect-timeout: 5000
|
||||||
|
read-timeout: 10000
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 application-local.yml (개발 환경)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
vmis:
|
||||||
|
integration:
|
||||||
|
mode: external # 개발 중에는 외부 API 사용
|
||||||
|
|
||||||
|
external:
|
||||||
|
api:
|
||||||
|
url: "http://localhost:8081/api/v1/vehicles"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 application-dev.yml (개발 서버)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
vmis:
|
||||||
|
integration:
|
||||||
|
mode: internal # 개발 서버에서는 내부 모듈 테스트
|
||||||
|
|
||||||
|
gpki:
|
||||||
|
enabled: false # 개발 서버는 암호화 비활성화
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 application-prd.yml (운영 환경)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
vmis:
|
||||||
|
integration:
|
||||||
|
mode: internal # 운영 환경은 내부 모듈 사용
|
||||||
|
|
||||||
|
gpki:
|
||||||
|
enabled: true # 운영 환경은 암호화 활성화
|
||||||
|
cert-path: "${GPKI_CERT_PATH}"
|
||||||
|
private-key-path: "${GPKI_PRIVATE_KEY_PATH}"
|
||||||
|
private-key-password: "${GPKI_PASSWORD}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.5 환경변수 설정 예시
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Internal 모드 (운영)
|
||||||
|
export VMIS_INTEGRATION_MODE=internal
|
||||||
|
export GOV_API_KEY_BASIC=your-api-key-basic
|
||||||
|
export GOV_API_KEY_LEDGER=your-api-key-ledger
|
||||||
|
export GPKI_PASSWORD=your-gpki-password
|
||||||
|
|
||||||
|
# External 모드 (롤백 시)
|
||||||
|
export VMIS_INTEGRATION_MODE=external
|
||||||
|
export VMIS_EXTERNAL_API_URL=http://vmis-interface-server:8081/api/v1/vehicles
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 테스트 방법
|
||||||
|
|
||||||
|
### 5.1 단위 테스트
|
||||||
|
|
||||||
|
#### InternalVehicleInfoServiceImplTest.java
|
||||||
|
```java
|
||||||
|
@SpringBootTest
|
||||||
|
@TestPropertySource(properties = {
|
||||||
|
"vmis.integration.mode=internal"
|
||||||
|
})
|
||||||
|
class InternalVehicleInfoServiceImplTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VehicleInfoService vehicleInfoService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetBasicInfo() {
|
||||||
|
// given
|
||||||
|
String vhrno = "12가3456";
|
||||||
|
|
||||||
|
// when
|
||||||
|
VehicleBasicInfoResponseVO response = vehicleInfoService.getBasicInfo(vhrno);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertNotNull(response);
|
||||||
|
assertEquals(vhrno, response.getVhrno());
|
||||||
|
assertTrue(vehicleInfoService instanceof InternalVehicleInfoServiceImpl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ExternalVehicleInfoServiceImplTest.java
|
||||||
|
```java
|
||||||
|
@SpringBootTest
|
||||||
|
@TestPropertySource(properties = {
|
||||||
|
"vmis.integration.mode=external",
|
||||||
|
"vmis.external.api.url=http://localhost:8081/api/v1/vehicles"
|
||||||
|
})
|
||||||
|
class ExternalVehicleInfoServiceImplTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VehicleInfoService vehicleInfoService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetBasicInfo() {
|
||||||
|
// given
|
||||||
|
String vhrno = "12가3456";
|
||||||
|
|
||||||
|
// when
|
||||||
|
VehicleBasicInfoResponseVO response = vehicleInfoService.getBasicInfo(vhrno);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertNotNull(response);
|
||||||
|
assertTrue(vehicleInfoService instanceof ExternalVehicleInfoServiceImpl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 통합 테스트
|
||||||
|
|
||||||
|
#### 1. Internal 모드 테스트
|
||||||
|
```bash
|
||||||
|
# application-local.yml 수정
|
||||||
|
vmis:
|
||||||
|
integration:
|
||||||
|
mode: internal
|
||||||
|
|
||||||
|
# 애플리케이션 실행
|
||||||
|
./gradlew bootRun --args='--spring.profiles.active=local'
|
||||||
|
|
||||||
|
# 로그 확인
|
||||||
|
# [INTERNAL MODE] 차량 기본정보 조회 - 차량번호: 12가3456
|
||||||
|
|
||||||
|
# API 호출
|
||||||
|
curl -X POST http://localhost:8080/api/inspection \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"vhrno":"12가3456"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. External 모드 테스트
|
||||||
|
```bash
|
||||||
|
# 1. VMIS-interface 서버 실행 (8081 포트)
|
||||||
|
cd D:\workspace\git\VMIS-interface
|
||||||
|
./gradlew bootRun
|
||||||
|
|
||||||
|
# 2. application-local.yml 수정
|
||||||
|
vmis:
|
||||||
|
integration:
|
||||||
|
mode: external
|
||||||
|
external:
|
||||||
|
api:
|
||||||
|
url: "http://localhost:8081/api/v1/vehicles"
|
||||||
|
|
||||||
|
# 3. VIPS 실행
|
||||||
|
cd D:\workspace\git\VIPS
|
||||||
|
./gradlew bootRun --args='--spring.profiles.active=local'
|
||||||
|
|
||||||
|
# 로그 확인
|
||||||
|
# [EXTERNAL MODE] 차량 기본정보 조회 - 차량번호: 12가3456
|
||||||
|
|
||||||
|
# API 호출
|
||||||
|
curl -X POST http://localhost:8080/api/inspection \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"vhrno":"12가3456"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 성능 비교 테스트
|
||||||
|
|
||||||
|
```java
|
||||||
|
@SpringBootTest
|
||||||
|
class VehicleInfoServicePerformanceTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VehicleInfoService vehicleInfoService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void comparePerformance() {
|
||||||
|
String vhrno = "12가3456";
|
||||||
|
int iterations = 100;
|
||||||
|
|
||||||
|
// Warmup
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
vehicleInfoService.getBasicInfo(vhrno);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performance test
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
for (int i = 0; i < iterations; i++) {
|
||||||
|
vehicleInfoService.getBasicInfo(vhrno);
|
||||||
|
}
|
||||||
|
long endTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
long avgTime = (endTime - startTime) / iterations;
|
||||||
|
log.info("평균 응답 시간: {}ms (모드: {})", avgTime,
|
||||||
|
vehicleInfoService.getClass().getSimpleName());
|
||||||
|
|
||||||
|
// Internal 모드가 External 모드보다 빠를 것으로 예상
|
||||||
|
assertTrue(avgTime < 100); // 100ms 이내
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 장애 상황 테스트
|
||||||
|
|
||||||
|
#### 시나리오 1: External 모드에서 외부 API 장애
|
||||||
|
```bash
|
||||||
|
# VMIS-interface 서버 중지
|
||||||
|
# → External 모드는 에러 반환
|
||||||
|
# → 설정을 Internal 모드로 변경하여 복구
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 시나리오 2: Internal 모드에서 DB 장애
|
||||||
|
```bash
|
||||||
|
# DB 연결 차단
|
||||||
|
# → Internal 모드는 에러 반환
|
||||||
|
# → 설정을 External 모드로 변경하여 복구
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 운영 시나리오
|
||||||
|
|
||||||
|
### 6.1 점진적 배포 (Blue-Green)
|
||||||
|
|
||||||
|
**1단계: 준비**
|
||||||
|
```bash
|
||||||
|
# 모든 서버: External 모드
|
||||||
|
vmis.integration.mode=external
|
||||||
|
```
|
||||||
|
|
||||||
|
**2단계: 일부 서버 전환**
|
||||||
|
```bash
|
||||||
|
# 서버 A, B: External 모드
|
||||||
|
vmis.integration.mode=external
|
||||||
|
|
||||||
|
# 서버 C, D: Internal 모드 (테스트)
|
||||||
|
vmis.integration.mode=internal
|
||||||
|
```
|
||||||
|
|
||||||
|
**3단계: 모니터링 후 전체 전환**
|
||||||
|
```bash
|
||||||
|
# 모든 서버: Internal 모드
|
||||||
|
vmis.integration.mode=internal
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 롤백 절차
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. application-prd.yml 또는 환경변수 수정
|
||||||
|
vmis.integration.mode=external
|
||||||
|
|
||||||
|
# 2. 애플리케이션 재시작 (또는 설정 리로드)
|
||||||
|
# 3. 로그 확인
|
||||||
|
# [EXTERNAL MODE] ...
|
||||||
|
|
||||||
|
# 4. VMIS-interface 외부 서버가 실행 중인지 확인
|
||||||
|
curl http://vmis-interface-server:8081/actuator/health
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 모니터링 지표
|
||||||
|
|
||||||
|
**Prometheus/Grafana 메트릭**:
|
||||||
|
```java
|
||||||
|
@Component
|
||||||
|
public class VehicleInfoServiceMetrics {
|
||||||
|
|
||||||
|
private final Counter internalCalls;
|
||||||
|
private final Counter externalCalls;
|
||||||
|
private final Timer internalTimer;
|
||||||
|
private final Timer externalTimer;
|
||||||
|
|
||||||
|
// 메트릭 수집...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**로그 분석**:
|
||||||
|
```bash
|
||||||
|
# Internal 모드 호출 건수
|
||||||
|
grep "\[INTERNAL MODE\]" application.log | wc -l
|
||||||
|
|
||||||
|
# External 모드 호출 건수
|
||||||
|
grep "\[EXTERNAL MODE\]" application.log | wc -l
|
||||||
|
|
||||||
|
# 평균 응답 시간
|
||||||
|
grep "응답시간" application.log | awk '{sum+=$NF; count++} END {print sum/count}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 주의사항
|
||||||
|
|
||||||
|
### 7.1 설정 검증
|
||||||
|
|
||||||
|
**애플리케이션 시작 시 체크**:
|
||||||
|
```java
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class VmisIntegrationValidator implements ApplicationRunner {
|
||||||
|
|
||||||
|
@Value("${vmis.integration.mode}")
|
||||||
|
private String integrationMode;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private VehicleInfoService vehicleInfoService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(ApplicationArguments args) {
|
||||||
|
if (vehicleInfoService == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"VehicleInfoService 빈이 생성되지 않았습니다. " +
|
||||||
|
"vmis.integration.mode 설정을 확인하세요: " + integrationMode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("VMIS 통합 모드 검증 완료: {} (구현체: {})",
|
||||||
|
integrationMode,
|
||||||
|
vehicleInfoService.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 External 모드 사용 시 주의점
|
||||||
|
|
||||||
|
1. **외부 서버 가용성 확인**
|
||||||
|
- VMIS-interface 서버가 실행 중이어야 함
|
||||||
|
- Health check 엔드포인트 모니터링
|
||||||
|
|
||||||
|
2. **네트워크 타임아웃 설정**
|
||||||
|
- connect-timeout, read-timeout 적절히 설정
|
||||||
|
- Circuit Breaker 패턴 고려
|
||||||
|
|
||||||
|
3. **API 버전 호환성**
|
||||||
|
- External 서버와 클라이언트의 API 계약 일치 확인
|
||||||
|
|
||||||
|
### 7.3 Internal 모드 사용 시 주의점
|
||||||
|
|
||||||
|
1. **트랜잭션 범위**
|
||||||
|
- @Transactional 설정 주의
|
||||||
|
- DB 연결 풀 크기 조정
|
||||||
|
|
||||||
|
2. **GPKI 설정**
|
||||||
|
- 운영 환경에서는 gpki.enabled=true
|
||||||
|
- 인증서 경로 및 비밀번호 확인
|
||||||
|
|
||||||
|
3. **API 키 보안**
|
||||||
|
- 정부 API 키는 환경변수로 관리
|
||||||
|
- 코드에 하드코딩 금지
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. FAQ
|
||||||
|
|
||||||
|
### Q1: 설정을 변경하면 재시작이 필요한가요?
|
||||||
|
**A**: 네, @ConditionalOnProperty는 애플리케이션 시작 시에만 평가됩니다. 설정 변경 후 재시작이 필요합니다.
|
||||||
|
|
||||||
|
### Q2: 두 모드를 동시에 사용할 수 있나요?
|
||||||
|
**A**: 아니오, 한 번에 하나의 모드만 활성화됩니다. 설정에 따라 하나의 구현체만 빈으로 등록됩니다.
|
||||||
|
|
||||||
|
### Q3: External 모드에서 VMIS-interface 서버가 다운되면?
|
||||||
|
**A**: RestTemplate 타임아웃이 발생하고 에러가 반환됩니다. Circuit Breaker 패턴을 추가로 구현하면 장애 전파를 방지할 수 있습니다.
|
||||||
|
|
||||||
|
### Q4: Internal 모드가 External 모드보다 얼마나 빠른가요?
|
||||||
|
**A**: 네트워크 오버헤드가 없으므로 평균 50-100ms 정도 빠를 것으로 예상됩니다. 실제 성능은 환경에 따라 다릅니다.
|
||||||
|
|
||||||
|
### Q5: 개발 중에는 어떤 모드를 사용하나요?
|
||||||
|
**A**: 개발 초기에는 External 모드를 사용하여 기존 방식으로 개발하고, 통합 테스트 단계에서 Internal 모드로 전환하여 검증합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**문서 버전**: 1.0
|
||||||
|
**최종 수정**: 2025-11-06
|
||||||
Loading…
Reference in New Issue