Files
eapim-online/docs/ELINK_TRANSACTION_CONTEXT.md
T

14 KiB

ElinkTransactionContext 아키텍처

개요

ElinkTransactionContext는 트랜잭션(거래) 범위에서 컨텍스트 변수를 관리하기 위한 ThreadLocal 기반 유틸리티입니다. JEP 함수 등 EAIMessage나 Properties에 직접 접근할 수 없는 클래스에서 트랜잭션 범위 변수에 접근할 수 있도록 합니다.


핵심 설계 원칙

  1. ThreadLocal이 직접 저장소: EAIMessage의 Map을 참조하지 않고 ThreadLocal에 직접 저장
  2. Lazy Initialization: 최초 put 시 Map 자동 생성 (명시적 초기화 불필요)
  3. clear()는 필수: 스레드 풀 환경에서 메모리 누수 방지
  4. 비동기 전달용 필드 분리: transactionContextTransfer는 JMS 전달 전용

구성 요소

1. ElinkTransactionContext (저장소)

// 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 (전달용)

// 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 복원 조건

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 등)

// SeedSalt 설정 (최초 호출 시 Map 자동 생성)
ElinkTransactionContext.setSeedSalt("o123y498172");

// EncryptionKey 설정
ElinkTransactionContext.setEncryptionKey("myKey123");

값 조회 (JEP 함수, Transform 등)

// SeedSalt 조회
String seedSalt = ElinkTransactionContext.getSeedSalt();

// EncryptionKey 조회
String encryptionKey = ElinkTransactionContext.getEncryptionKey();

변수 추가 방법

새로운 트랜잭션 변수가 필요한 경우 ElinkTransactionContext에 접근자 메소드 추가:

// 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 직접 사용 금지

// ❌ 잘못된 사용
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 초기화    │
└─────────────────────────────────────┘