RAP

managed vs unmanaged — 선택 기준 딱 2가지 #shorts #SAP #RAP

개요 및 이 글에서 다루는 내용

SAP RAP(ABAP RESTful Application Programming Model)에서 Behavior Definition(BDEF)을 작성할 때 가장 먼저 마주치는 갈림길이 바로 managedunmanaged 시나리오의 선택입니다. 잘못 고르면 처음부터 다시 만들거나, 불필요한 ABAP 코드를 수백 줄 작성하게 됩니다. 이 글은 두 시나리오의 동작 원리와 선택 기준을 실무 SalesOrder 예제로 설명합니다.

  • managed / unmanaged / additional save 시나리오 차이 이해
  • BDEF 키워드(implementation in class ... unique)의 의미 파악
  • 레거시 함수 모듈 재활용 시 unmanaged 선택 근거
  • 신규 테이블 기반 트랜잭션에서 managed 선택 근거
  • 실전 BDEF 코드 두 가지 비교 작성

읽기 전에 알아두면 좋은 것

이 글은 ABAP CDS View, RAP의 기본 구조(Data Model → Behavior → Service Definition → Service Binding), Eclipse ADT 환경에 익숙한 독자를 가정합니다. 또한 OData v4 기반 트랜잭션 처리 개념과 ABAP OO(전역 클래스, Interface)에 대한 기초가 있으면 unmanaged 시나리오의 핸들러 클래스 작성 부분이 훨씬 수월합니다.

환경과 버전 정보

RAP의 managed/unmanaged 시나리오는 일반적으로 다음 환경에서 동작합니다. 버전에 따라 사용 가능한 키워드(strict(2), draft 등)가 다르므로 본인 환경을 먼저 확인해야 합니다.

  • ABAP Platform: SAP S/4HANA 2022 이상 권장, ABAP Cloud(SAP BTP, ABAP Environment) 1908 이상
  • 개발 도구: ADT(ABAP Development Tools for Eclipse) 최신 버전
  • RAP BO 종류: Managed, Unmanaged, Managed with unmanaged save (additional save)
  • OData 버전: 일반적으로 OData v4 (UI5/Fiori Elements 권장)
  • 테스트: SAP Gateway Client 또는 RAP Generator + Preview App

S/4HANA On-Premise에서는 strict mode 버전(0, 1, 2)을 환경에 맞게 선언해야 하며, ABAP Cloud에서는 strict(2)가 사실상 표준입니다.

managed와 unmanaged, 무엇이 다른가

RAP의 BDEF는 비즈니스 객체(BO)의 트랜잭션 동작을 선언적으로 기술합니다. 이때 "누가 INSERT/UPDATE/DELETE 같은 실제 DB 변경을 책임지는가"가 시나리오를 가르는 핵심입니다.

1) Managed 시나리오 — RAP 프레임워크가 자동으로 모든 표준 동작을 처리합니다. 키 생성, 락(Lock), 권한 체크, DB 저장까지 프레임워크가 담당하며, 개발자는 BDEF에 선언만 하면 됩니다. 비유하자면 "풀옵션 자동변속 차량"입니다. 운전대와 가속 페달만 조작하면 알아서 굴러갑니다.

2) Unmanaged 시나리오 — 모든 트랜잭션 동작(CREATE, UPDATE, DELETE, LOCK, SAVE)을 개발자가 직접 ABAP 핸들러 클래스에 구현해야 합니다. 기존 BAPI, 함수 모듈, 클래스 메서드를 재사용해야 하는 경우에 사용합니다. 비유하자면 "수동변속 클래식카"입니다. 직접 모든 기어를 조작하지만, 그만큼 자유도가 높습니다.

3) Managed with additional save — 80%는 managed로 처리하고, 저장 직전에 외부 시스템 호출 같은 추가 로직만 수동으로 끼워 넣는 하이브리드 방식입니다.

선택 기준은 단순합니다. 신규 Z 테이블을 직접 다루는 경우 managed, 레거시 BAPI/함수 모듈을 감싸야 하는 경우 unmanaged가 일반적입니다.

실전 예제 1단계 — Managed 기본 BDEF

먼저 신규로 만든 ZSALES_ORDER 테이블을 기반으로 한 managed BDEF를 살펴봅니다. 시나리오는 사내 영업 시스템의 주문 헤더 관리입니다.

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

define behavior for ZR_SalesOrder alias SalesOrder
persistent table zsales_order
lock master
authorization master ( instance )
etag master local_last_changed_at
{
  create;
  update;
  delete;

  field ( readonly ) order_uuid, created_at, created_by;
  field ( mandatory ) customer_id, order_date;

  mapping for zsales_order
  {
    order_uuid        = order_uuid;
    customer_id       = customer_id;
    order_date        = order_date;
    total_amount      = total_amount;
    created_at        = created_at;
    created_by        = created_by;
    local_last_changed_at = local_last_changed_at;
  }
}

여기서 핵심 키워드는 managedpersistent table zsales_order입니다. 프레임워크가 이 선언만 보고 자동으로 INSERT/UPDATE/DELETE 문을 생성해 DB에 반영합니다. 개발자는 핸들러 클래스에 코드 한 줄도 작성할 필요가 없습니다.

실전 예제 2단계 — Unmanaged 시나리오와 핸들러 구현

이번에는 20년 된 레거시 BAPI를 RAP로 노출해야 한다고 가정합니다. 새 테이블도 없고, 기존 BAPI가 모든 검증/저장 로직을 담고 있기 때문에 managed로는 처리가 불가능합니다.

unmanaged implementation in class zbp_r_purchasereq_tp unique;
strict ( 2 );

define behavior for ZR_PurchaseRequisition alias PReq
lock master
authorization master ( instance )
early numbering
{
  create;
  update;
  delete;

  field ( readonly ) preq_no, created_on;
  field ( mandatory ) material, quantity, plant;
}

위 BDEF는 동작을 "선언"만 할 뿐, 실제 처리는 핸들러 클래스에서 직접 구현합니다.

CLASS lhc_preq DEFINITION INHERITING FROM cl_abap_behavior_handler.
  PRIVATE SECTION.
    METHODS create FOR MODIFY
      IMPORTING entities FOR CREATE preq.
    METHODS save_modified REDEFINITION.
    METHODS lock FOR LOCK
      IMPORTING keys FOR LOCK preq.
ENDCLASS.

CLASS lhc_preq IMPLEMENTATION.
  METHOD create.
    LOOP AT entities INTO DATA(ls_entity).
      DATA: lt_return TYPE STANDARD TABLE OF bapiret2,
            lv_preqno TYPE banfn.
      CALL FUNCTION 'BAPI_PR_CREATE'
        EXPORTING
          prheader = VALUE #( doc_type = 'NB' )
        IMPORTING
          number   = lv_preqno
        TABLES
          return   = lt_return.
      IF lv_preqno IS NOT INITIAL.
        APPEND VALUE #( %cid = ls_entity-%cid
                        preq_no = lv_preqno ) TO mapped-preq.
      ELSE.
        APPEND VALUE #( %cid = ls_entity-%cid
                        %msg = new_message_with_text(
                          severity = if_abap_behv_message=>severity-error
                          text     = '구매요청 생성 실패' ) ) TO reported-preq.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.
  METHOD save_modified.
    CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
      EXPORTING wait = 'X'.
  ENDMETHOD.
ENDCLASS.

보시다시피 unmanaged는 BAPI 호출, 에러 메시지 변환, COMMIT 처리까지 전부 손으로 작성해야 합니다. 그러나 기존 자산을 그대로 활용할 수 있다는 점이 결정적 장점입니다.

실전 예제 3단계 — Managed with Additional Save (프로덕션 패턴)

실무에서 가장 많이 쓰이는 패턴입니다. 신규 테이블은 managed로 처리하되, 저장 시점에 외부 시스템(예: 외부 ERP, 메시징 큐)에 이벤트를 발행하는 경우입니다.

managed with additional save implementation
  in class zbp_r_salesorder_tp unique;
strict ( 2 );

define behavior for ZR_SalesOrder alias SalesOrder
persistent table zsales_order
lock master
authorization master ( instance )
with additional save
{
  create;
  update;
  delete;
  determination calculateTotal on save { create; update; }
  validation checkCustomer on save { field customer_id; create; }
}

그리고 saver 클래스에서 추가 로직만 짧게 작성합니다.

CLASS lsc_salesorder DEFINITION INHERITING FROM cl_abap_behavior_saver.
  PROTECTED SECTION.
    METHODS save_modified REDEFINITION.
ENDCLASS.

CLASS lsc_salesorder IMPLEMENTATION.
  METHOD save_modified.
    LOOP AT create-salesorder INTO DATA(ls_new).
      TRY.
          zcl_event_publisher=>publish_order_created(
            iv_order_uuid = ls_new-order_uuid
            iv_customer   = ls_new-customer_id ).
        CATCH zcx_event_failed INTO DATA(lx_err).
          zcl_app_log=>warning( lx_err->get_text( ) ).
      ENDTRY.
    ENDLOOP.
  ENDMETHOD.
ENDCLASS.

이 패턴은 managed의 생산성과 unmanaged의 유연성을 동시에 챙길 수 있어 프로덕션 환경에서 권장됩니다. 단위 테스트는 CL_CDS_TEST_ENVIRONMENTCL_ABAP_BEHV_TEST_ENVIRONMENT를 활용해 DB 의존성을 격리한 상태로 작성하는 것이 일반적입니다.

흔한 실수와 트러블슈팅 FAQ

Q1. managed인데 데이터가 저장이 안 됩니다.
A. persistent table 키워드 누락, mapping 절의 필드명 불일치, 또는 %cid를 응답에서 추적하지 않은 경우가 대부분입니다. mapping 절에서 BDEF 필드명과 DB 필드명이 정확히 매핑되었는지 확인하세요.

Q2. 레거시 BAPI를 wrapping 했는데 ETag 충돌이 발생합니다.
A. unmanaged 시나리오에서는 ETag 계산을 프레임워크가 자동으로 하지 않습니다. etag master <field>로 명시하거나, 핸들러에서 직접 last_changed 타임스탬프를 갱신해 주어야 합니다.

Q3. managed로 시작했다가 외부 시스템 연동이 필요해졌습니다. 전부 unmanaged로 다시 짜야 하나요?
A. 그럴 필요 없습니다. managed with additional save로 키워드만 변경하고 saver 클래스에 로직을 추가하세요. 기존 managed 동작은 그대로 유지됩니다.

  • strict(2) 모드에서는 numbering이나 lock 정의 누락이 활성화 오류로 이어집니다.
  • unmanaged에서 COMMIT WORK를 깜빡하면 데이터가 사라지지만 에러도 안 납니다.
  • managed의 mapping 절에서 일부 필드를 빼먹으면 해당 필드는 항상 NULL로 저장됩니다.
  • 권한 체크는 두 시나리오 모두 별도로 instance/global authorization 핸들러에 구현해야 하며, 자동 처리되지 않습니다.

이 글 이후 더 살펴볼 주제

managed/unmanaged 선택을 익혔다면, 다음 단계로 Draft-enabled BO(Fiori Elements의 임시 저장 기능), Late Numbering vs Early Numbering의 성능 차이, Action과 Function을 통한 사용자 정의 동작, 그리고 Determination과 Validation의 시점(on save, on modify) 설계를 학습하는 것을 권장합니다. 특히 실무에서는 RAP BO 간의 Composition 관계 정의가 자주 필요하므로 부모-자식 BDEF 작성법도 함께 익혀두면 좋습니다.

댓글 0

아직 댓글이 없습니다.