RAP

Draft 필드 직접 수정 — 큰일 나는 실수 3가지 #shorts #SAP #RAP

▶ YouTube에서 보기

Draft 상태 관리의 핵심: Admin 필드란 무엇인가

SAP RAP(RESTful ABAP Programming Model)에서 Draft 기능은 사용자가 데이터를 편집하는 동안 임시로 보관하고, 여러 세션을 넘나들면서도 작업을 이어갈 수 있게 하는 매우 중요한 메커니즘이다. 이 Draft 기능이 안정적으로 작동하는 이유는 프레임워크가 내부적으로 관리하는 Draft Administrative Fields(Admin 필드) 덕분이다. 대표적인 필드로는 IsActiveEntity, HasDraftEntity, HasActiveEntity, DraftIsActiveEntity, DraftIsCreatedByMe, DraftEntityLastChangedDateTime, DraftEntityCreationDateTime 등이 있다.

이 필드들은 Draft 테이블(활성 테이블과 병존하는 Draft 접미어 테이블)에 저장되며, 각 인스턴스가 "지금 활성 데이터인지, 편집 중인 Draft인지, 잠금이 걸려있는지, 다른 사용자에게 소유권이 있는지" 등을 판별하는 역할을 한다. 이 글에서 다룰 핵심은 이 필드들을 절대 개발자가 직접 UPDATE·SET하려 시도해서는 안 된다는 원칙이다.

이 글을 다 읽으면 다음을 판단할 수 있게 된다.

  • Draft Admin 필드가 각각 어떤 의미를 갖는지 구분한다
  • 왜 이 필드들을 프레임워크가 독점적으로 관리해야 하는지 이해한다
  • BDEF와 Behavior Implementation에서 Admin 필드에 손대는 실수 패턴을 감지한다
  • Activate/Discard 액션과 자동 전환 로직을 올바르게 설계한다

알아두면 좋은 사전 지식

이 글은 ABAP RAP의 기본 개념(Behavior Definition, Behavior Implementation, Projection Layer)을 알고 있고, CDS View와 Fiori Elements List Report/Object Page를 한 번이라도 만들어본 개발자를 대상으로 한다. Draft-Enabled 시나리오, Managed/Unmanaged 시나리오의 차이, EML(Entity Manipulation Language) 문법에 대해 최소한의 이해가 있다면 이 글의 실전 예제를 훨씬 편하게 소화할 수 있다.

환경 및 버전 확인

이 글의 예제는 다음 환경에서 검증하는 것을 권장한다.

  • SAP S/4HANA Cloud Private Edition 2023 FPS02 이상 또는 SAP BTP ABAP Environment(Steampunk) 2405 릴리스 이상
  • ABAP Development Tools(ADT) for Eclipse 2024-03 이후 버전
  • RAP Managed BO with Draft 시나리오 (unmanaged draft는 별도 취급)
  • Fiori Elements Preview 또는 SAP Business Application Studio가 활성화된 상태

Draft 관련 기능은 SAP NetWeaver 7.5x 계열 중에서도 특정 SP 이후부터 완전 지원되며, 오래된 릴리스에서는 일부 표준 오퍼레이션(예: Resume, Edit)이 지원되지 않을 수 있다. 필드 자동 관리 원칙 자체는 릴리스와 무관하게 유지된다.

프레임워크 자동 관리 원칙 - 왜 건드리면 안 되는가

RAP Draft 메커니즘을 자동차 변속기에 비유해 볼 수 있다. 오토매틱 차량에서 D 상태로 주행 중일 때 억지로 R 기어의 내부 유압을 손으로 조작하면 트랜스미션 전체가 망가진다. Draft Admin 필드도 마찬가지다. 프레임워크는 다음 순간마다 이 필드들을 원자적(atomic)으로 갱신한다.

  • Draft 생성 시점: 사용자가 List Report에서 Create를 누르거나 활성 인스턴스에서 Edit를 눌러 Draft가 새로 만들어질 때
  • Draft 저장 시점: 자동 저장 또는 명시적 Save Draft 호출 시
  • Activate 시점: Save를 눌러 Draft가 활성 인스턴스로 승격될 때
  • Discard 시점: 사용자가 Cancel하여 Draft가 폐기될 때
  • Resume 시점: 다른 세션에서 저장된 Draft를 다시 이어받을 때

이 시점마다 IsActiveEntity, HasDraftEntity, DraftIsCreatedByMe 등이 서로 정합성을 이루도록 조율된다. 개발자가 MODIFY ENTITIES … FIELDS ( is_active_entity )처럼 직접 값을 넣으면, Draft 테이블과 활성 테이블 사이의 참조 무결성이 깨지고, Fiori UI는 Draft Locked by Another User 같은 유령 메시지를 표시하거나, 최악의 경우 활성 데이터가 Draft 상태로 표시되어 편집이 불가능해진다.

IsActiveEntity와 DraftIsActiveEntity 차이점 심층 분석

두 필드는 이름이 비슷해서 혼동하기 쉽지만 역할이 다르다.

  • IsActiveEntity: 각 인스턴스 자체가 활성인지 Draft인지 표시. true면 활성 테이블 레코드, false면 Draft 테이블 레코드. 이 필드는 각 행이 "누구인지"를 나타내는 신분증에 가깝다.
  • DraftIsActiveEntity: OData 응답이나 CDS 상에서 프론트엔드가 인스턴스를 소비할 때 참조용으로 노출되는 값. Fiori UI가 Draft/Active 전환 링크를 그릴 때 필요한 힌트다.
  • HasActiveEntity: 현재 Draft가 원본 활성 인스턴스를 갖고 있는지 여부. true면 기존 활성 레코드를 편집 중인 Draft, false면 신규 생성 Draft.
  • HasDraftEntity: 활성 인스턴스 관점에서 현재 편집 중인 Draft가 존재하는지 여부. Fiori 리스트에 Unsaved Changes 배지를 표시할 때 사용된다.

정리하면, IsActiveEntity는 인스턴스 자신의 정체성, HasActiveEntity/HasDraftEntity는 상대편 존재 여부, DraftIsActiveEntity는 UI 소비용 파생 값이다. 이 관계를 억지로 재정의하려 하면 프레임워크가 만들어놓은 상호 참조가 어긋난다.

DraftIsCreatedByMe와 잠금 메커니즘

DraftIsCreatedByMe는 현재 로그인한 사용자가 해당 Draft를 만든 소유자인지 여부를 나타낸다. Draft가 다른 사용자에 의해 생성된 경우 이 값은 false가 되며, RAP 프레임워크는 그 Draft에 대해 편집 잠금(Exclusive Lock)을 부여해 다른 사용자가 동시에 수정하지 못하도록 막는다. 이는 SAP의 락 오브젝트(ENQUEUE_ESFRAP_XXX 계열)와 연동되어 유지된다.

일반적으로 이 잠금은 일정 시간(기본 8시간) 유지되며, 소유자가 Discard 하거나 만료 시각이 되면 자동 해제된다. 개발자가 DraftIsCreatedByMe를 임의로 true로 바꾸면 락 소유자와 필드가 불일치하는 상태가 되어, 잠금은 걸려 있는데 UI에서는 편집 가능해 보이는 위험한 상황이 발생한다.

실전 예제 1 - SalesOrder Draft BDEF의 흔한 실수 패턴

먼저 Draft-enabled BDEF의 최소 구조를 보자. 판매 오더(ZI_SalesOrderTP) 시나리오다.

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

define behavior for ZI_SalesOrderTP alias SalesOrder
persistent table zsalesorder
draft table zsalesorder_d
lock master
total etag LastChangedAt
authorization master ( instance )
etag master LocalLastChangedAt
{
  field ( readonly ) SalesOrderId, CreatedBy, CreatedAt;
  field ( mandatory ) CustomerId, OrderDate;

  create;
  update;
  delete;

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

여기까지는 정상이다. 이제 실무 현장에서 자주 발견되는 안티패턴을 보자. 어떤 개발자가 "Draft를 만들자마자 특정 상태로 강제하고 싶다"는 요구를 받고 다음과 같이 Determination을 작성했다고 하자.

METHOD set_initial_draft_state.
  " 안티패턴: Admin 필드를 직접 수정 시도
  MODIFY ENTITIES OF zi_salesordertp IN LOCAL MODE
    ENTITY SalesOrder
      UPDATE FIELDS ( is_active_entity has_draft_entity draft_is_created_by_me )
      WITH VALUE #( FOR key IN keys
                    ( %tky              = key-%tky
                      is_active_entity  = abap_false
                      has_draft_entity  = abap_true
                      draft_is_created_by_me = abap_true ) )
    FAILED failed
    REPORTED reported.
ENDMETHOD.

이 코드는 컴파일은 통과할 수 있지만 실행 시 대부분 Field is read-only 또는 Update on draft admin field is not allowed 오류를 발생시킨다. 설령 특정 릴리스에서 예외 없이 통과한다 해도, Draft 테이블과 락 테이블의 정합성이 깨져서 그 다음 Activate 호출이 실패하거나, UI가 Draft를 인식하지 못하는 문제가 생긴다.

실전 예제 2 - 올바른 Draft 상태 확인 로직

Draft 상태에 따라 특정 동작을 다르게 처리해야 한다면, Admin 필드를 변경하는 것이 아니라 읽어서 분기해야 한다. 예를 들어, 배송 확정 액션이 Draft 상태에서는 호출되지 않도록 방어하려면 다음처럼 작성한다.

METHOD confirm_shipment.
  READ ENTITIES OF zi_salesordertp IN LOCAL MODE
    ENTITY SalesOrder
      FIELDS ( SalesOrderId ShipmentStatus )
      WITH CORRESPONDING #( keys )
    RESULT DATA(orders).

  LOOP AT orders ASSIGNING FIELD-SYMBOL(<order>).
    " %is_draft로 안전하게 상태 확인 (Admin 필드 직접 접근 금지)
    IF <order>-%is_draft = if_abap_behv=>mk-on.
      APPEND VALUE #(
        %tky        = <order>-%tky
        %msg        = new_message(
                        id       = 'ZSO_MSG'
                        number   = '015'
                        severity = if_abap_behv_message=>severity-error )
        %element-shipmentstatus = if_abap_behv=>mk-on
      ) TO reported-salesorder.
      APPEND VALUE #( %tky = <order>-%tky ) TO failed-salesorder.
      CONTINUE.
    ENDIF.

    MODIFY ENTITIES OF zi_salesordertp IN LOCAL MODE
      ENTITY SalesOrder
        UPDATE FIELDS ( ShipmentStatus )
        WITH VALUE #( ( %tky           = <order>-%tky
                        ShipmentStatus = 'CONFIRMED' ) )
      REPORTED DATA(update_reported).
  ENDLOOP.
ENDMETHOD.

여기서 핵심은 %is_draft 시스템 필드를 사용해 상태를 판별할 뿐, IsActiveEntity 자체에 손대지 않는다는 점이다. %is_draft는 프레임워크가 안전하게 제공하는 힌트이며, 이는 필요할 때 언제든 참조해도 무방하다.

실전 예제 3 - 프로덕션 품질의 Activate 확장

Activate 시점에 추가 검증(예: 여신한도 확인)이 필요한 경우 additional save 또는 validation on save 트리거를 활용한다. 여기서도 Admin 필드는 절대 손대지 않는다.

METHOD validate_creditlimit.
  READ ENTITIES OF zi_salesordertp IN LOCAL MODE
    ENTITY SalesOrder
      FIELDS ( CustomerId TotalAmount )
      WITH CORRESPONDING #( keys )
    RESULT DATA(orders_to_validate).

  LOOP AT orders_to_validate INTO DATA(order).
    DATA(available_limit) = zcl_credit_service=>get_available_limit(
                              customer_id = order-CustomerId ).

    IF order-TotalAmount > available_limit.
      APPEND VALUE #( %tky = order-%tky ) TO failed-salesorder.
      APPEND VALUE #(
        %tky      = order-%tky
        %state_area = 'CREDIT_CHECK'
        %msg      = new_message(
                      id       = 'ZSO_MSG'
                      number   = '030'
                      severity = if_abap_behv_message=>severity-error
                      v1       = |{ available_limit NUMBER = USER }| )
        %element-TotalAmount = if_abap_behv=>mk-on
      ) TO reported-salesorder.
    ENDIF.
  ENDLOOP.
ENDMETHOD.

이런 방식이면 Activate 도중 검증 실패가 발생하더라도 프레임워크는 Draft를 그대로 유지한다. Admin 필드는 Activate 실패 → Draft 유지라는 상태 전이를 자동으로 기록하므로, 개발자가 추가 조치를 할 필요가 없다.

흔한 실수와 트러블슈팅

FAQ 1. "Draft에서 활성으로 바로 승격시키려고 IsActiveEntity = X 를 UPDATE했더니 데이터가 사라졌어요."
Draft → Active 전환은 반드시 Activate 액션(또는 EML의 COMMIT ENTITIES with activation)으로 수행해야 한다. 직접 UPDATE는 두 테이블 사이의 이관 로직을 건너뛰어 원본 활성 레코드와 Draft 레코드 모두 손상시킬 수 있다.

FAQ 2. "HasDraftEntity가 true인데 Draft 테이블에는 아무 것도 없어요."
과거에 Admin 필드를 임의 조작했거나, Draft 폐기 시 락은 풀렸지만 활성 레코드의 플래그가 잔존한 경우다. 표준 클래스 CL_DRAFT_HDL_ASSISTANT_FACTORY 또는 SAP Note에서 안내하는 정합성 복구 리포트를 사용해 초기화해야 한다. 재발 방지책은 Admin 필드에 대한 모든 write 로직을 코드베이스에서 제거하는 것이다.

FAQ 3. "다른 사용자가 만든 Draft를 관리자로서 강제 해제하고 싶습니다."
DraftIsCreatedByMe를 임의 조작하지 말고, 표준 액션 Discard를 관리자 권한으로 호출하거나, Draft Lifetime 프레임워크 설정을 통해 만료를 조정하는 것이 권장된다. 필요한 경우 커스텀 액션에서 관리자 권한을 확인한 뒤 MODIFY ENTITIES … EXECUTE Discard로 정상 경로를 사용해야 한다.

FAQ 4. "테스트 데이터로 Draft 테이블을 채우고 싶은데 어떻게 해야 하나요?"
직접 INSERT 하지 말고, RAP Test Doubles(CL_CDS_TEST_ENVIRONMENT, CL_RAP_BEHV_TEST_ENVIRONMENT)를 사용해 Edit 액션을 호출하는 방식으로 Draft를 생성해야 한다. 단위 테스트에서도 프레임워크 경로를 준수해야 실제 런타임과 동일한 결과가 보장된다.

이어서 살펴볼 만한 주제

Admin 필드 자동 관리 원칙을 이해했다면, 다음 주제들을 이어서 학습하는 것이 자연스럽다.

  • Unmanaged Draft 시나리오에서 개발자가 직접 구현해야 하는 Save/Load Handler 패턴
  • Draft DeterminationsDraft Actions의 실행 시점 차이
  • Numbering 전략(early/late) + Draft 조합 시의 주의사항
  • Draft-Enabled BO의 성능 튜닝 - 인덱스, 락 오브젝트, 자동 정리 잡(Cleanup Job) 설정
  • Fiori Elements Object Page에서 Draft/Active 사이드바 라우팅 커스터마이징

더 깊이 파고들 수 있는 자료

  • SAP Help Portal - ABAP RAP Draft Concept
  • SAP Help Portal - Draft Administrative Data Fields
  • SAP Help Portal - Behavior Definition Syntax
  • SAP Community - ABAP 및 RAP 태그 토론
  • SAP Blogs - RAP 관련 아키텍처 게시글 모음

댓글 0

아직 댓글이 없습니다.