BTP

Message Mapping vs XSLT 어떤 게 더 빠를까? #shorts #SAP #BTP

▶ YouTube에서 보기

이 글에서 다룰 내용과 도달 지점

SAP Integration Suite의 Cloud Integration(iFlow) 안에서 데이터를 변환할 때 개발자가 가장 먼저 마주하는 결정은 Graphical Message Mapping을 쓸 것인가, 아니면 XSLT Mapping을 쓸 것인가입니다. 두 방식은 결과물이 같아 보여도 내부 실행 엔진, 디버깅 방식, 성능 특성, 유지보수 비용이 상당히 다릅니다. 이 글은 PurchaseOrder XML을 다운스트림 판매 시스템의 SalesOrder XML로 바꾸는 실전 시나리오를 사용해 두 방식을 나란히 놓고 비교합니다.

  • Message Mapping과 XSLT의 내부 처리 흐름 이해
  • 복잡도 기준으로 적절한 방식 선택하기
  • 동일 페이로드로 두 방식 구현 후 성능·가독성 비교
  • 대용량 처리 시 자주 발생하는 병목 원인 파악

미리 갖추면 좋은 배경

XML/XPath 문법에 대한 기본적인 감각, iFlow의 Message Mapping·XSLT Mapping 스텝을 팔레트에서 배치해 본 경험, 그리고 Cloud Integration의 Payload / Trace 뷰어를 다뤄본 정도면 충분히 따라올 수 있습니다. XSD 스키마의 occursnillable 개념을 알면 다대다 매핑 파트가 훨씬 쉬워집니다.

사용 환경과 사전 준비물

이 글의 예제는 다음 환경에서 검증했습니다. 릴리스 주기가 짧기 때문에 실제 화면 배치가 조금 다를 수 있으나 개념과 필드 명은 동일합니다.

  • SAP Integration Suite - Cloud Integration (Edge Integration Cell 미사용, 표준 클라우드 런타임)
  • Runtime: 2026 Q2 릴리스 기준 Camel 기반 통합 프로세스 엔진
  • Message Mapping Editor: Web UI 버전
  • XSLT: 1.0 완전 지원 / 2.0은 부분 지원 (일반적으로 XSLT 1.0을 권장)
  • 테스트 페이로드: 약 2KB의 소형 PurchaseOrder와 500개 라인아이템을 담은 4MB 대형 페이로드 두 종
  • 추적 도구: iFlow의 Message Processing Log와 Trace 레벨 로깅

런타임 노드가 공유 리소스이므로 Free Tier가 아니라 최소 Standard Edition 이상에서 성능 테스트를 진행하는 것이 권장됩니다.

변환 엔진의 동작 원리 파헤치기

두 방식은 겉으로는 모두 "XML → XML" 변환이지만, 내부 처리 모델이 다릅니다.

Message Mapping은 그래픽 에디터에서 정의한 소스·타깃 노드 매핑을 배포 시점에 내부적으로 실행 가능한 매핑 프로그램으로 컴파일합니다. 런타임에서는 XML SAX 파서와 유사한 이벤트 기반 방식으로 소스를 순회하며, 각 타깃 노드에 대해 ContextQueue 개념으로 값을 밀어 넣습니다. 이 Queue 모델은 다대다(m:n) 관계 매핑을 우아하게 처리할 수 있는 이유이자, 초심자가 SUPPRESS, [](빈 컨텍스트), collapseContexts 함수 앞에서 좌절하는 이유이기도 합니다.

XSLT는 널리 알려진 표준 XML 변환 언어로, iFlow 런타임은 내부적으로 JAXP TransformerFactory(대부분 Saxon-HE 또는 Xalan 계열)에 위임합니다. 소스 문서 전체를 DOM 또는 트리 모델로 로드한 뒤 템플릿 매칭 방식으로 순회합니다. 결과적으로 메모리 상주량이 커질 수 있지만, XPath 표현 하나로 깊이 있는 조건 분기를 처리할 수 있다는 강력함이 있습니다.

비유하자면 Message Mapping은 잘 정돈된 공장 컨베이어 벨트입니다. 소스 노드가 순서대로 지나가면서 각 매핑 함수 스테이션에서 조립됩니다. 반면 XSLT는 도서관 사서입니다. 문서 전체를 책상 위에 펼쳐놓고 필요한 부분을 자유롭게 찾아 쓸 수 있지만, 그만큼 책상(메모리)이 넓어야 합니다.

이 차이에서 파생되는 실무적 특성을 정리하면 다음과 같습니다.

  • 디버깅 도구: Message Mapping은 웹 에디터의 Test 탭에서 값의 흐름을 시각적으로 확인할 수 있습니다. XSLT는 로컬 도구(Oxygen, IntelliJ)에 의존하는 편이 낫습니다.
  • 재사용성: XSLT는 <xsl:import>, <xsl:include>로 라이브러리 조합이 자연스럽습니다. Message Mapping은 함수 라이브러리(User-Defined Function 그룹)로 재사용을 지원하지만 iFlow 간 공유가 제한적입니다.
  • 수정 이력 추적: XSLT는 텍스트 파일이라 Git diff가 명확합니다. Message Mapping은 내부 XML(.mmap)이 병합 충돌 시 사람이 읽기 어렵습니다.

1단계 — 최소 예제로 감 잡기

가장 단순한 PurchaseOrderSalesOrder 변환을 두 방식으로 만들어 봅니다. 소스 페이로드는 다음과 같은 형태라고 가정합니다.

<PurchaseOrder>
  <Header>
    <PONumber>PO-20260714-001</PONumber>
    <BuyerId>BUY-4471</BuyerId>
    <CreatedAt>2026-07-14T09:32:00Z</CreatedAt>
  </Header>
  <Items>
    <Item>
      <Sku>SKU-A100</Sku>
      <Qty>3</Qty>
      <UnitPrice currency="EUR">12.50</UnitPrice>
    </Item>
    <Item>
      <Sku>SKU-B220</Sku>
      <Qty>1</Qty>
      <UnitPrice currency="EUR">89.00</UnitPrice>
    </Item>
  </Items>
</PurchaseOrder>

XSLT 버전은 다음처럼 작성할 수 있습니다.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="xml" indent="yes" encoding="UTF-8"/>

  <xsl:template match="/PurchaseOrder">
    <SalesOrder>
      <OrderId><xsl:value-of select="Header/PONumber"/></OrderId>
      <CustomerRef><xsl:value-of select="Header/BuyerId"/></CustomerRef>
      <IssuedAt><xsl:value-of select="Header/CreatedAt"/></IssuedAt>
      <Lines>
        <xsl:for-each select="Items/Item">
          <Line>
            <Position><xsl:value-of select="position() * 10"/></Position>
            <Material><xsl:value-of select="Sku"/></Material>
            <Quantity><xsl:value-of select="Qty"/></Quantity>
            <NetAmount currency="{UnitPrice/@currency}">
              <xsl:value-of select="format-number(Qty * UnitPrice, '0.00')"/>
            </NetAmount>
          </Line>
        </xsl:for-each>
      </Lines>
    </SalesOrder>
  </xsl:template>

</xsl:stylesheet>

동일한 로직을 Message Mapping으로 만들면 다음처럼 정리됩니다. UI 스크린샷을 텍스트로 표현하면 이렇습니다.

  • Header/PONumberOrderId (직결)
  • Header/BuyerIdCustomerRef (직결)
  • Items/ItemLines/Line (컨텍스트 매핑)
  • Position: 표준 함수 index → 산술 함수 multiply(10)
  • NetAmount: QtyUnitPricemultiply 함수로 결합 → formatNum('0.00')
  • NetAmount/@currency: UnitPrice/@currency에서 직결 (Attribute 매핑)

이 단계에서는 두 방식 모두 30분 이내에 완성되며, 결과 XML은 동일합니다. 짧은 필드 매핑이 지배적이라면 Message Mapping이 시각적으로 이해하기 좋고, 온보딩 비용이 낮은 편입니다.

2단계 — 실무 시나리오와 예외 처리

실무에서는 조건부 매핑기본값, 에러 시 대체 동작이 반드시 등장합니다. 요구사항이 다음과 같이 확장됐다고 가정합니다.

  1. 1000 EUR 이상 총액 라인은 <Priority>HIGH</Priority>를 붙일 것
  2. UnitPrice가 없거나 0이면 해당 라인은 스킵
  3. Header에 BuyerId가 없으면 UNKNOWN으로 채우고 경고 로깅

XSLT는 xsl:choose와 XPath 조건으로 다음처럼 정리됩니다.

<xsl:template match="/PurchaseOrder">
  <SalesOrder>
    <OrderId><xsl:value-of select="Header/PONumber"/></OrderId>
    <CustomerRef>
      <xsl:choose>
        <xsl:when test="normalize-space(Header/BuyerId) != ''">
          <xsl:value-of select="Header/BuyerId"/>
        </xsl:when>
        <xsl:otherwise>UNKNOWN</xsl:otherwise>
      </xsl:choose>
    </CustomerRef>
    <Lines>
      <xsl:for-each select="Items/Item[number(UnitPrice) > 0]">
        <xsl:variable name="net" select="Qty * UnitPrice"/>
        <Line>
          <Position><xsl:value-of select="position() * 10"/></Position>
          <Material><xsl:value-of select="Sku"/></Material>
          <Quantity><xsl:value-of select="Qty"/></Quantity>
          <NetAmount currency="{UnitPrice/@currency}">
            <xsl:value-of select="format-number($net, '0.00')"/>
          </NetAmount>
          <xsl:if test="$net >= 1000">
            <Priority>HIGH</Priority>
          </xsl:if>
        </Line>
      </xsl:for-each>
    </Lines>
  </SalesOrder>
</xsl:template>

Message Mapping에서는 다음 함수 조합이 필요합니다.

  • CustomerRef: ifWithoutElse + exists 함수 조합, 또는 mapWithDefault('UNKNOWN')
  • 0 이하 라인 스킵: createIf 함수를 라인의 부모 노드에 걸어 UnitPrice > 0일 때만 타깃 라인 생성
  • Priority: greaterEquals 결과가 참일 때만 createIfPriority 노드 생성 후 상수 HIGH 연결

이 단계부터 두 방식의 차이가 느껴집니다. XSLT는 조건 하나가 XPath 한 줄로 끝나지만, Message Mapping은 함수 노드를 마우스로 여러 개 연결해야 합니다. 다만 Message Mapping은 조건이 잘못 걸렸을 때 Test 탭에서 어느 노드의 값이 SUPPRESS되었는지 시각적으로 즉시 보입니다. 이는 신입 개발자 인수인계 시 유지보수 효율에서 큰 장점이 됩니다.

로깅은 두 방식 모두 iFlow의 Groovy Script 스텝을 앞뒤에 배치해 처리하는 것이 일반적입니다. XSLT 내부에서는 xsl:message를 쓸 수 있지만 런타임 로그로 안정적으로 흘러간다는 보장이 어렵기 때문에, MessageProcessingLog에 남기는 스크립트로 대체하는 것이 권장됩니다.

3단계 — 프로덕션 관점의 성능·보안 튜닝

500개 라인아이템, 4MB 페이로드로 두 방식을 각각 100회 실행해 얻은 평균값은 다음과 같은 경향을 보였습니다(테넌트 상태에 따라 편차 있음).

지표Message MappingXSLT (Saxon-HE)
평균 처리 시간약 780 ms약 620 ms
피크 힙 사용량약 65 MB약 110 MB
구현 시간(주니어 기준)1.5 시간3 시간

일반적으로 대용량·복잡 조건에서는 XSLT가 실행 시간이 짧고, 반대로 메모리는 더 많이 씁니다. 프로덕션에서 안정적으로 돌리려면 다음을 챙기는 것이 권장됩니다.

<!-- 스트리밍 처리에 유리한 XSLT 스타일 -->
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <!-- 텍스트 노드의 불필요한 공백 제거로 트리 크기 감소 -->
  <xsl:strip-space elements="*"/>
  <xsl:output method="xml" indent="no"/>

  <!-- Identity 템플릿 대신 명시적 템플릿만 사용 → 불필요 순회 방지 -->
  <xsl:template match="/PurchaseOrder">
    <SalesOrder>
      <xsl:apply-templates select="Header"/>
      <Lines>
        <xsl:apply-templates select="Items/Item[number(UnitPrice) > 0]"/>
      </Lines>
    </SalesOrder>
  </xsl:template>

  <xsl:template match="Item">
    <Line>
      <Position><xsl:value-of select="position() * 10"/></Position>
      <Material><xsl:value-of select="Sku"/></Material>
      <NetAmount><xsl:value-of select="format-number(Qty * UnitPrice, '0.00')"/></NetAmount>
    </Line>
  </xsl:template>

</xsl:stylesheet>

Message Mapping에서 챙길 프로덕션 포인트는 조금 다릅니다.

  • 큰 소스 노드의 Context를 필요 이상으로 잘게 쪼개지 말 것. 잘못된 splitByValue 사용은 Queue 크기를 폭발시킵니다.
  • User-Defined Function(UDF)에서 Global Container를 남용하면 상태 공유로 인해 병렬 처리 시 오류가 발생합니다. UDF는 가급적 순수 함수로 유지하세요.
  • 매핑 리소스는 iFlow 배포 시 컴파일되므로, 대형 매핑은 첫 호출 시 지연이 있습니다. 필요하다면 워밍업 스케줄러를 두는 것이 권장됩니다.

보안 관점에서 XSLT는 외부 엔티티(XXE) 취약점의 표적이 될 수 있어서 iFlow 런타임은 기본적으로 DOCTYPE과 외부 엔티티 로드를 차단합니다. 다만 커스텀 XSLT에서 document()로 임의 URL을 부르는 코드는 지양하는 것이 권장됩니다. Message Mapping도 UDF에서 임의 HTTP 호출을 하는 것은 트랜잭션 원자성을 해치므로 피하는 게 좋습니다.

자주 부딪히는 함정과 FAQ

Q1. Message Mapping에서 타깃 노드가 아예 생성되지 않아요.
소스 컨텍스트에서 값이 SUPPRESS로 전달됐거나, createIf 조건이 항상 false로 평가되는 경우가 대부분입니다. 웹 에디터의 Display Queue 기능으로 각 노드로 흘러가는 큐를 열어 확인하는 것이 지름길입니다.

Q2. XSLT에서 네임스페이스가 꼬여서 결과 XML의 타깃 시스템이 파싱 실패합니다.
소스 XML의 기본 네임스페이스가 존재할 때 XPath에서 /PurchaseOrder가 매칭되지 않는 것이 가장 흔합니다. xmlns:po="..."로 접두어를 붙이고 /po:PurchaseOrder로 접근해야 합니다. 결과 문서 쪽은 xsl:elementnamespace 속성으로 명시적으로 만들어 주는 것이 안전합니다.

Q3. 두 방식을 섞어 써도 되나요?
네, 실무에서는 오히려 자연스럽습니다. 예를 들어 앞단에서 XSLT로 노이즈 필드를 제거하고 정규화한 뒤, 뒷단에서 Message Mapping으로 비즈니스 매핑을 시각적으로 문서화하는 조합이 자주 쓰입니다. 다만 스텝이 늘어나면 Trace 저장량이 증가하니 프로덕션에서는 Trace 레벨을 Info로 두는 것이 권장됩니다.

Q4. 매핑에서 갑자기 소수점이 0.30000000000000004처럼 튑니다.
XSLT의 산술 연산은 IEEE 754 double 기반이라 필연적으로 발생합니다. format-number로 자릿수를 고정하거나, Message Mapping에서는 formatNum 함수를 마지막에 반드시 걸어주세요.

이어서 확장할 수 있는 영역

이 글의 비교 결과를 바탕으로, 다음 영역을 이어서 파보면 실전 역량이 빠르게 성장합니다. Groovy Script를 이용한 메시지 헤더 조작, JSON to XML Converter와 XSLT의 조합, Integration Advisor를 활용한 매핑 가이드라인 생성, 그리고 GitHub과 Cloud Transport Management를 연결한 CI/CD 파이프라인. 특히 XSLT는 텍스트 자산이라 CTMS 이송 과정에서 코드 리뷰 프로세스를 붙이기 좋습니다.

더 깊이 파고들 수 있는 자료

댓글 0

아직 댓글이 없습니다.