RAP

RAP Draft 30초 — Lock UX 자동으로 잡힙니다 #shorts #SAP #RAP

▶ YouTube에서 보기

1. RAP Draft의 역할 — 왜 존재하는가

SAP RAP(RESTful ABAP Programming Model)에서 Draft는 단순한 "임시 저장" 기능이 아닙니다. Fiori Elements UI와 OData V4 백엔드 사이의 상태 관리 계층으로, 사용자가 화면에서 값을 입력하는 동안 그 변경 사항을 서버 측에 안전하게 보관하면서도 다른 사용자의 동시 편집을 차단하고, 탭을 닫았다 다시 열어도 작업을 이어서 할 수 있게 해 줍니다. ABAP Cloud / S/4HANA 2022 이후 표준 RAP 가이드에서는 "Edit" 동작이 곧 Draft 생성으로 매핑되며, 이는 SAP가 표준 비즈니스 객체(MM, SD 등)에서도 유지하고 있는 패턴입니다.

이 글에서 다룰 체크포인트는 다음과 같습니다.

  • Draft 없는 BO를 만들 때 잃게 되는 UX 안전망 식별
  • ETag와 Optimistic Concurrency Control의 동작 흐름 이해
  • without draft Behavior Definition에서 ETag만으로 충돌을 막는 코딩
  • InvoiceItem 시나리오로 with draft / without draft 두 케이스 비교
  • 나중에 Draft를 추가해야 할 시점을 판단하는 기준

2. Draft 없이 개발할 때 사라지는 UX 안전망

Draft를 끄고 RAP BO(Business Object)를 만들면 코드량은 줄지만, 그 대가로 다음 세 가지가 동시에 사라집니다.

  1. Lock 메커니즘의 약화 — Draft 모드에서는 사용자가 Edit 버튼을 누르는 순간 ABAP Lock(Enqueue)이 걸리고, Draft Table이 잠금의 수명을 관리합니다. Draft가 없으면 Lock은 트랜잭션 한 번(MODIFY ENTITIES 한 호출) 내에서만 유지되며, 사용자가 화면에서 30분 동안 입력 중이어도 백엔드는 그 사실을 알지 못합니다.
  2. 다중 필드 트랜잭션의 부재 — 사용자가 InvoiceItem의 수량, 단가, 할인율을 차례로 변경할 때 Draft 없는 BO는 매 필드 변경을 개별 PATCH로 직접 DB에 반영합니다. 중간에 검증 실패가 나도 이미 일부 값은 커밋된 상태입니다.
  3. 탭 복구 불가 — 사용자가 브라우저를 닫으면 입력 내용이 모두 사라집니다. Fiori Elements의 "변경된 작업이 있습니다" 복구 다이얼로그는 Draft가 있어야 동작합니다.

특히 동시 편집 충돌은 잘 드러나지 않는 함정입니다. 두 사용자가 같은 InvoiceItem을 거의 동시에 수정할 때, 마지막 Save가 앞선 변경을 조용히 덮어쓰는 "Last Writer Wins" 문제가 발생합니다. 이를 막는 최소한의 방어선이 ETag입니다.

3. Optimistic Lock과 ETag의 작동 원리

비관적 잠금(Pessimistic Lock)은 "내가 편집을 시작했으니 다른 사람은 못 만진다"를 선언하는 방식이며, RAP에서는 Draft + Enqueue 조합이 이 역할을 합니다. 반면 낙관적 잠금(Optimistic Lock)은 "충돌이 거의 안 일어날 거라 가정하되, 일어났을 때만 거부한다"는 전략이며, 이때 사용하는 토큰이 ETag(Entity Tag)입니다.

흐름은 다음과 같이 진행됩니다.

  • 클라이언트가 GET을 보내면 서버는 응답 헤더에 ETag: "20260621093015" 같은 값을 함께 돌려줍니다.
  • 클라이언트가 PATCH/PUT/DELETE를 보낼 때 If-Match: "20260621093015" 헤더를 포함합니다.
  • 서버는 DB의 현재 LocalChangedAt과 헤더 값을 비교합니다. 다르면 HTTP 412 Precondition Failed를 반환하고, 같으면 변경을 진행한 뒤 새 ETag를 돌려줍니다.

RAP Behavior Definition에서 ETag는 두 가지 모드로 정의됩니다. etag master는 루트 엔티티의 필드를 기준 토큰으로 삼고, etag dependent by _Parent는 자식 엔티티가 부모의 ETag를 따라가게 합니다.

4. Draft 없이도 가능한 것 vs 불가능한 것

기능with draftwithout draft + ETag
동시 편집 충돌 감지가능 (Lock)가능 (412 응답)
편집 중 자동 저장가능불가
탭 복구 / Continue editing가능불가
검증 후 일괄 저장가능제한적
Side Effects 다단계풍부단순 trigger만
대용량 BO 부담Draft 테이블 부하 있음적음

5. with draft vs without draft Behavior Definition 비교

InvoiceItem 엔티티를 예로 두 가지 정의를 나란히 보겠습니다. 먼저 without draft 버전입니다.

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

define behavior for ZI_InvoiceItem alias InvoiceItem
persistent table zinvoice_item
lock master
authorization master ( instance )
etag master LocalChangedAt
{
  create;
  update;
  delete;

  field ( readonly ) ItemUUID, InvoiceUUID,
                     CreatedAt, CreatedBy,
                     LocalChangedAt, LocalChangedBy;

  field ( mandatory ) ProductCode, Quantity, UnitPrice;

  validation validateQuantity on save { create; update; field Quantity; }
  determination calcLineAmount on modify { field Quantity, UnitPrice, DiscountRate; }
}

핵심은 etag master LocalChangedAt 한 줄입니다. 이 선언만으로 RAP 런타임은 모든 update / delete 호출에서 If-Match 헤더의 값과 DB의 LocalChangedAt을 비교해 충돌 시 412를 반환합니다.

같은 BO에 Draft를 더하면 다음과 같이 바뀝니다.

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

define behavior for ZI_InvoiceItem alias InvoiceItem
persistent table zinvoice_item
draft table zinvoice_item_d
lock master total etag LocalChangedAt
authorization master ( instance )
etag master LocalChangedAt
{
  create;
  update;
  delete;

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

  field ( readonly ) ItemUUID, InvoiceUUID,
                     CreatedAt, CreatedBy,
                     LocalChangedAt, LocalChangedBy;
}

with draft, draft table, 그리고 네 개의 draft action이 추가됐고, Lock의 수명이 Draft 트랜잭션 전체로 확장됩니다.

6. ETag 기반 낙관적 잠금 구현 — Draft 없이도 안전하게

CLASS lhc_invoice_item IMPLEMENTATION.

  METHOD save.
    DATA: lv_now TYPE timestampl.

    GET TIME STAMP FIELD lv_now.

    LOOP AT update-invoice_item ASSIGNING FIELD-SYMBOL(<ls_change>).
      <ls_change>-local_changed_at = lv_now.
    ENDLOOP.

    MODIFY zinvoice_item FROM TABLE @update-invoice_item.
    IF sy-subrc <> 0.
      APPEND VALUE #( %key = update-invoice_item[ 1 ]-%key
                      %msg = new_message_with_text(
                              severity = if_abap_behv_message=>severity-error
                              text     = 'DB update failed' ) ) TO reported-invoice_item.
    ENDIF.
  ENDMETHOD.

ENDCLASS.

ETag 비교는 RAP 런타임이 update 메서드 진입 전에 이미 수행합니다. Handler에 진입했다면 If-Match 검사를 통과한 상태이므로, Handler 코드는 LocalChangedAt 갱신과 DB 쓰기에만 집중하면 됩니다.

// 1. 현재 상태와 ETag 조회
const getResp = await fetch(`/odata/v4/InvoiceService/InvoiceItem(${itemId})`);
const etag = getResp.headers.get("ETag");

// 2. 변경 후 PATCH (If-Match 헤더 포함)
const patchResp = await fetch(
  `/odata/v4/InvoiceService/InvoiceItem(${itemId})`,
  {
    method: "PATCH",
    headers: { "Content-Type": "application/json", "If-Match": etag },
    body: JSON.stringify({ Quantity: 10, DiscountRate: 0.05 }),
  }
);

if (patchResp.status === 412) {
  console.warn("Concurrent modification detected — reload and retry");
}

7. Draft 미사용이 적합한 케이스 vs 피해야 할 케이스

  • 적합: 마스터 데이터 단순 CRUD(코드 마스터, 환율표), Action 중심 BO(승인/반려), 외부 시스템 통합용 OData 서비스, 단일 필드만 수정하는 인라인 편집형 List Report.
  • 피해야 함: Object Page에서 사용자가 여러 필드를 입력하는 양식 화면, 자식 테이블을 함께 편집하는 헤더-아이템 구조(예: 송장 + InvoiceItem), 검증 후 한 번에 Save하는 비즈니스 트랜잭션.

헤더-아이템 구조에서 Draft를 빼면 사용자가 InvoiceItem 5건을 입력하던 중 한 건이 검증 실패할 경우 이미 저장된 4건을 어떻게 롤백할지 직접 설계해야 합니다.

자주 묻는 문제들

  • Q. ETag 헤더가 빈 값으로 옵니다. Behavior Definition에 etag master <field> 선언이 누락됐거나, 해당 필드가 Service Definition에 expose되지 않은 경우입니다.
  • Q. 412 대신 200이 떨어집니다. 클라이언트가 If-Match를 보내지 않은 케이스가 가장 흔합니다. /IWFND/ERROR_LOG에서 요청 헤더를 확인하세요.
  • Q. LocalChangedAt이 자동 갱신되지 않습니다. Managed BO에서도 필드 이름이 정확히 LocalChangedAt이고 readonly로 선언돼야 런타임이 채워줍니다.

8. 정리 — Draft 추가가 필요한지 판단하는 체크리스트

  1. 사용자가 한 화면에서 3개 이상의 필드를 입력하는가
  2. 입력 도중 자리를 비웠다가 돌아오는 시나리오가 자주 발생하는가
  3. 두 명 이상이 같은 인스턴스를 거의 동시에 편집할 가능성이 있는가
  4. 헤더-아이템 같은 부모-자식 구조에서 자식을 여러 건 추가/삭제하는가
  5. Side Effects가 사용자 입력 사이에 백엔드 계산을 트리거해야 하는가

이 중 두 개 이상에 해당한다면, ETag만으로는 부족하고 Draft 도입을 적극 검토해야 합니다. 반대로 모두 해당하지 않는 단순 마스터 데이터라면 without draft + etag master 조합이 가장 가볍고 성능 면에서도 유리합니다.

댓글 0

아직 댓글이 없습니다.