acquire-core-x/README.md
hyeongwoo-choi 7139fdd2fd docs: 상세 README (아키텍처·XA흐름·빌드/실행·검증·모듈·마이그레이션 매핑)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 23:09:39 +00:00

221 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# acquire-core-x
**실제로 동작하는** 카드 매입·정산(acquiring & settlement) 레거시 시스템 —
오픈소스 TP 모니터 **Enduro/X**(open Tuxedo/ATMI) + **ECPG/PostgreSQL** + **XA 2PC** 위에서
`tpcall` 서비스와 배치가 진짜로 기동·트랜잭션 처리된다.
> **목적** — Klaro Forge 자율 마이그레이션 도구의 **대형 전환 대상 픽스처**.
> 이 C/ATMI 시스템(2,000+본)을 Java Spring Boot로 전환하는 시나리오를 검증한다.
> 컴파일만 되는 스텁이 아니라 **상용급 TP 미들웨어에서 실제로 도는** 시스템이라는 점이 핵심이다.
---
## 1. 스택
| 계층 | 실체 | 비고 |
|---|---|---|
| TP 모니터 | **Enduro/X 7.0.12** (open Tuxedo/ATMI) | `tpservice`/`tpcall`/`tpadvertise`/`tpbegin`/`tpcommit`, 소스빌드 |
| 전문 버퍼 | **UBF** (≈ Tuxedo FML32) | `Bget`/`Bchg`, 필드테이블 `mkfldhdr` |
| DB 접근 | **ECPG** (`EXEC SQL`) → **PostgreSQL 15** | Pro\*C 대응 오픈소스 |
| 분산 트랜잭션 | **XA 2PC**`libndrxxaecpg.so` ECPG XA 스위치 | `tmsrv`가 prepare/commit/rollback 조율 |
| 오케스트레이션 | `ndrxd` + `ndrxconfig.xml` + `app.ini`(CCONFIG) | 서버 그룹·서비스·RM 설정 |
| 컨테이너 | `docker compose` (postgres + endurox app) | 재현 가능한 단일 스택 |
> **주의(라이선스):** Enduro/X 런타임은 **AGPLv3**(Mavimax). 내부 테스트 자산으로 사용.
> 상용 Tuxedo(Oracle)·Tmax ProFrame의 오픈소스 대응이며, 진짜 Tuxedo/Tmax는 사용하지 않는다.
---
## 2. 아키텍처
### 2.1 런타임 토폴로지
```
┌──────────────────────── app 컨테이너 (Enduro/X) ─────────────────────────┐
acqdrv ─────▶ │ ndrxd (TP 모니터) │
(client) │ ├─ tmsrv (RM1 = PostgreSQL, XA 조율: prepare/commit/rollback) │
tpinit │ ├─ cconfsrv / tpevsrv / cpmsrv (시스템 서버) │
tpbegin │ ├─ ac_svr … 매입 30 서비스 (ACQUIRE, ACQ_DDC, ACQ_CANCEL …) │
tpcall ─────▶│ ├─ au_svr … 승인/한도 30 │ 11개 "모듈 서버" │
tpcommit │ ├─ rc_svr … 대사 30 │ 각 서버가 자기 모듈의 │
│ ├─ st_svr … 정산/수수료 30 │ ~30 서비스를 tpadvertise │
│ └─ … lg cl mm vl mg cm py │ (총 333 서비스) │
│ │ EXEC SQL (ECPG, XA 스위치 libndrxxaecpg) │
└────────┼───────────────────────────────────────────────────────────────┘
PostgreSQL (db 컨테이너, max_prepared_transactions=100)
merchant · purchase · approval · settlement · ledger · … (75 테이블)
```
### 2.2 "모듈 서버" 패턴 (실제 Tuxedo 방식)
서비스 하나당 프로세스 하나가 아니라, **모듈 서버 1개가 그 모듈의 다수 서비스를 advertise**한다.
서비스 로직은 파일 하나에 하나씩(`svc/<SVCNAME>.pgc` + 카피북 `<SVCNAME>.h`), 서버(`<mod>_svr.pgc`)는
얇은 디스패처(`tpsvrinit`에서 `tpadvertise` 테이블만). DB 접근은 `dbio/*.pgc`(정적 라이브러리로 링크),
배치는 독립 실행파일(`batch/*.pgc`).
### 2.3 XA 2PC 트랜잭션 흐름 (핵심)
```
client: tpbegin() # 글로벌 트랜잭션 시작
└▶ tpcall("ACQUIRE") # ac_svr: EXEC SQL INSERT purchase (XA 브랜치 A)
└▶ tpcall("RECONCILE") # rc_svr: EXEC SQL INSERT ledger (XA 브랜치 B)
└▶ tpcall("SETTLE") # st_svr: EXEC SQL INSERT settlement(XA 브랜치 C)
client: tpcommit()
└▶ tmsrv: xa_prepare(A,B,C) → xa_commit(A,B,C) # 2단계 커밋, 원자적 확정
```
`tpabort()` 시 tmsrv가 세 브랜치를 모두 `xa_rollback` → DB에 아무것도 남지 않는다.
ECPG XA 스위치가 `tpopen()`에서 연결을 열므로 **`EXEC SQL CONNECT`가 없다**. XA 브랜치 간에는
서로의 미커밋 행이 안 보이므로 각 서비스는 **자기 테이블만 쓰는(owner-writes)** 규율을 지킨다.
---
## 3. 디렉터리 구조
```
acquire-core-x/
├─ docker/
│ ├─ endurox.Dockerfile # Enduro/X 소스빌드 (-DENABLE_POSTGRES=ON → libndrxxaecpg)
│ └─ docker-compose.yml # postgres + endurox app (sysctl/ulimit 포함)
├─ db/
│ └─ schema.d/ # 00-base.sql + 모듈별 NN-<mod>.sql (75 테이블)
├─ app/
│ ├─ conf/ # app.ini(CCONFIG), ndrxconfig.xml(생성), setapp.sh(XA env)
│ ├─ ubftab/acq.fd # UBF 필드테이블 (전 모듈 공용)
│ ├─ build.sh # 모듈 자동발견 빌드 + ndrxconfig 생성
│ ├─ entrypoint.sh # 빌드 → ndrxd 기동
│ └─ src/<mod>/ # 11개 모듈 (ac au rc st py lg cl mm vl mg cm)
│ ├─ <mod>_svr.pgc # 얇은 모듈 서버(디스패처)
│ ├─ svc/*.pgc + *.h # 1서비스=1파일 + 카피북 (모듈당 30)
│ ├─ dbio/*.pgc + *.h # DB 접근 계층 (모듈당 ~40)
│ ├─ batch/*.pgc # XA 배치 (모듈당 ~22)
│ └─ run/*.sh # 운영 기동 스크립트 (모듈당 ~17)
└─ docs/
├─ architecture.md # 상세 아키텍처
└─ service-catalog.md # 서비스 카탈로그
```
---
## 4. 빌드 & 실행
```bash
docker compose -f docker/docker-compose.yml up -d --build # postgres + endurox app 기동
```
빌드(전 모듈 ecpg→buildserver, ~240 배치 컴파일)는 수 분 걸린다. 완료되면 `ndrxd`
11개 모듈 서버 + `tmsrv`를 부팅한다.
### 4.1 필수 런타임 요건 (없으면 ndrxd 부팅 실패 — 실측으로 규명)
POSIX 메시지큐/세마포어 한도 때문에 **app 컨테이너에 반드시** 필요 (compose에 이미 반영):
```yaml
sysctls:
fs.mqueue.msg_max: "512"
fs.mqueue.msgsize_max: "65536"
fs.mqueue.queues_max: "8192" # 333 서비스 = 333+ 큐, 기본 256 초과
ulimits:
msgqueue: 2147483648 # 큐 1개 = MSGMAX×MSGSIZEMAX, 다수 큐 → RLIMIT_MSGQUEUE 상향
nofile: 65536
```
`app.ini`: `NDRX_MSGMAX=50`, `NDRX_MSGSIZEMAX=16000` (큐당 메모리 축소).
PostgreSQL: `-c max_prepared_transactions=100` (XA 2단계 커밋용).
---
## 5. 검증 (실행 중인 시스템 확인)
```bash
C="docker compose -f docker/docker-compose.yml"
# 서버 프로세스 — 12개 전부 runok 이어야 함
$C exec app bash -lc '. /app/conf/setapp.sh; xadmin ppm'
# advertise된 서비스 — 333 AVAIL
$C exec app bash -lc '. /app/conf/setapp.sh; xadmin psc'
# 매입 거래 실행 (tpbegin → ACQUIRE→RECONCILE→SETTLE → tpcommit)
$C exec app bash -lc '. /app/conf/setapp.sh; /app/bin/acqdrv M0001 1000000'
# >>> COMMIT OK: purchase_id=… fee=2500 net=997500 status=S
# DB 반영 + XA 정합성
$C exec db psql -U acq -d acq -c "select * from purchase; select * from settlement;"
$C exec db psql -U acq -d acq -c "select count(*) from pg_prepared_xacts;" # 0 = 2PC 정상
```
**검증 완료 상태:** 12 서버 runok · 333 서비스 AVAIL · 매입체인 XA 원자 커밋(status=S) ·
`pg_prepared_xacts=0`(롤백 시 무잔존) · 전 `.pgc` 정규화-고유(클론 0).
---
## 6. 모듈 & 도메인
| 코드 | 모듈 | 대표 서비스 |
|---|---|---|
| **ac** | 매입(acquiring) | ACQUIRE, ACQ_DDC/EDI/EDC, CANCEL/CORRECT, PARTIAL/INSTALL/FOREIGN, DUPCHK, WHT, TAXINV |
| **au** | 승인/한도 | AUTH, LIMIT_CHK/DEC/RST, STANDIN, FRAUD_DETECT, PREAUTH, DCC_QUOTE |
| **rc** | 대사(reconcile) | RECONCILE, RC_3WAY(승인·매입·입금), RC_TOLERANCE, RC_AMTDIFF, RC_AUTOFIX |
| **st** | 정산/수수료 | SETTLE, ST_MDR, ST_VANFEE, ST_NETTING, ST_VAT, ST_WHT, ST_PAYDATE(T+n) |
| **py** | 지급(payment) | PAY_FILEGEN(고정길이 전문), PAY_RESULT, PAY_SPLIT, PAY_XFERLINK |
| **lg** | 원장(ledger) | LG_POST, LG_DOUBLE(복식부기 차/대변), LG_BALCHK, LG_REVERSE, LG_TRIALBAL |
| **cl** | 마감(closing) | CL_DAILY/MONTHLY/QUARTER, CL_SNAPSHOT, CL_RECLOSE, CL_YEAREND |
| **mm** | 마스터 | MM_MERCH_REG/UPD, MM_FEERATE, MM_BIN, MM_LIMIT, MM_GRADE |
| **vl** | 정합성검증 | VL_SALES, VL_CARD, VL_AMOUNT, VL_ANOMALY, VL_REFINTEG |
| **mg** | 전문게이트웨이 | MG_ISO8583, MG_BITMAP, MG_STAN(채번), MG_ROUTE, MG_MAC |
| **cm** | 공통 | CM_CODE, CM_BIZDAY, CM_LUHN, CM_CRC, CM_FX, CM_SEQ |
**대표 tpcall 체인:** `ACQUIRE → RECONCILE → SETTLE`(글로벌 XA, 매입→대사→정산→원장 반영).
---
## 7. 파일 통계
| 종류 | 수 |
|---|---|
| 전체 (git 추적) | **2,052** |
| 프로그램 `.pgc` (svc 330 + dbio ~440 + batch ~240) | **1,015** |
| 카피북/헤더 `.h` | 817 |
| 기동/배치 스크립트 `.sh` | 191 |
| 스키마 `.sql` | 11 |
| — 모듈 서버 11 · 서비스 330 · 배치 바이너리 242 · DB 테이블 75 | |
`.pgc`는 정규화(식별자→X, 숫자→N, 공백제거) 후 **md5 고유**(클론 0) — 파일마다 실제 다른 로직.
---
## 8. 규명·해결한 실런타임 이슈 (재현 노트)
1. **gpgme 빌드의존** — Enduro/X `tpbridge``gpgme.h` 요구 → `libgpgme-dev` 추가.
2. **atmitest 빌드깨짐**`DEFINE_DISABLETEST`가 제외 안 함 → CMakeLists에서 `add_subdirectory(atmitest)` 제거.
3. **mqueue 한도**`fs.mqueue.queues_max`(기본 256 < 333 서비스), `RLIMIT_MSGQUEUE`(큐당 5.6MB0.8MB로 축소 + ulimit 상향).
4. **XA 브랜치 격리** 형제 브랜치는 서로의 미커밋 불가시 owner-writes 규율 + 서비스별 분리 서버.
---
## 9. 마이그레이션 타깃 매핑 (→ Spring Boot)
| 레거시 (Enduro/X) | Spring Boot |
|---|---|
| `tpservice` / `tpadvertise` | `@Service` + 메서드 |
| `tpcall(SVC)` | 주입 호출(동기) |
| UBF `Bget`/`Bchg` (전문버퍼) | DTO / record |
| `tpbegin`/`tpcommit` (XA) | `@Transactional` (JTA 또는 단일 DB) |
| `EXEC SQL` / ECPG dbio | MyBatis / JPA repository |
| 커서 배치 | Spring Batch |
| 고정길이 전문 | 코덱(fixed-length) |
| `ndrxconfig` 서비스 등록 | 컴포넌트 스캔 / 라우팅 |
| `db/schema.d/*.sql` | Flyway |
---
## 10. 검증 체크리스트
- [x] Enduro/X 소스빌드 (PostgreSQL XA 포함) 이미지 `acquire-x/endurox:7.0.12`
- [x] `ndrxd` 부팅 (11 모듈서버 + tmsrv 전부 runok)
- [x] 커스텀 `tpservice` advertise + 실제 `tpcall` 왕복
- [x] ECPG XA 서비스 체인(매입대사정산) + 커밋 row psql 검증 + 롤백(`prepared_xacts=0`)
- [x] 2,000본+ 실동작 시스템 (2,052 파일 / `.pgc` 1,015본, 모듈 dbio·batch 세분화)
- [x] `.pgc` 정규화-고유 (클론 0)
- [ ] Klaro Forge 마이그레이션 Spring Boot (Phase 3, 예정)