ABAP

RAP Draft 취소 실수 3가지 #shorts #SAP #ABAP

▶ YouTube에서 보기

RAP Draft 메커니즘과 Discard의 의미

ABAP RAP(RESTful Application Programming Model)에서 Draft는 사용자가 작업 중인 데이터를 일시적으로 보관하는 기능입니다. RAP 런타임은 활성(active) 인스턴스와 드래프트(draft) 인스턴스를 분리 관리하며, 두 영역은 %is_draft 플래그로 식별됩니다.

Discard는 사용자가 취소 버튼을 누르거나 세션이 만료되었을 때 드래프트 인스턴스를 제거하는 행위입니다. Fiori Elements UI에서는 OData V4의 DiscardDraft 액션이 자동으로 호출되며, 백엔드에서 다음 순서로 동작합니다.

  1. 드래프트 인스턴스의 모든 자식(composition child) 식별
  2. 드래프트 테이블에서 행 삭제
  3. 락(Lock) 해제 및 Draft Admin Data 정리
  4. 활성 인스턴스가 존재하면 그대로 유지, 신규 드래프트라면 완전 폐기
신규 생성 드래프트와 기존 활성 인스턴스의 편집 드래프트가 다르게 처리된다는 점이 핵심입니다. 이 차이를 간과하면 운영 데이터가 손실되는 사고로 이어집니다.

실수 1: BDEF에 draft action Discard 미등록

가장 빈번한 실수는 BDEF에 draft action을 명시적으로 선언하지 않는 것입니다. RAP는 draft-enabled를 선언해도 Discard, Activate, Edit, Resume 등 표준 draft action을 자동으로 노출하지 않습니다.

// 잘못된 예: draft action 누락
managed implementation in class zbp_i_salesorder unique;
strict ( 2 );
with draft;

define behavior for ZI_SalesOrder alias SalesOrder
persistent table zsalesorder
draft table zsalesorder_d
lock master
authorization master ( instance )
etag master LastChangedAt
{
  create;
  update;
  delete;
  // draft action 선언 없음 → Fiori UI에서 Discard 호출 불가
}
// 올바른 예: 표준 draft action 명시
managed implementation in class zbp_i_salesorder unique;
strict ( 2 );
with draft;

define behavior for ZI_SalesOrder alias SalesOrder
persistent table zsalesorder
draft table zsalesorder_d
lock master total etag LastChangedAt
authorization master ( instance )
etag master LastChangedAt
{
  create;
  update;
  delete;

  draft action Edit;
  draft action Activate optimized;
  draft action Discard;        " 필수 등록!
  draft action Resume;
  draft determine action Prepare;

  field ( numbering : managed, read only ) SalesOrderUUID;
  field ( read only ) SalesOrderID, LastChangedAt;
  association _Items { create; with draft; }
}

실수 2: earlynumbering에서 드래프트 UUID 정리 누락

판매주문에 사람이 읽을 수 있는 번호(SalesOrderID)를 부여하는 경우 earlynumbering을 사용합니다. 드래프트 단계에서 번호를 미리 채번하면, 사용자가 Discard 했을 때 번호 갭(gap)이 생기거나 활성 인스턴스의 번호를 덮어쓰는 사고가 발생합니다.

// 안전한 패턴: determination on save 사용
// on modify 대신 on save → Discard 시 채번 자체가 일어나지 않음
define behavior for ZI_SalesOrder alias SalesOrder
...
{
  field ( numbering : managed, read only ) SalesOrderUUID;
  determination assignSalesOrderID on save { create; }
}
METHOD assignSalesOrderID.
  READ ENTITIES OF zi_salesorder IN LOCAL MODE
    ENTITY SalesOrder
      FIELDS ( SalesOrderID )
      WITH CORRESPONDING #( keys )
    RESULT DATA(orders).

  LOOP AT orders ASSIGNING FIELD-SYMBOL()
       WHERE SalesOrderID IS INITIAL.
    TRY.
        DATA(new_id) = cl_numberrange_runtime=>number_get(
                         nr_range_nr = '01'
                         object      = 'ZSO_NR' )-number.
        -SalesOrderID = |SO-{ new_id ALPHA = OUT }|.
      CATCH cx_nr_object_not_found
            cx_number_ranges INTO DATA(lx).
        APPEND VALUE #(
          %tky = -%tky
          %msg = new_message_with_text(
                   severity = if_abap_behv_message=>severity-error
                   text     = lx->get_text( ) ) ) TO reported-salesorder.
        CONTINUE.
    ENDTRY.

    MODIFY ENTITIES OF zi_salesorder IN LOCAL MODE
      ENTITY SalesOrder
        UPDATE FIELDS ( SalesOrderID )
        WITH VALUE #( ( %tky         = -%tky
                        SalesOrderID = -SalesOrderID ) ).
  ENDLOOP.
ENDMETHOD.

실수 3: finalize 단계 원본 복원 미검증

편집 드래프트를 Discard 할 때 가장 위험한 시나리오는 원본 활성 인스턴스가 드래프트 변경값으로 오염되는 경우입니다. save_modified에서 ETag(LastChangedAt) 검증으로 충돌을 감지하고 원본을 보호합니다.

METHOD finalize.
  " Discard 직전 호출 — 임시 리소스 정리 위치
  LOOP AT keys ASSIGNING FIELD-SYMBOL().
    IF -%is_draft = if_abap_behv=>mk-on.
      cl_zso_temp_attach=>cleanup_for_draft(
        iv_draft_uuid = -SalesOrderUUID ).
    ENDIF.
  ENDLOOP.
ENDMETHOD.

METHOD save_modified.
  IF update-salesorder IS NOT INITIAL.
    LOOP AT update-salesorder ASSIGNING FIELD-SYMBOL().
      UPDATE zsalesorder
        SET LastChangedAt = @cl_abap_context_info=>get_system_date( )
        WHERE SalesOrderUUID = @-SalesOrderUUID
          AND LastChangedAt  = @-LastChangedAt. " 낙관적 락
      IF sy-subrc <> 0.
        " ETag 불일치 → 충돌 감지, 원본 보호됨
      ENDIF.
    ENDLOOP.
  ENDIF.
ENDMETHOD.

Discard 직후에는 save_modified가 호출되지 않으므로 원본은 자동으로 보호됩니다. finalize에서 임시 첨부파일이나 락 같은 부수 리소스만 정리하면 됩니다.

올바른 BDEF + Behavior Implementation 구조

완전한 Draft-enabled 판매주문 BDEF는 다음 요소를 모두 포함해야 합니다.

  • lock master total etag — 전체 계층 락 + ETag 설정
  • draft action 4종 (Edit, Activate optimized, Discard, Resume) 명시
  • draft determine action Prepare — 활성화 전 validation 트리거
  • 자식 association에 with draft 양방향 선언
  • determination on save — 채번을 Activate 시점으로 지연

Draft Admin Data 이해와 %is_draft 플래그

%is_draft는 EML 조회 시 활성/드래프트를 구분하는 핵심 플래그입니다. READ ENTITIES 시 MAPPING %is_draft = %is_draft를 명시하지 않으면 의도치 않게 두 영역 데이터가 혼합될 수 있습니다. Draft Admin Data(DRAFTADMINISTRATIVEDATA)는 시스템이 자동 관리하며, 잠금 소유자, 마지막 변경 시각, 만료 시각을 포함합니다.

운영 환경에서 고아 드래프트 처리

RAP는 기본 28일 만료 시간을 가지며, 표준 잡 RAP_DRAFT_CLEANUP_JOB을 스케줄링해야 합니다. 클라우드 환경에서는 Application Jobs 앱에서 "Cleanup of Draft Data" 템플릿으로 등록합니다. 고아 드래프트가 쌓이면 드래프트 테이블이 비대해지고 Draft Lock 조회 성능에 영향을 줍니다.

Discard 후 데이터 정합성 확인 체크리스트

  • BDEF draft action Discard 선언 확인
  • 자식 엔티티 association에 with draft 양방향 확인
  • earlynumbering을 on save로 변경 (on modify 사용 금지)
  • finalize에서 임시 리소스 정리 로직 추가
  • save_modified ETag 검증으로 낙관적 락 적용
  • RAP_DRAFT_CLEANUP_JOB 스케줄 등록 확인

흔한 실수와 트러블슈팅

  • Discard 후에도 드래프트 테이블에 행이 남음: 자식 BDEF에 with draft, association에 with draft가 양방향으로 선언되어야 cascade discard가 동작합니다.
  • Fiori에서 Discard 버튼이 보이지 않음: draft action Discard 선언 누락 또는 V2 Service Binding 사용. OData V4 바인딩 권장.
  • earlynumbering 번호 갭 누적: on modify 대신 determination on save로 변경하거나 Activate 시점에 최종 번호 치환.
  • 고아 드래프트 누적: Application Jobs에서 Cleanup of Draft Data 잡 등록 필수.

댓글 0

아직 댓글이 없습니다.