301 lines
14 KiB
Markdown
301 lines
14 KiB
Markdown
# ElinkTransactionContext 아키텍처
|
|
|
|
## 개요
|
|
|
|
`ElinkTransactionContext`는 트랜잭션(거래) 범위에서 컨텍스트 변수를 관리하기 위한 ThreadLocal 기반 유틸리티입니다. JEP 함수 등 EAIMessage나 Properties에 직접 접근할 수 없는 클래스에서 트랜잭션 범위 변수에 접근할 수 있도록 합니다.
|
|
|
|
---
|
|
|
|
## 핵심 설계 원칙
|
|
|
|
1. **ThreadLocal이 직접 저장소**: EAIMessage의 Map을 참조하지 않고 ThreadLocal에 직접 저장
|
|
2. **Lazy Initialization**: 최초 put 시 Map 자동 생성 (명시적 초기화 불필요)
|
|
3. **clear()는 필수**: 스레드 풀 환경에서 메모리 누수 방지
|
|
4. **비동기 전달용 필드 분리**: `transactionContextTransfer`는 JMS 전달 전용
|
|
|
|
---
|
|
|
|
## 구성 요소
|
|
|
|
### 1. ElinkTransactionContext (저장소)
|
|
|
|
```java
|
|
// ElinkTransactionContext.java (elink-online-core)
|
|
public class ElinkTransactionContext {
|
|
private static final ThreadLocal<Map<String, Object>> THREAD_LOCAL = new ThreadLocal<>();
|
|
|
|
// 컨텍스트 정리 (필수)
|
|
public static void clear();
|
|
|
|
// 비동기 전달용
|
|
public static Map<String, Object> getAll(); // 복사본 반환
|
|
public static void restore(Map<String, Object> ctx); // 복원
|
|
|
|
// 명시적 접근자 (Lazy init - 최초 put 시 Map 생성)
|
|
public static void setSeedSalt(String seedSalt);
|
|
public static String getSeedSalt();
|
|
public static void setEncryptionKey(String encryptionKey);
|
|
public static String getEncryptionKey();
|
|
}
|
|
```
|
|
|
|
- **위치**: `elink-online-core`
|
|
- **역할**: ThreadLocal 기반 트랜잭션 변수 저장소
|
|
- **특징**:
|
|
- 명시적 초기화 불필요 (Lazy init)
|
|
- `clear()`는 필수 (finally 블록에서 호출)
|
|
|
|
### 2. EAIMessage.transactionContextTransfer (전달용)
|
|
|
|
```java
|
|
// EAIMessage.java (elink-online-common)
|
|
/**
|
|
* [주의] 비동기 JMS 전달 전용 - 직접 사용 금지!
|
|
*/
|
|
private Map<String, Object> transactionContextTransfer;
|
|
|
|
public Map<String, Object> getTransactionContextTransfer();
|
|
public void setTransactionContextTransfer(Map<String, Object> ctx);
|
|
```
|
|
|
|
- **위치**: `elink-online-common`
|
|
- **역할**: 비동기 거래 시 JMS를 통한 컨텍스트 전달
|
|
- **특징**:
|
|
- `Serializable` - ObjectMessage로 직렬화
|
|
- **직접 데이터 저장 용도로 사용 금지**
|
|
- 복원 후 즉시 null로 초기화
|
|
|
|
---
|
|
|
|
## 처리 흐름
|
|
|
|
### 동기 (Sync) 처리
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ ApiAdapterController.callApi() │
|
|
│ │ │
|
|
│ ├─ try { │
|
|
│ │ service.callApi() │
|
|
│ │ └─ RequestProcessor.hookup() │
|
|
│ │ │ │
|
|
│ │ ├─ [컨텍스트 사용 가능 - Lazy Init] ───────────────── │
|
|
│ │ │ ElinkTransactionContext.setSeedSalt("value") │
|
|
│ │ │ └─ 최초 호출 시 Map 자동 생성 │
|
|
│ │ │ │
|
|
│ │ ├─ FlowRouter.process() │
|
|
│ │ │ └─ ElinkESBProcessProxy → Process 호출 │
|
|
│ │ │ │
|
|
│ │ ├─ [반환 후에도 컨텍스트 사용 가능] ✓ │
|
|
│ │ │ │
|
|
│ │ └─ 응답 반환 │
|
|
│ │ } │
|
|
│ └─ finally { │
|
|
│ ElinkTransactionContext.clear() ← 최상위 진입점에서 해제 │
|
|
│ } │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 비동기 (Async) 처리 - Queue 사용
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ [송신 스레드] │
|
|
│ │
|
|
│ ApiAdapterController.callApi() │
|
|
│ │ │
|
|
│ ├─ try { │
|
|
│ │ RequestProcessor.hookup() │
|
|
│ │ ├─ ElinkTransactionContext.setSeedSalt("value") │
|
|
│ │ │ └─ ThreadLocal에 직접 저장 │
|
|
│ │ │ │
|
|
│ │ └─ FlowRouter.process() │
|
|
│ │ ├─ [컨텍스트 복사] ───────────────────────────────────── │
|
|
│ │ │ eaiMessage.setTransactionContextTransfer( │
|
|
│ │ │ ElinkTransactionContext.getAll() │
|
|
│ │ │ ) │
|
|
│ │ └─ JMSSender.sendToQueue(eaiMessage, ...) │
|
|
│ │ └─ EAIMessage 직렬화 (transactionContextTransfer 포함)│
|
|
│ │ } │
|
|
│ └─ finally { ElinkTransactionContext.clear() } │
|
|
│ │ │
|
|
└──────────────────────────┼──────────────────────────────────────────────┘
|
|
│
|
|
[JMS Queue]
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ [수신 스레드 - Consumer] │
|
|
│ │
|
|
│ FCQueueReceiver / DefaultQueueConsumer │
|
|
│ │ │
|
|
│ ├─ EAIMessage 역직렬화 (transactionContextTransfer 복원) │
|
|
│ │ │
|
|
│ └─ ElinkESBProcessProxy.callElinkESBProcess(BaseFCProcess, ...) │
|
|
│ │ │
|
|
│ ├─ [조건 충족: ASYNC && !outbound] │
|
|
│ │ ├─ ElinkTransactionContext.restore( │
|
|
│ │ │ eaiMessage.getTransactionContextTransfer() │
|
|
│ │ │ ) │
|
|
│ │ └─ eaiMessage.setTransactionContextTransfer(null) ← 즉시 초기화
|
|
│ │ │
|
|
│ ├─ BaseFCProcess.callService() │
|
|
│ │ └─ ElinkESBProcessProxy(..., RESTProcess, ...) │
|
|
│ │ └─ [조건 미충족: outbound] → 복원 안함 │
|
|
│ │ └─ RESTProcess 호출 │
|
|
│ │ └─ ElinkTransactionContext.getSeedSalt() │
|
|
│ │ └─ 송신 스레드에서 설정한 값 조회 ✓ │
|
|
│ │ │
|
|
│ └─ finally { ElinkTransactionContext.clear() } │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 초기화/해제 위치
|
|
|
|
| 위치 | 역할 | 비고 |
|
|
|------|------|------|
|
|
| `ApiAdapterController.callApi()` | 해제 (finally) | 동기 - 최상위 진입점 |
|
|
| `RequestProcessorSupport.execute()` | 해제 (finally) | 동기 - 안전망 (이중 해제) |
|
|
| `FlowRouter.process()` | 컨텍스트 복사 | 비동기 송신 전 |
|
|
| `ElinkESBProcessProxy.callElinkESBProcess()` | 복원/해제 (조건부) | 비동기 수신 시 |
|
|
|
|
### ElinkESBProcessProxy 복원 조건
|
|
|
|
```java
|
|
boolean shouldManageContext = eaiMessage != null
|
|
&& EAIMessageKeys.ASYNC_SVC.equals(eaiMessage.getSvcTsmtUsgTp()) // ASYNC 거래
|
|
&& (serviceURI == null || !serviceURI.startsWith("com.eactive.eai.outbound")); // outbound가 아닐 때
|
|
|
|
if (shouldManageContext) {
|
|
ElinkTransactionContext.restore(eaiMessage.getTransactionContextTransfer());
|
|
eaiMessage.setTransactionContextTransfer(null); // 복원 후 즉시 초기화
|
|
}
|
|
```
|
|
|
|
- **ASYNC 거래**: 동기 거래는 Lazy init으로 자동 처리
|
|
- **outbound가 아닐 때**: BaseFCProcess 호출 시에만 복원, RESTProcess 호출 시에는 이미 복원된 상태
|
|
|
|
> **Note**: `clear()`는 멱등(idempotent)하므로 여러 번 호출되어도 안전합니다.
|
|
|
|
---
|
|
|
|
## 사용 예시
|
|
|
|
### 값 설정 (RequestProcessor, DefaultProcess 등)
|
|
|
|
```java
|
|
// SeedSalt 설정 (최초 호출 시 Map 자동 생성)
|
|
ElinkTransactionContext.setSeedSalt("o123y498172");
|
|
|
|
// EncryptionKey 설정
|
|
ElinkTransactionContext.setEncryptionKey("myKey123");
|
|
```
|
|
|
|
### 값 조회 (JEP 함수, Transform 등)
|
|
|
|
```java
|
|
// SeedSalt 조회
|
|
String seedSalt = ElinkTransactionContext.getSeedSalt();
|
|
|
|
// EncryptionKey 조회
|
|
String encryptionKey = ElinkTransactionContext.getEncryptionKey();
|
|
```
|
|
|
|
---
|
|
|
|
## 변수 추가 방법
|
|
|
|
새로운 트랜잭션 변수가 필요한 경우 `ElinkTransactionContext`에 접근자 메소드 추가:
|
|
|
|
```java
|
|
// ElinkTransactionContext.java
|
|
|
|
// 1. Key 상수 추가
|
|
private static final String KEY_NEW_VARIABLE = "NEW_VARIABLE";
|
|
|
|
// 2. Setter 추가 (private put 헬퍼 사용)
|
|
public static void setNewVariable(String value) {
|
|
put(KEY_NEW_VARIABLE, value);
|
|
}
|
|
|
|
// 3. Getter 추가 (private get 헬퍼 사용)
|
|
public static String getNewVariable() {
|
|
return get(KEY_NEW_VARIABLE);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 주의사항
|
|
|
|
### Thread Pool 환경
|
|
|
|
- `ApiAdapterController.callApi()` finally에서 `clear()` 호출
|
|
- `ElinkESBProcessProxy.callElinkESBProcess()` finally에서 조건부 `clear()` 호출
|
|
- Thread 재사용 시 이전 트랜잭션 데이터 누수 방지
|
|
|
|
### 비동기 Queue 처리
|
|
|
|
- `EAIMessage.transactionContextTransfer`는 **전달 전용** (직접 접근 금지)
|
|
- `FlowRouter`에서 송신 전 `getAll()`로 복사
|
|
- `ElinkESBProcessProxy`에서 `restore()` 후 즉시 null 초기화
|
|
|
|
### transactionContextTransfer 직접 사용 금지
|
|
|
|
```java
|
|
// ❌ 잘못된 사용
|
|
eaiMessage.getTransactionContextTransfer().put("key", "value");
|
|
|
|
// ✓ 올바른 사용
|
|
ElinkTransactionContext.setSeedSalt("value");
|
|
```
|
|
|
|
---
|
|
|
|
## 관련 파일
|
|
|
|
| 파일 | 위치 | 역할 |
|
|
|------|------|------|
|
|
| `ElinkTransactionContext.java` | `elink-online-core` | ThreadLocal 컨텍스트 저장소 |
|
|
| `EAIMessage.java` | `elink-online-common` | transactionContextTransfer (전달용) |
|
|
| `ApiAdapterController.java` | `eapim-online` | 해제 (동기 최상위 진입점) |
|
|
| `FlowRouter.java` | `elink-online-common` | 비동기 송신 전 컨텍스트 복사 |
|
|
| `ElinkESBProcessProxy.java` | `elink-online-common` | 복원/해제 (비동기 - 조건부) |
|
|
| `RequestProcessorSupport.java` | `elink-online-common` | 해제 (동기 안전망) |
|
|
|
|
---
|
|
|
|
## 클래스 다이어그램
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ elink-online-core │
|
|
├─────────────────────────────────────┤
|
|
│ ElinkTransactionContext │
|
|
│ ┌───────────────────────────────┐ │
|
|
│ │ ThreadLocal<Map<String,Object>>│◄── 직접 저장소 (Lazy Init)
|
|
│ └───────────────────────────────┘ │
|
|
│ + clear() │
|
|
│ + getAll() → Map 복사본 │
|
|
│ + restore(Map) │
|
|
│ + setSeedSalt() / getSeedSalt() │
|
|
│ + setEncryptionKey() / ... │
|
|
└───────────────┬─────────────────────┘
|
|
│ 비동기 전달 시
|
|
│ getAll() → 복사
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ elink-online-common │
|
|
├─────────────────────────────────────┤
|
|
│ EAIMessage │
|
|
│ - transactionContextTransfer │◄── JMS 전달 전용 (직접 사용 금지!)
|
|
│ : Map<String,Object> │ Serializable
|
|
│ │
|
|
│ [송신] FlowRouter에서 설정 │
|
|
│ [수신] ElinkESBProcessProxy에서 │
|
|
│ restore() 후 null 초기화 │
|
|
└─────────────────────────────────────┘
|
|
```
|