# 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 초기화    │
└─────────────────────────────────────┘
```
