LabHub
はじめる
배우기 러닝패스 코스

EAI 中間層をつくる

中継層はヘッダーだけを読む

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

EAI 같은 중간 계층은 전문의 공통 헤더만 보고 길을 정한다. 그래서 헤더는 업무와 무관한 몇 개의 필드 — 길이·거래코드·글로벌 ID·송수신 기관·요청응답 구분·응답 코드 — 로 고정되고, 이 필드를 한 바이트라도 다르게 자르는 시스템이 하나 있으면 그 뒤의 모든 구간이 틀린다.

왜 이게 필요했나

은행 한 곳에는 채널(인터넷뱅킹·모바일·창구), 계정계(원장), 정보계, 카드, 대외계가 수십 개씩 있다. 시스템마다 서로 직접 연결하면 선이 n×(n-1)/2 개로 늘고, 한 시스템의 형식이 바뀔 때마다 그 시스템과 붙은 모든 쪽을 고쳐야 한다. 중간 계층(EAI, 채널 쪽은 흔히 MCI, 대외 쪽은 FEP 라고 부른다)은 이 선을 허브 하나로 모은다. 각 시스템은 허브와만 약속하고, 허브가 받은 전문을 어디로 보낼지, 어떤 형식으로 바꿀지, 실패하면 무엇을 돌려줄지를 대신 판단한다.

그런데 허브가 판단하려면 업무 내용을 몰라도 읽을 수 있는 자리가 있어야 한다. 계좌이체 전문과 보험 청구 전문은 업무부(본문)의 모양이 전혀 다르다. 허브가 거래마다 본문 레이아웃을 알아야 길을 정할 수 있다면, 새 거래 하나가 생길 때마다 허브를 고쳐야 한다. 그래서 모든 전문 앞에 같은 모양의 공통 헤더를 붙이고, 허브는 헤더만 읽는다. 본문은 목적지가 해석한다. 이 분리가 이 코스 전체의 출발점이다.

어떻게 동작하나

이 코스는 가상의 LabHub 은행 표준(LH-STD)을 쓴다. 헤더는 80바이트이고 전부 ASCII 다. 실제 기관의 규격은 공개되지 않은 경우가 많고 필드 순서·길이가 제각각이지만, 들어 있는 필드의 역할은 거의 같다.

필드 길이 역할
전문길이(MSG_LEN) 4 이 필드를 뺀 나머지 바이트 수. TCP 에서 전문 경계를 찾는 유일한 근거
거래코드(TX_CODE) 8 무슨 거래인가. 라우팅(2모듈)의 열쇠
글로벌 ID(GUID) 32 이 거래 하나를 끝까지 따라가는 번호. 추적(7모듈)과 중복 방지(8모듈)의 열쇠
송신·수신 기관 3+3 누가 누구에게. 대외 기관이면 대외계로 보낸다
요청응답구분 1 Q 요청 / R 응답
응답코드 4 요청은 공백, 응답은 0000 이나 오류 코드
전송일시 14 이 구간에서 보낸 시각

길이는 바이트다. 본문은 EUC-KR 이다. EUC-KR 은 KS X 1001 문자 집합을 담는 인코딩이라 한글 한 글자가 2바이트이고, 같은 글자가 UTF-8 에서는 3바이트다. 누군가 전문 파일을 편집기로 열었다가 UTF-8 로 저장하면 글자는 똑같아 보이는데 바이트 수가 늘어나고, 전문길이 필드는 옛 값 그대로 남는다. 받는 쪽은 길이만큼 읽고 나머지를 다음 전문의 앞부분으로 착각한다. 또 KS X 1001 에는 완성형 한글 2,350자만 들어 있어서, 이름에 '똠' 같은 글자가 들어간 고객은 2바이트 완성형 코드가 없다. 이때 구현마다 동작이 갈린다 — 실습 이미지의 glibc iconv 는 변환을 거절하고, 파이썬의 euc_kr 코덱은 오류 없이 8바이트짜리 조합 시퀀스로 바꾼다(이 과정에서 실측). 조용히 8바이트가 되면 고정길이 칸 계산이 통째로 어긋난다. 이런 고객을 어떻게 다룰지는 인코딩을 고르는 순간 정해 두어야 할 업무 규칙이다(3모듈에서 직접 다룬다).

TCP 는 전문을 모른다. TCP 는 바이트를 순서대로, 빠짐없이 전달하는 스트림이다(RFC 9293). 보내는 쪽이 send 를 두 번 했다고 받는 쪽 recv 가 두 번 나뉘어 오지 않는다. 한 번의 recv 에 전문 반 개가 올 수도, 두 개 반이 올 수도 있다. 그래서 받는 쪽은 언제나 길이 4바이트를 먼저 정확히 읽고, 그 길이만큼 다시 정확히 읽는다. 전문길이 필드가 헤더 첫머리에 있는 이유다. 이 규칙을 어긴 코드는 개발 환경(같은 기계, 짧은 전문)에서 거의 언제나 동작하다가 운영의 긴 전문과 느린 망에서만 깨진다.

GUID 는 한 번 만들고 끝까지 싣는다. 거래를 처음 만든 시스템(채널)이 발급하고, 허브·계정계·대외계는 받은 값을 그대로 넘긴다. 구간마다 새로 만들면 한 거래가 네 개의 번호로 흩어져 장애 때 이을 방법이 없다. LH-STD 는 GUID 를 W3C Trace Context 의 trace-id 와 같은 모양 — 소문자 16진수 32자, 전부 0 은 무효 — 으로 정했다. 그러면 HTTP 로 넘어가는 구간에서 traceparent 헤더에 그대로 실을 수 있다. 발급은 예측할 수 없는 난수(secrets, uuid4)로 한다. 시각과 순번으로 만들면 서버 두 대가 같은 밀리초에 같은 값을 낸다.

응답은 요청을 뒤집어 만든다. 거래코드와 GUID 는 그대로, 송신·수신 기관은 맞바꾸고, 구분은 R, 응답 코드를 채우고, 전송일시는 지금으로, 길이는 다시 계산한다. 길이를 손으로 넣는 코드는 언젠가 반드시 틀린다 — 만들 때 계산한다.

현장에서 만나는 모습

가장 흔한 사고는 길이 기준의 착각이다. 어떤 기관은 길이 필드가 자기 자신을 포함하고, 어떤 기관은 뺀다. 정의서에 한 줄로 적혀 있는 이 차이 때문에 대외 기관 하나와의 연동이 개통 날 통째로 멈춘다(6모듈에서 기관별 차이를 직접 다룬다). 두 번째는 형식 오류를 조용히 넘기는 파서다. 요청응답구분이 X 인 전문을 '요청' 으로 추측해 처리하면, 그 전문이 어디서 왜 망가졌는지 아무도 모른 채 원장이 바뀐다. 형식이 틀린 전문은 거절하고, 거절한 이유를 코드로 남긴다(E102). 세 번째는 GUID 를 구간마다 새로 따는 시스템이다. 장애 회의에서 "그 거래 번호가 우리 쪽에서는 안 보인다" 는 말이 나오면 대개 이것이다.

다음 실습에서 할 것

정의서(SPEC.md)를 읽고 헤더 레이아웃을 오프셋까지 적은 뒤, 수신함의 전문 여덟 건을 바이트로 대조한다. 그다음 파서(hdr.py)·응답 생성기(reply.py)·GUID 발급기(guid.py)·스트림 분리기(split.py)를 차례로 만들고, 수신함 전체의 판정 보고서를 낸다. 2모듈부터는 이 일을 하는 사내 공통 라이브러리(lhstd.py)를 쓴다 — 헤더는 한 벌만 있어야 한다.