RAP

S/4 Cloud에서 막히는 것 3가지 — ABAP Cloud 그만 #shorts #SAP #RAP

▶ YouTube에서 보기

이 글에서 다루는 내용과 체크포인트

온프레미스 ABAP에서 잘 돌아가던 코드를 S/4HANA Cloud Public Edition이나 SAP BTP ABAP Environment(Steampunk)로 옮기려는 순간, 개발자들은 "Statement is not allowed in ABAP Cloud" 라는 붉은 오류 메시지를 마주하게 됩니다. 이 글은 클라우드 계층(ABAP Cloud Development Model)에서 왜 특정 API가 봉인되었는지, Released 여부는 어떻게 판단하는지, 그리고 실제로 가장 자주 부딪히는 세 가지 사례(RFC_READ_TABLE, SUBMIT, sy-ucomm/List processing)를 어떤 대안 패턴으로 우회하는지를 실전 코드와 함께 정리합니다.

  • ABAP Cloud Development Model과 Clean Core 전략의 배경 이해
  • Released API의 의미와 C1/C2 Release Contract 구분
  • ADT의 Release Info 뷰와 ATC 체크로 사전 검증하는 방법
  • Not Released API 3종을 Released 대안으로 리팩터링하는 패턴

이 글을 읽기 전에 준비되어 있어야 할 것

ABAP OO 기본 문법(클래스, 인터페이스, 예외처리), CDS View 및 RAP Behavior Definition에 대한 초급 수준의 이해, ADT(ABAP Development Tools for Eclipse) 사용 경험이 있으면 좋습니다. Fiori Elements나 OData v4의 개념을 접해봤다면 3단계 예제를 더 쉽게 따라올 수 있습니다.

대상 환경과 버전

이 글의 코드와 스크린 설명은 아래 환경을 기준으로 작성됐습니다. 온프레미스 S/4HANA(Private Cloud/On-Premise)에서도 ABAP Cloud Development Model을 옵트인 방식으로 사용할 수 있으며, 이 경우 릴리즈 계약 검사가 동일하게 적용됩니다.

  • SAP S/4HANA Cloud Public Edition 2508 (ABAP Cloud만 허용, 개발자 확장은 Developer Extensibility 전용)
  • SAP BTP ABAP Environment (Steampunk) 2025 릴리즈
  • SAP S/4HANA 2023 FPS02 (온프레미스, ABAP Cloud 옵트인)
  • ADT 3.44 이상, Eclipse 2024-09
  • ATC 프로파일: SAP_CP_READINESS_REMOTE 또는 ABAP_CLOUD_READINESS

패키지를 만들 때 Package Type을 Development로 두고 Software Component를 ZLOCAL 또는 별도 SC로 지정한 뒤, ABAP Language Version을 ABAP for Cloud Development로 설정해야 이 글의 제약이 실제로 활성화됩니다.

왜 클라우드 계층은 API를 봉인하는가

SAP가 ABAP Cloud에서 API를 대량으로 잠근 근본적인 이유는 Clean Core 전략 때문입니다. 온프레미스 시대에는 고객이 SAP 표준 테이블에 직접 SELECT를 날리고, 표준 리포트에 SUBMIT을 걸어 결과를 낚아채는 방식이 흔했습니다. 문제는 이렇게 짜인 커스텀 코드가 SAP의 반기 업그레이드를 사실상 불가능하게 만든다는 점이었습니다.

클라우드 계층에서는 SAP가 표준 오브젝트의 시그니처와 동작을 자유롭게 바꿀 수 있어야 하고, 대신 Released Object라는 명시적 계약을 통해서만 고객 코드와 접점을 노출합니다. 비유하자면 온프레미스는 "집 내부 배관을 직접 뜯어 고칠 수 있는 자가 주택"이고, ABAP Cloud는 "관리사무소가 공식 밸브(=Released API)만 제공하는 아파트"입니다.

Release Contract는 세 가지 계층으로 나뉩니다.

  • C0 (Use System-Internally): SAP 내부용, 고객은 사용 불가
  • C1 (Use in Cloud Development): ABAP Cloud 개발자가 사용 가능. 이 글에서 목표로 하는 등급
  • C2 (Use in Key User Apps): 커스텀 필드/로직 앱 등 Low-code용

Released 오브젝트는 SAP가 하위 호환성을 관리하는 대상으로 취급되고, Deprecation 시에도 사전 공지 및 마이그레이션 경로가 제공되는 것이 일반적입니다. 반면 Not Released 오브젝트는 언제든 시그니처가 바뀌거나 사라질 수 있어서, 클라우드 계층 컴파일러가 아예 문법 단계에서 차단합니다.

ADT에서 특정 클래스 위에 커서를 두고 우클릭 → Show Release State를 실행하면 릴리즈 등급과 유효 릴리즈 범위가 나타납니다. CDS View의 경우 어노테이션 @API.state: 'released'와 @EndUserText.label을 함께 확인하면 됩니다.

실전 리팩터링 3단계

1단계 — RFC_READ_TABLE 대신 Released CDS View + Service Binding

온프레미스 시절 외부 시스템에서 SAP 테이블을 읽어갈 때 십중팔구는 RFC_READ_TABLE을 호출했습니다. 이 함수 모듈은 ABAP Cloud에서 Not Released 상태이며, 애초에 CALL FUNCTION 자체가 릴리즈되지 않은 대상은 호출할 수 없습니다.

" 온프레미스 스타일 - ABAP Cloud에서 컴파일 오류
DATA: lt_data TYPE STANDARD TABLE OF tab512,
      lt_fields TYPE STANDARD TABLE OF rfc_db_fld.

CALL FUNCTION 'RFC_READ_TABLE'
  EXPORTING
    query_table = 'VBAK'
    delimiter   = '|'
  TABLES
    fields      = lt_fields
    data        = lt_data.
" -> "Function module RFC_READ_TABLE is not released for ABAP for Cloud Development"

클라우드 계층에서는 데이터 노출을 CDS View + Service Definition + Service Binding의 3단 조합으로 대체합니다. 아래는 판매 오더 헤더 일부를 외부 시스템에 OData v4로 공개하는 예시입니다.

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Header Export'
@Metadata.allowExtensions: true
define view entity ZC_SalesOrderExport
  as select from I_SalesOrder
{
  key SalesOrder            as OrderId,
      SalesOrderType        as OrderType,
      SalesOrganization     as SalesOrg,
      SoldToParty           as Customer,
      TotalNetAmount        as NetAmount,
      TransactionCurrency   as Currency,
      CreationDate          as CreatedOn
}
where
  CreationDate >= $session.system_date - 30;
@EndUserText.label: 'Sales Order Export Service'
define service ZUI_SalesOrderExport {
  expose ZC_SalesOrderExport as SalesOrderExport;
}

Service Binding을 OData V4 - Web API 타입으로 만들면, 외부 시스템은 통신 시나리오 + Communication Arrangement를 통해 안전하게 엔드포인트를 호출합니다. RFC의 무제한 접근과 달리 인증, 인가, 필드 단위 노출까지 모두 명시적으로 제어됩니다.

2단계 — SUBMIT 리포트 대신 RAP Action + 애플리케이션 로그

커스텀 프로그램에서 표준 리포트에 SUBMIT ... VIA SELECTION-SCREEN을 걸어 처리하는 관행도 완전히 막힙니다. SUBMIT 문 자체가 클라우드 계층에서 허용되지 않습니다. 대안은 RAP Action으로 로직을 노출하고, 결과는 Released 애플리케이션 로그 API로 남기는 것입니다.

managed implementation in class zbp_i_shipmentplan unique;
strict ( 2 );

define behavior for ZI_ShipmentPlan alias ShipmentPlan
persistent table zshipment_plan
lock master
authorization master ( instance )
{
  create; update; delete;
  action ( features : instance ) recalculateEta result [1] $self;
}
CLASS lhc_shipmentplan IMPLEMENTATION.
  METHOD recalculateEta.
    DATA(lo_log) = cl_bali_log=>create_with_header(
                     cl_bali_header_setter=>create(
                       object    = 'ZSHIPMENT'
                       subobject = 'ETA_CALC'
                       external_id = |ETA-{ sy-datum }| ) ).

    LOOP AT keys INTO DATA(ls_key).
      TRY.
          DATA(lv_new_eta) = zcl_eta_calculator=>compute(
                              shipment_id = ls_key-ShipmentId ).

          MODIFY ENTITIES OF ZI_ShipmentPlan IN LOCAL MODE
            ENTITY ShipmentPlan
              UPDATE FIELDS ( EstimatedArrival )
              WITH VALUE #( ( %tky             = ls_key-%tky
                              EstimatedArrival = lv_new_eta ) )
            REPORTED DATA(ls_reported).

          lo_log->add_item(
            cl_bali_free_text_setter=>create(
              severity = if_bali_constants=>c_severity_status
              text     = |ETA updated for { ls_key-ShipmentId }| ) ).

        CATCH zcx_eta_error INTO DATA(lx_err).
          APPEND VALUE #( %tky = ls_key-%tky
                          %msg = new_message_with_text(
                                   severity = if_abap_behv_message=>severity-error
                                   text     = lx_err->get_text( ) ) )
                 TO reported-shipmentplan.
      ENDTRY.
    ENDLOOP.
    cl_bali_log_db=>get_instance( )->save_log( log = lo_log ).
    result = VALUE #( FOR key IN keys ( %tky = key-%tky %param = CORRESPONDING #( key ) ) ).
  ENDMETHOD.
ENDCLASS.

이 방식의 이점은 세 가지입니다. 첫째, Fiori Elements가 액션 버튼을 자동 렌더링하므로 별도 UI 코드가 필요 없습니다. 둘째, cl_bali_* 로그 API는 SLG1과 동일한 저장소를 쓰지만 클라우드 계약 하에서 안정적입니다. 셋째, 트랜잭션 컨트롤과 락킹이 RAP 프레임워크에 위임되므로 COMMIT WORK를 직접 호출할 필요가 없습니다.

3단계 — sy-ucomm/List Processing 대신 이벤트 기반 아키텍처

Dynpro 시절에는 sy-ucomm과 AT USER-COMMAND로 사용자 인터랙션을 잡아냈고, WRITE로 리스트를 그렸습니다. 클라우드 계층은 sy-ucomm, WRITE, CALL SCREEN, LEAVE TO LIST-PROCESSING을 전부 차단합니다. UI는 Fiori Elements 또는 UI5 프리스타일이 담당하고, ABAP은 이벤트 소비자 또는 도메인 서비스로만 역할이 축소됩니다.

METHOD adjustPriorityOnRegionChange.
  READ ENTITIES OF ZI_ShipmentPlan IN LOCAL MODE
    ENTITY ShipmentPlan
      FIELDS ( DestinationRegion Priority )
      WITH CORRESPONDING #( keys )
    RESULT DATA(lt_current).

  LOOP AT lt_current INTO DATA(ls_current).
    DATA(lv_new_priority) = SWITCH #(
      ls_current-DestinationRegion
        WHEN 'EU'  THEN 'HIGH'
        WHEN 'NA'  THEN 'HIGH'
        WHEN 'APJ' THEN 'MEDIUM'
        ELSE            'LOW' ).

    IF ls_current-Priority <> lv_new_priority.
      MODIFY ENTITIES OF ZI_ShipmentPlan IN LOCAL MODE
        ENTITY ShipmentPlan
          UPDATE FIELDS ( Priority )
          WITH VALUE #( ( %tky     = ls_current-%tky
                          Priority = lv_new_priority ) ).
    ENDIF.
  ENDLOOP.
ENDMETHOD.

단위 테스트는 CL_ABAP_BEHV_TEST_ENVIRONMENT를 이용해 프레임워크 의존성을 격리시키고, CDS 레이어는 CL_CDS_TEST_ENVIRONMENT로 더블링합니다.

보안 측면에서는 CDS의 @AccessControl.authorizationCheck: #CHECK와 별도 DCL(Data Control Language) 뷰를 조합해 필드/인스턴스 레벨 인가를 선언적으로 처리합니다. sy-uname 대신 cl_abap_context_info=>get_user_technical_name()을 사용하는 습관도 ATC 경고를 줄이는 데 도움이 됩니다.

자주 걸리는 함정과 FAQ

Q1. ATC는 통과했는데 배포 시점에 릴리즈 상태가 바뀌어 실패했습니다.
Released 상태는 시간에 따라 변경될 수 있습니다. CI 파이프라인에서 ATC를 매 야간 재수행하고, Custom Code Migration 앱의 리포지토리 스냅샷을 주기적으로 갱신하는 것이 일반적입니다.

Q2. Where-Used를 눌렀는데 SAP 표준 오브젝트에서만 참조가 보입니다.
ADT에서 Where-Used는 자신의 소프트웨어 컴포넌트 범위로 제한됩니다. 릴리즈된 대안을 찾을 때는 오브젝트 이름 위에서 Open Release Info를 열어 "Successor" 또는 "Replaced by" 힌트를 확인하고, 없으면 SAP API Hub에서 카탈로그를 탐색하는 것이 빠릅니다.

Q3. Not Released 클래스를 반드시 써야만 하는 요구사항이라면?
S/4HANA Cloud Private Edition이나 On-Premise에서는 Tier 2 (Wrapper) 접근이 허용됩니다. 별도 소프트웨어 컴포넌트(Classic ABAP)에 얇은 래퍼 클래스를 만들어 Not Released 로직을 감싼 뒤, Released 인터페이스를 Cloud 계층에 노출하는 패턴입니다. Public Edition에서는 이 우회가 불가능하므로 SAP에 Influence Request를 등록하거나 파트너 확장 옵션을 검토합니다.

또 한 가지 흔한 실수는 cl_abap_context_info=>get_user_technical_name() 대신 sy-uname을 그대로 쓰는 것입니다. sy-uname은 클라우드 계층에서 여전히 접근은 되지만 권장되지 않으며, ATC 프로파일에 따라 경고가 발생합니다. 컨텍스트 클래스로 통일하는 것이 안전합니다.

이어서 파고들면 좋은 주제

이 글의 세 가지 대안 패턴을 익혔다면, 다음으로 RAP의 Unmanaged 시나리오(레거시 테이블 연동), Draft-enabled 시나리오(임시 저장 지원), 그리고 이벤트 브로커를 활용한 Business Events 발행/구독 구조를 살펴보길 권합니다. 또한 Developer Extensibility와 Side-by-Side Extensibility(BTP ABAP Environment 별도 인스턴스)의 결정 기준을 정리해두면 아키텍처 회의에서 실제로 쓸모가 큽니다.

더 읽어보면 좋은 원문

  • SAP Help Portal — ABAP Cloud Development Model 개요
  • SAP Help Portal — SAP BTP ABAP Environment
  • SAP Help Portal — Released APIs and Release Contracts
  • SAP Help Portal — ATC와 ABAP Cloud Readiness 체크
  • SAP Community — Clean Core 전략과 3-Tier 확장 모델
  • SAP API Business Hub — Released API 카탈로그
  • SAP Developers — BTP ABAP Environment 시작하기

댓글 0

아직 댓글이 없습니다.