RAP Draft 메커니즘과 Discard의 의미
ABAP RAP(RESTful Application Programming Model)에서 Draft는 사용자가 작업 중인 데이터를 일시적으로 보관하는 기능입니다. RAP 런타임은 활성(active) 인스턴스와 드래프트(draft) 인스턴스를 분리 관리하며, 두 영역은 %is_draft 플래그로 식별됩니다.
Discard는 사용자가 취소 버튼을 누르거나 세션이 만료되었을 때 드래프트 인스턴스를 제거하는 행위입니다. Fiori Elements UI에서는 OData V4의 DiscardDraft 액션이 자동으로 호출되며, 백엔드에서 다음 순서로 동작합니다.
- 드래프트 인스턴스의 모든 자식(composition child) 식별
- 드래프트 테이블에서 행 삭제
- 락(Lock) 해제 및 Draft Admin Data 정리
- 활성 인스턴스가 존재하면 그대로 유지, 신규 드래프트라면 완전 폐기
신규 생성 드래프트와 기존 활성 인스턴스의 편집 드래프트가 다르게 처리된다는 점이 핵심입니다. 이 차이를 간과하면 운영 데이터가 손실되는 사고로 이어집니다.
실수 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
아직 댓글이 없습니다.