SAP BTP Integration Suite 실전 가이드 — iFlow 설계부터 Error Handling까지

Moderator · 조회 4
# SAP BTP Integration Suite 실전 가이드 — iFlow 설계부터 Error Handling까지

1. 개요 및 학습 목표

SAP BTP Integration Suite는 클라우드 네이티브 통합 플랫폼(iPaaS)으로, 이질적인 시스템 간의 데이터 흐름을 표준화된 방식으로 연결합니다. 그 중심에는 iFlow(Integration Flow)가 있으며, 이는 BPMN 2.0 기반 그래픽 모델링으로 메시지 라우팅, 변환, 매핑, 예외 처리를 정의하는 단위입니다. 본 가이드에서는 Cloud Integration(CPI) 기준으로 iFlow의 설계 원칙과 운영 단계의 Error Handling 전략을 다룹니다.

학습 체크리스트
  • iFlow의 BPMN 기반 구조와 Sender/Receiver 개념을 이해한다
  • Content Modifier와 Groovy Script로 메시지를 가공할 수 있다
  • SFTP/OData/HTTP Adapter의 핵심 설정 항목을 파악한다
  • Exception Subprocess로 에러 시나리오를 분기 처리한다
  • JMS/DLQ 기반 재처리 패턴을 적용한다
  • MPL(Message Processing Log) 기반 모니터링 전략을 수립한다

2. Integration Suite와 iFlow 기본 구성요소

SAP BTP Integration Suite는 Cloud Integration, API Management, Open Connectors, Trading Partner Management, Integration Advisor 등 여러 캐퍼빌리티(capability)를 묶은 통합 슈트입니다. 이 중 Cloud Integration(CPI)이 iFlow를 다루는 핵심 모듈이며, 일반적으로 SAP S/4HANA, SuccessFactors, Ariba, 그리고 비-SAP(Salesforce, AWS, Azure 등)을 연결하는 데 사용됩니다.

2.1 iFlow의 BPMN 구성요소

iFlow는 다음 4가지 핵심 빌딩블록으로 구성됩니다.

비유하자면, iFlow는 공항의 수하물 처리 컨베이어와 같습니다. Sender는 체크인 카운터, Channel은 컨베이어 벨트, Integration Process는 분류·검사 라인, Receiver는 항공기 화물칸입니다. 각 단계에서 분기·합류·검사·재시도가 일어나는 것이 BPMN 모델로 그려집니다.

3. 환경 / 버전 / 준비물

Cloud Integration 런타임은 일반적으로 Apache Camel 기반으로 구성되며, 따라서 Camel의 Exchange 모델(Header/Property/Body)을 이해하면 디버깅이 수월합니다.

4. 핵심 개념 — Content Modifier와 Groovy Script

iFlow의 거의 모든 메시지 가공은 Content ModifierGroovy Script로 처리됩니다. 두 단계의 역할을 명확히 구분하는 것이 설계의 출발점입니다.

4.1 Content Modifier

Content Modifier는 코드 없이 Header, Property, Body를 선언적으로 설정하는 단계입니다. 다음과 같은 작업에 적합합니다.

4.2 Groovy Script

Groovy Script는 복잡한 로직, 조건부 처리, 동적 매핑이 필요할 때 사용합니다. 다음 시그니처가 진입점입니다.

import com.sap.gateway.ip.core.customdev.util.Message
import java.util.HashMap

def Message processData(Message message) {
    def body = message.getBody(String)
    def headers = message.getHeaders()
    def properties = message.getProperties()

    // 비즈니스 로직
    message.setBody(body.toUpperCase())
    message.setHeader("X-Processed-At", new Date().toString())
    return message
}

설계 원칙: "단순 매핑은 Content Modifier, 분기/예외/외부 호출은 Groovy"로 책임을 분리합니다. Groovy 스크립트가 200라인을 넘어가면 Sub-Process로 분리하거나 Local Integration Process로 모듈화하는 것이 권장됩니다.

5. 실전 코드 3단계

5.1 1단계: 기본 예제 — SFTP 파일을 OData로 전송

SFTP 폴더에 떨어진 CSV 파일을 읽어 S/4HANA OData 서비스로 등록하는 시나리오입니다.

<!-- iFlow XML 일부: SFTP Sender Channel -->
<bean id="sftpSender">
    <property name="address" value="sftp.example.com"/>
    <property name="port" value="22"/>
    <property name="directory" value="/in/orders"/>
    <property name="fileName" value="*.csv"/>
    <property name="pollInterval" value="60"/>
    <property name="postProcessing" value="Move"/>
    <property name="archiveDirectory" value="/in/orders/archive"/>
    <property name="authentication" value="PublicKey"/>
</bean>

CSV → JSON 변환 후 OData V4로 POST하는 Groovy Script:

import com.sap.gateway.ip.core.customdev.util.Message
import groovy.json.JsonBuilder

def Message processData(Message message) {
    def csv = message.getBody(String)
    def lines = csv.split("\n")
    def headers = lines[0].split(",")
    def rows = lines[1..-1].collect { line ->
        def cols = line.split(",")
        [headers, cols].transpose().collectEntries()
    }
    message.setBody(new JsonBuilder(rows).toString())
    message.setHeader("Content-Type", "application/json")
    return message
}

5.2 2단계: 실무 시나리오 — Exception Subprocess와 로깅

실무에서는 OData 호출이 401/500을 반환할 수 있고, SFTP 연결이 끊길 수도 있습니다. Exception Subprocess는 BPMN 풀 내에 별도의 트랙으로 그려지며, 메인 프로세스의 어느 단계에서든 예외 발생 시 자동 진입합니다.

import com.sap.gateway.ip.core.customdev.util.Message

def Message processData(Message message) {
    def ex = message.getProperty("CamelExceptionCaught")
    def httpStatus = message.getHeader("CamelHttpResponseCode", String) ?: "N/A"
    def msgId = message.getHeader("SAP_MessageProcessingLogID", String)

    def errorPayload = """
    {
      "messageId": "${msgId}",
      "httpStatus": "${httpStatus}",
      "errorClass": "${ex?.getClass()?.getName()}",
      "errorMessage": "${ex?.getMessage()?.replaceAll('"','\\\\"')}",
      "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")}"
    }
    """

    // MPL Custom Header에 기록 (모니터링에서 검색 가능)
    def messageLog = messageLogFactoryBean.getMessageLog(message)
    if (messageLog != null) {
        messageLog.addCustomHeaderProperty("ErrorCategory",
            httpStatus.startsWith("4") ? "ClientError" : "ServerError")
        messageLog.addAttachmentAsString("ErrorDetails", errorPayload, "application/json")
    }

    message.setBody(errorPayload)
    return message
}

이렇게 작성한 Exception Subprocess는 Mail Adapter, JMS Adapter, 또는 Alert API로 라우팅하여 운영자에게 통지하거나 DLQ에 적재할 수 있습니다.

5.3 3단계: 프로덕션 — 멱등성, JMS 재처리, 보안

프로덕션 등급의 iFlow는 다음 3요소를 갖추어야 합니다.

(1) 멱등성(Idempotency) 처리 — 동일 메시지 중복 수신 시 동일한 결과를 보장합니다.

import com.sap.gateway.ip.core.customdev.util.Message
import com.sap.it.api.ITApiFactory
import com.sap.it.api.idempotency.IdempotentRepositoryService

def Message processData(Message message) {
    def repo = ITApiFactory.getService(IdempotentRepositoryService, null)
    def msgId = message.getHeader("X-Business-Key", String)

    if (!repo.addIfNotExists("ORDER_PROCESS", msgId, 86400)) {
        message.setProperty("DUPLICATE", true)
        message.setBody('{"status":"duplicate","ignored":true}')
    } else {
        message.setProperty("DUPLICATE", false)
    }
    return message
}

(2) JMS Queue 기반 비동기 재처리 — 처리 실패 메시지를 JMS 큐에 적재하고, 재처리 iFlow가 polling하는 패턴입니다. 이는 일반적으로 가장 안정적인 재처리 패턴으로 권장됩니다.

# JMS Receiver 채널 설정
queueName: orders.dlq
retentionThreshold: 30   # days
expirationPeriod: 90     # days
encryptStoredMessage: true
transactionHandling: Required

(3) 보안 강화 — Credential은 Security Material 저장소를 사용하고, OAuth 2.0 토큰은 OAuth2 Credentials로 관리합니다. 절대 Groovy 스크립트에 평문 비밀번호를 하드코딩하지 않습니다.

import com.sap.it.api.ITApiFactory
import com.sap.it.api.securestore.SecureStoreService

def Message processData(Message message) {
    def secureStore = ITApiFactory.getService(SecureStoreService, null)
    def cred = secureStore.getUserCredential("S4_BACKEND_USER")
    def basicAuth = "Basic " +
        "${cred.getUsername()}:${new String(cred.getPassword())}".bytes.encodeBase64()
    message.setHeader("Authorization", basicAuth.toString())
    return message
}

6. 흔한 실수 / 트러블슈팅

FAQ 1. "Message Processing Log에 Body가 안 보여요"

기본적으로 MPL은 Header/Property만 저장하고 Body는 저장하지 않습니다. Log Level을 Trace로 변경하거나 Groovy에서 messageLog.addAttachmentAsString()으로 명시적으로 첨부해야 합니다. Trace는 1시간 후 자동 비활성화되므로 임시 디버깅용으로만 사용합니다.

FAQ 2. "OData 호출 시 timeout이 자주 발생합니다"

OData Receiver Adapter의 기본 timeout은 일반적으로 짧게 설정되어 있습니다. Timeout (in ms)를 60000~120000 사이로 조정하고, 대량 데이터는 $batch를 사용하거나 Splitter + Parallel Processing으로 분할 호출하는 것이 권장됩니다. 또한 OData 서비스 자체의 페이지 사이즈($top, $skip)를 명시해 응답 크기를 제어하세요.

FAQ 3. "Exception Subprocess가 호출되지 않아요"

흔한 원인 3가지:

FAQ 4. "동일 메시지가 두 번 처리됩니다"

SFTP에서 파일을 읽고 처리 도중 노드가 재시작되면 같은 파일이 다시 polling될 수 있습니다. Post-Processing = Move + Idempotent Repository 조합이 권장됩니다. HTTP 트리거의 경우 클라이언트의 X-Correlation-ID 같은 비즈니스 키 기반 중복 검사가 필요합니다.

7. 다음 단계 / 관련 주제

8. 참고 자료

본 가이드는 2025년 기준 SAP BTP Integration Suite의 일반적 운영 패턴을 기반으로 작성되었으며, 실제 구현 시 사용 중인 에디션과 런타임 버전에 따라 일부 UI/옵션이 다를 수 있습니다. 프로덕션 적용 전 반드시 자체 환경에서 검증하시기 바랍니다.