RAP

RAP Draft Table 직접 수정하면? #shorts #SAP #RAP

▶ YouTube에서 보기

1. 개요 및 이 글에서 다루는 것

SAP S/4HANA Fiori 화면에서 사용자가 신규 판매 오더를 입력하다가 실수로 브라우저를 닫으면 지금까지 입력한 내용이 모두 사라져 버립니다. RAP(ABAP RESTful Application Programming Model)의 Draft 기능은 이런 상황에서 사용자의 미완성 입력을 안전하게 서버에 보관해 두었다가, 나중에 재접속했을 때 이어서 작업할 수 있도록 해주는 핵심 UX 개선 장치입니다. 이 글은 관리형(Managed) RAP 시나리오에서 Draft를 활성화하고, Behavior Definition·Behavior Implementation·Metadata Extension을 어떻게 구성해야 Fiori Elements List Report에서 자연스럽게 임시 저장이 동작하는지를 실전 예제 중심으로 설명합니다.

  • Draft-Enabled 엔티티의 내부 동작 원리 이해
  • Managed Draft 구현을 위한 BDL(Behavior Definition Language) 문법
  • Draft Determination / Draft Action 활용법
  • Total ETag·Lock·Discard 흐름과 트러블슈팅

2. 시작 전에 알아두면 좋은 것

이 글은 ABAP RESTful Application Programming Model의 기본 개념(Behavior Definition, Behavior Pool, Service Definition, Service Binding)을 이해하고 있다는 전제로 진행됩니다. CDS View Entity 문법과 Fiori Elements List Report/Object Page의 기본 구조, 그리고 OData V4 프로토콜의 대략적인 요청·응답 형태를 알고 있으면 Draft 흐름을 이해하기가 훨씬 수월합니다. 과거 BOPF Draft를 다뤄본 경험이 있다면 개념적 유사성을 빠르게 파악할 수 있지만, 필수는 아닙니다.

3. 환경과 준비물

이 글의 예제는 다음 환경을 기준으로 작성되었습니다. Draft 기능은 ABAP Platform 1909 이후로 관리형 시나리오에서 안정적으로 제공되고 있으며, 최신 릴리스일수록 편의 문법이 늘어나 있으므로 가급적 상위 버전을 권장합니다.

  • SAP BTP ABAP Environment(Steampunk) 2308 이상 또는 S/4HANA 2022 온프레미스
  • ABAP Development Tools(ADT) for Eclipse 3.36 이상
  • Fiori Elements Preview 또는 SAP Business Application Studio
  • 테스트용 데이터베이스 테이블 2개 (헤더/아이템 구조)
  • OData V4 서비스 바인딩 권한(Developer Role Collection)

Draft를 사용하려면 데이터베이스 테이블에 %admin 관련 필드가 직접 있을 필요는 없지만, RAP 런타임이 내부적으로 Draft Shadow Table을 생성·관리합니다. 개발 시스템의 스토리지 여유를 미리 확인하는 것이 좋습니다.

4. 핵심 개념 정리

Draft를 이해하는 가장 쉬운 비유는 "구글 문서의 자동 저장"입니다. 사용자가 글을 쓰는 동안 매 순간의 변경 사항이 백그라운드에서 저장되고, 다른 기기에서 접속해도 이어서 편집할 수 있는 그 경험을 RAP가 엔터프라이즈 트랜잭션 위에서 재현합니다.

내부적으로 Draft가 활성화된 엔티티는 두 개의 논리적 저장소를 가집니다. 하나는 이미 저장(active)된 최종 데이터를 담는 Active Instance, 다른 하나는 사용자가 편집 중인 임시 데이터를 담는 Draft Instance입니다. Fiori 화면에서 "Edit" 버튼을 누르는 순간 EditAction이 호출되어 Active로부터 Draft가 만들어지고, 사용자가 필드에 값을 입력할 때마다 PATCH 요청이 Draft로 흘러갑니다. 최종적으로 "Save"를 누르면 ActivateAction이 실행되어 Draft가 Active로 승격되고 Draft는 삭제됩니다.

Draft는 트랜잭션 락(Exclusive Lock)이 아니라 Optimistic Lock을 사용합니다. 한 사용자가 편집 중일 때 다른 사용자가 동시에 편집하려 하면 시스템은 경고를 표시하고, 필요 시 락 소유자를 강제로 넘겨받을 수 있는 이력 관리 흐름을 제공합니다.

RAP Draft는 크게 세 가지 관리 유형이 있습니다. 첫째, Managed Draft는 RAP 프레임워크가 Draft Shadow Table을 자동 생성·관리해주는 방식으로, 신규 개발에서 가장 권장됩니다. 둘째, Unmanaged Draft는 레거시 로직 재사용을 위해 Draft 저장 로직을 개발자가 직접 구현하는 방식입니다. 셋째, Draft with Additional Save는 활성화 시점에 외부 시스템 호출을 삽입해야 하는 시나리오에 사용합니다. 이 글에서는 첫 번째인 Managed Draft를 다룹니다.

Draft 동작을 지탱하는 핵심 요소는 다음과 같습니다.

  • Total ETag: 부모·자식을 포함한 전체 인스턴스의 변경 여부를 감지하는 해시
  • Draft Determination: Draft 저장 시점에 자동으로 계산·보정할 필드 정의
  • Draft Action: Activate, Discard, Edit, Resume 등 표준 액션
  • Auto Expiration: 오래된 Draft를 주기적으로 정리하는 만료 정책

5. 실전 예제 3단계

5-1. 기본 예제 — Managed Draft 활성화

판매 오더 헤더(zsl_shdr)와 아이템(zsl_sitm) 구조를 가진 시나리오에서, 먼저 CDS View Entity와 Behavior Definition을 만들고 Draft를 켜보겠습니다.

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Header - Root'
define root view entity ZR_SalesOrderTP
  as select from zsl_shdr
  composition [0..*] of ZR_SalesItemTP as _Items
{
  key sales_order_id     as SalesOrderId,
      customer_id        as CustomerId,
      order_date         as OrderDate,
      currency_code      as CurrencyCode,
      total_amount       as TotalAmount,
      overall_status     as OverallStatus,
      @Semantics.user.createdBy: true
      created_by         as CreatedBy,
      @Semantics.systemDateTime.createdAt: true
      created_at         as CreatedAt,
      @Semantics.user.lastChangedBy: true
      last_changed_by    as LastChangedBy,
      @Semantics.systemDateTime.lastChangedAt: true
      last_changed_at    as LastChangedAt,
      @Semantics.systemDateTime.localInstanceLastChangedAt: true
      local_last_changed_at as LocalLastChangedAt,
      _Items
}

이어서 Behavior Definition을 관리형·Draft 모드로 정의합니다. 여기서 핵심은 with draft 지시자와 etag master 선언입니다.

managed implementation in class zbp_r_salesordertp unique;
strict ( 2 );
with draft;

define behavior for ZR_SalesOrderTP alias SalesOrder
persistent table zsl_shdr
draft table zsl_shdr_d
lock master
total etag LastChangedAt
authorization master ( instance )
etag master LocalLastChangedAt
{
  field ( numbering : managed, readonly ) SalesOrderId;
  field ( readonly ) CreatedBy, CreatedAt, LastChangedBy, LastChangedAt, LocalLastChangedAt;

  create;
  update;
  delete;

  draft action Edit;
  draft action Activate optimized;
  draft action Discard;
  draft action Resume;
  draft determine action Prepare;

  association _Items { create; with draft; }
}

여기서 draft table zsl_shdr_d는 RAP가 Draft 인스턴스를 저장할 Shadow Table 이름을 명시하는 부분입니다. 실제 테이블 정의는 별도로 만들어야 하며, 활성 테이블과 동일한 필드에 더해 %admin(관리 필드) 및 %control(필드별 변경 플래그)이 자동으로 추가됩니다.

5-2. 실무 시나리오 — Determination과 Validation 추가

실무에서는 단순히 Draft를 켜는 것으로 끝나지 않습니다. Draft로 저장되는 순간 총 금액을 재계산하고, Activate 직전에 여신 한도를 검증해야 하는 요구사항이 흔합니다. 이때는 Draft Determination과 Validation을 Behavior Definition에 추가합니다.

define behavior for ZR_SalesOrderTP alias SalesOrder
...
{
  ...
  determination calculateTotalAmount on modify { field TotalAmount; create; update; }
  determination setDefaultCurrency on modify { create; }

  validation checkCustomerCredit on save { create; update; field CustomerId, TotalAmount; }
  validation checkOrderDate      on save { create; field OrderDate; }

  side effects
  {
    field CustomerId affects field CurrencyCode, field TotalAmount;
  }
}

Behavior Implementation 클래스에서는 이렇게 선언한 Determination을 구현합니다. Draft 저장 시점마다 호출되므로 성능에 민감한 로직은 지양하는 것이 좋습니다.

METHOD calculateTotalAmount.
  READ ENTITIES OF zr_salesordertp IN LOCAL MODE
    ENTITY SalesOrder BY \_Items
    ALL FIELDS WITH CORRESPONDING #( keys )
    RESULT DATA(lt_items).

  LOOP AT keys ASSIGNING FIELD-SYMBOL(<fs_key>).
    DATA(lv_total) = REDUCE #(
      INIT sum = CONV /dmo/total_price( 0 )
      FOR item IN lt_items WHERE ( SalesOrderId = <fs_key>-SalesOrderId )
      NEXT sum = sum + item-Quantity * item-UnitPrice ).

    MODIFY ENTITIES OF zr_salesordertp IN LOCAL MODE
      ENTITY SalesOrder
      UPDATE FIELDS ( TotalAmount )
      WITH VALUE #( ( %tky = <fs_key>-%tky
                       TotalAmount = lv_total ) )
      REPORTED DATA(ls_reported).
  ENDLOOP.
ENDMETHOD.

METHOD checkCustomerCredit.
  READ ENTITIES OF zr_salesordertp IN LOCAL MODE
    ENTITY SalesOrder
    FIELDS ( CustomerId TotalAmount )
    WITH CORRESPONDING #( keys )
    RESULT DATA(lt_orders).

  LOOP AT lt_orders INTO DATA(ls_order).
    SELECT SINGLE credit_limit FROM zsl_customer
      WHERE customer_id = @ls_order-CustomerId INTO @DATA(lv_limit).

    IF ls_order-TotalAmount > lv_limit.
      APPEND VALUE #( %tky = ls_order-%tky ) TO failed-salesorder.
      APPEND VALUE #( %tky = ls_order-%tky
                       %msg = new_message(
                         id       = 'ZSL_MSG'
                         number   = '010'
                         severity = if_abap_behv_message=>severity-error
                         v1       = ls_order-CustomerId )
                       %element-CustomerId = if_abap_behv=>mk-on
                       %element-TotalAmount = if_abap_behv=>mk-on ) TO reported-salesorder.
    ENDIF.
  ENDLOOP.
ENDMETHOD.

이렇게 하면 사용자가 아이템을 추가하거나 수량을 바꿀 때마다 헤더 총액이 Draft 저장 시점에 실시간으로 다시 계산되며, Activate 직전에 여신 한도 초과 여부가 검증됩니다.

5-3. 프로덕션 — Metadata Extension, 만료 정책, 테스트

운영 환경에서는 Draft가 화면에 어떻게 보일지도 신경 써야 합니다. Draft 상태 아이콘·잠금 표시·"편집 재개" 팝업은 Fiori Elements가 어노테이션을 읽어서 자동으로 그려주기 때문에, Metadata Extension에서 관련 UI 어노테이션을 반드시 설정해야 합니다.

@Metadata.layer: #CORE
annotate entity ZC_SalesOrderTP with
{
  @UI.lineItem: [ { position: 10, label: 'Order ID' },
                  { type: #FOR_ACTION, dataAction: 'Discard', label: 'Discard Draft' } ]
  @UI.selectionField: [ { position: 10 } ]
  SalesOrderId;

  @UI.identification: [ { position: 20, label: 'Customer' } ]
  @Consumption.valueHelpDefinition: [ { entity.name: 'ZI_CustomerVH', element: 'CustomerId' } ]
  CustomerId;

  @UI.hidden: true
  LocalLastChangedAt;
}

Behavior Definition의 Projection Layer에도 use draft;을 잊지 말고 넣어야 프로젝션에서 Draft가 노출됩니다. 또한 오래된 Draft가 누적되는 것을 막기 위한 만료 정책을 지정할 수 있습니다.

projection;
use draft;

define behavior for ZC_SalesOrderTP alias SalesOrder
{
  use create;
  use update;
  use delete;

  use action Edit;
  use action Activate;
  use action Discard;
  use action Resume;
  use action Prepare;

  use association _Items { create; with draft; }
}

ABAP Unit 기반 테스트에서는 CL_ABAP_TX=>SAVECOMMIT ENTITIES를 통해 Activate 흐름을 검증하고, MODIFY ENTITIES ... EXECUTE Discard로 Draft 삭제 시나리오도 커버해야 합니다. Draft 관련 테스트는 특히 %tky%is_draft 필드를 이용해 Draft/Active를 구분하는 것이 핵심입니다.

MODIFY ENTITIES OF zr_salesordertp
  ENTITY SalesOrder
  EXECUTE Edit FROM VALUE #(
    ( %key-SalesOrderId = '4500000010'
      %param-preserve_changes = abap_true ) )
  RESULT DATA(lt_edited)
  FAILED DATA(ls_failed).

COMMIT ENTITIES RESPONSES FAILED DATA(ls_commit_failed).

6. 자주 만나는 문제와 해결법

Draft를 도입하면 개발자들이 가장 많이 겪는 오류 유형과 해결책을 FAQ 형식으로 정리합니다.

Q1. "Draft table does not exist" 활성화 오류가 발생합니다.
Behavior Definition에서 지정한 draft table 이름과 실제 데이터베이스 테이블 이름이 정확히 일치해야 합니다. Draft 테이블은 활성 테이블 필드에 더해 %admindraftadministrativedata 참조 필드를 반드시 포함해야 하며, ADT의 "Generate Draft Table" 기능을 이용하는 것이 안전합니다.

Q2. Draft로 저장은 되는데 Activate 시 데이터가 사라집니다.
대부분 %control 플래그를 잘못 다뤄서 발생합니다. Determination 안에서 MODIFY ENTITIES로 필드를 업데이트할 때는 반드시 UPDATE FIELDS ( ... )로 대상 필드를 명시해야 하며, Behavior Implementation의 로컬 모드(IN LOCAL MODE)를 빼먹으면 Draft 트랜잭션이 꼬여 활성화가 부분적으로 실패합니다.

Q3. Fiori 화면에서 "Edit" 버튼이 보이지 않습니다.
Projection Behavior에 use action Edit;이 빠졌거나, Service Binding이 OData V2로 잡혀 있을 가능성이 큽니다. Managed Draft는 OData V4에서만 표준적으로 동작하므로 Service Binding을 V4 UI로 다시 만들어야 합니다.

Q4. 여러 사용자가 동시에 편집하면 어떻게 되나요?
Draft는 Exclusive Lock이 아닙니다. 다른 사용자가 이미 Draft를 만든 인스턴스를 편집하려 하면 "Unsaved Changes by Another User" 대화상자가 뜨며, 락을 넘겨받으면 원래 소유자의 Draft는 폐기됩니다. 감사가 필요한 경우 이 이벤트를 로깅하는 별도 Determination을 추가하는 것을 권장합니다.

Q5. 오래된 Draft가 계속 쌓입니다.
Draft는 명시적으로 Discard하지 않으면 사라지지 않습니다. 표준 잡 RUTDRAFTCLEANUP(또는 BTP 환경의 예약 잡)을 스케줄링해 만료 기간이 지난 Draft를 주기적으로 정리하는 것이 일반적으로 권장됩니다.

7. 이어서 살펴보면 좋은 주제

Managed Draft에 익숙해졌다면 다음 단계로 확장해볼 수 있는 주제는 다음과 같습니다.

  • Draft with Additional Save: 활성화 시점에 SAP Event Mesh나 외부 API 호출을 삽입하는 패턴
  • Unmanaged Draft: BAPI/함수모듈 기반 레거시 로직 위에 Draft를 얹는 시나리오
  • Draft와 Side Effects, Dynamic Actions의 조합으로 실시간 UI 반응성 강화
  • Field-based Authorization과 Draft의 조합 — 편집 권한이 없는 필드 처리
  • ABAP Unit for RAP — Draft 트랜잭션에 특화된 테스트 더블 활용

8. 더 읽어볼 자료

  • SAP Help Portal — Draft (ABAP RESTful Application Programming Model)
  • SAP Help Portal — Managed Scenario with Draft
  • SAP Help Portal — Behavior Definition Language Reference
  • SAP Community — ABAP 및 RAP 관련 블로그

댓글 0

아직 댓글이 없습니다.