개요 — 이 글에서 다루는 것
RAP(ABAP RESTful Application Programming Model)로 만든 Fiori Elements 앱에서 할인율을 바꿨는데 총액이 화면에 바로 반영되지 않는 경험, 한 번쯤 있으실 겁니다. 서버의 Determination은 분명히 재계산을 끝냈는데 UI는 옛 값을 보여줍니다. 이 문제를 선언적으로 해결하는 장치가 바로 Side Effects입니다. 이 실전 예제에서는 다음을 다룹니다.
- PATCH 이후 UI가 자동 갱신되지 않는 근본 원인(OData delta response) 이해
- Behavior Definition의
side effects구문과 triggeringProperties / targetProperties / targetEntities 구조 파악 - SalesOrder 할인율 → 총액 자동 갱신, PurchaseRequisition 수량 → 납기·금액 갱신 구현
- Side Effect가 동작하지 않을 때의 디버깅 체크리스트 확보
시작 전에 알아두면 좋은 배경
이 글은 RAP의 Managed 시나리오, CDS View Entity, Behavior Definition(BDEF)과 Determination의 기본 개념을 이미 알고 있다는 전제로 진행합니다. Fiori Elements List Report / Object Page의 동작 방식과 OData V4의 PATCH 요청 흐름을 대략적으로 이해하고 있으면 내용을 따라가기 훨씬 수월합니다.
환경과 버전 준비
BDEF 기반 side effects 구문은 비교적 최근 기능이므로 버전 확인이 중요합니다.
- SAP BTP ABAP Environment(Steampunk) 또는 SAP S/4HANA 2022 이상(ABAP Platform 2022, 온프레미스/Private Cloud) — 일반적으로 이 버전부터 BDEF의
side effects절을 사용할 수 있습니다 - ADT(ABAP Development Tools for Eclipse) 최신 버전
- Draft가 활성화된 RAP Business Object — Side Effects는 Draft 기반 상호작용에서 발동되므로
with draft가 사실상 필수입니다 - 테스트용 Fiori Elements 프리뷰(ADT의 Service Binding에서 Preview) 또는 OData V4 기반 Fiori 앱
핵심 개념 — 왜 UI는 스스로 갱신되지 않는가
Fiori Elements 앱에서 Draft 문서의 필드를 수정하면, 클라이언트는 포커스가 벗어나는 순간 해당 필드만 담은 PATCH 요청을 보냅니다. 서버에서는 Determination이 실행되어 연관 필드(예: 총액)를 재계산하지만, OData 응답은 변경 요청에 포함된 필드 중심의 delta만 돌려줍니다. 즉 서버 DB(Draft 테이블)에는 새 총액이 있어도 클라이언트는 그 사실을 모르는 상태가 됩니다. 화면을 새로고침하면 값이 맞는 이유가 여기에 있습니다.
Side Effects는 이 간극을 메우는 클라이언트를 향한 계약서입니다. "이 필드가 바뀌면, 저 필드(또는 저 엔티티)를 다시 읽어라"라는 규칙을 메타데이터에 실어 보내면, Fiori Elements가 PATCH 직후 대상만 콕 집어 추가 GET 요청을 자동으로 날립니다. 식당에 비유하면, 주문서(PATCH)를 받은 주방이 "이 메뉴를 바꾸면 계산서도 바뀝니다"라는 안내문(Side Effect 어노테이션)을 미리 붙여둔 덕분에, 홀 직원(UI)이 계산서(TotalAmount)를 알아서 다시 확인하러 오는 구조입니다.
OData 메타데이터로 노출되는 SideEffects 어노테이션은 세 가지 축으로 구성됩니다.
| 구성 요소 | 의미 | 예시 |
|---|---|---|
| triggeringProperties | 변경을 감지할 트리거 필드 | DiscountPercent |
| targetProperties | 다시 읽어올 대상 필드 | TotalNetAmount |
| targetEntities | 다시 읽어올 대상 엔티티(연관 포함) | _Item, $self |
대상이 필드 몇 개면 Field-Level Side Effect, 자식 엔티티나 자기 자신 전체를 재조회해야 하면 Entity-Level Side Effect로 구분합니다. RAP이 생성하는 $metadata에는 이 구분이 각각의 qualifier(예: FieldCL, EntityCL 계열 명명)로 표현되는 것이 일반적입니다. 대상 범위가 넓을수록 GET 부하가 커지므로, 가능한 한 필드 단위로 좁게 선언하는 것이 권장됩니다.
실전 코드 3단계
1단계 — 기본: SalesOrder 할인율 변경 시 총액 자동 갱신
판매오더 헤더에서 DiscountPercent를 바꾸면 읽기 전용 필드 TotalNetAmount가 즉시 갱신되도록 만들어 봅니다.
managed implementation in class zbp_r_ordsalesorder unique;
strict ( 2 );
with draft;
define behavior for ZR_OrdSalesOrder alias SalesOrder
persistent table zord_so draft table zord_so_d
lock master total etag LastChangedAt
authorization master ( instance )
{
field ( readonly ) TotalNetAmount, TaxAmount;
determination recalcTotals on modify { field DiscountPercent; }
side effects
{
field DiscountPercent
affects field TotalNetAmount, field TaxAmount;
}
}
Determination 구현부는 단순합니다.
METHOD recalcTotals.
READ ENTITIES OF ZR_OrdSalesOrder IN LOCAL MODE
ENTITY SalesOrder
FIELDS ( GrossAmount DiscountPercent )
WITH CORRESPONDING #( keys )
RESULT DATA(orders).
MODIFY ENTITIES OF ZR_OrdSalesOrder IN LOCAL MODE
ENTITY SalesOrder
UPDATE FIELDS ( TotalNetAmount )
WITH VALUE #( FOR o IN orders
( %tky = o-%tky
TotalNetAmount = o-GrossAmount *
( 1 - o-DiscountPercent / 100 ) ) ).
ENDMETHOD.
포인트는 두 가지입니다. Determination은 서버 값을 바꾸고, Side Effect는 클라이언트에게 재조회를 지시합니다. 둘 중 하나만 있으면 반쪽짜리입니다.
2단계 — 실무: PurchaseRequisition 아이템 수량 변경 + 메시지 갱신
구매요청 아이템에서 OrderedQuantity를 바꾸면 NetAmount와 ExpectedDeliveryDate가 갱신되고, 수량 초과 시 경고 메시지도 화면에 반영되어야 하는 시나리오입니다. messages를 대상에 포함하면 검증 메시지까지 함께 새로고침됩니다.
define behavior for ZR_PurReqnItem alias ReqnItem
{
field ( readonly ) NetAmount, ExpectedDeliveryDate;
determination deriveItemValues on modify { field OrderedQuantity; }
validation checkMaxQuantity on save { field OrderedQuantity; }
side effects
{
field OrderedQuantity
affects field NetAmount,
field ExpectedDeliveryDate,
messages;
}
}
METHOD deriveItemValues.
READ ENTITIES OF ZR_PurReqn IN LOCAL MODE
ENTITY ReqnItem
FIELDS ( OrderedQuantity UnitPrice MaterialGroup )
WITH CORRESPONDING #( keys )
RESULT DATA(items)
FAILED DATA(read_failed).
LOOP AT items INTO DATA(item).
DATA(lead_time) = zcl_pur_leadtime=>get( item-MaterialGroup ).
IF lead_time IS INITIAL.
APPEND VALUE #( %tky = item-%tky ) TO failed-reqnitem.
APPEND VALUE #( %tky = item-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-warning
text = '리드타임 미정 — 납기 산출 불가' ) )
TO reported-reqnitem.
CONTINUE.
ENDIF.
MODIFY ENTITIES OF ZR_PurReqn IN LOCAL MODE
ENTITY ReqnItem
UPDATE FIELDS ( NetAmount ExpectedDeliveryDate )
WITH VALUE #(
( %tky = item-%tky
NetAmount = item-OrderedQuantity * item-UnitPrice
ExpectedDeliveryDate = cl_abap_context_info=>get_system_date( )
+ lead_time ) ).
ENDLOOP.
ENDMETHOD.
reported에 담긴 메시지는 Side Effect의 messages 대상 덕분에 PATCH 직후 Object Page 메시지 팝오버에 바로 나타납니다.
3단계 — 프로덕션: 엔티티 단위 갱신, 성능, 테스트
헤더 할인율이 아이템 전체의 금액에 영향을 주는 경우에는 필드 나열 대신 targetEntities로 연관 엔티티를 지정합니다.
define behavior for ZR_OrdSalesOrder alias SalesOrder
{
side effects
{
field DiscountPercent
affects entity _Item, field TotalNetAmount;
action applyPricing affects $self, entity _Item;
determine action reCalcOrder
executed on field DiscountPercent;
}
}
프로덕션 관점 체크포인트는 다음과 같습니다.
- 성능:
$self나entity _Item은 재조회 범위가 넓어 아이템이 수백 건이면 체감 지연이 생깁니다. 정말 전체 갱신이 필요한지 먼저 따져보고, 가능하면 필드 단위로 좁히는 것이 일반적으로 유리합니다. - 보안: Side Effect가 유발하는 후속 GET에도 인스턴스 권한(
authorization master)이 그대로 적용되는지 확인합니다. 재조회 대상 필드가 권한상 숨겨야 할 값이라면 CDS 접근제어(DCL)를 재점검합니다. - 테스트: Determination 자체는 EML 기반 ABAP Unit으로 검증합니다.
METHOD discount_updates_total.
MODIFY ENTITIES OF ZR_OrdSalesOrder
ENTITY SalesOrder
UPDATE FIELDS ( DiscountPercent )
WITH VALUE #( ( %tky = order_tky DiscountPercent = '10' ) )
FAILED DATA(failed).
cl_abap_unit_assert=>assert_initial( failed ).
READ ENTITIES OF ZR_OrdSalesOrder
ENTITY SalesOrder FIELDS ( TotalNetAmount )
WITH VALUE #( ( %tky = order_tky ) )
RESULT DATA(result).
cl_abap_unit_assert=>assert_equals(
act = result[ 1 ]-TotalNetAmount exp = '900.00' ).
ENDMETHOD.
흔한 실수와 트러블슈팅 FAQ
Q1. Side Effect를 선언했는데 화면이 여전히 갱신되지 않습니다.
가장 흔한 원인 세 가지를 순서대로 확인하세요. (1) Draft 미사용 — Side Effect는 Draft 상호작용의 PATCH 흐름에서 발동되므로 with draft가 없으면 기대대로 동작하지 않는 경우가 많습니다. (2) OData 버전 — V2 UI 서비스에서는 지원 범위가 제한적이므로 OData V4 Service Binding인지 확인합니다. (3) 서비스 캐시 — BDEF 수정 후 $metadata가 갱신되지 않았다면 Service Binding을 다시 활성화하고 브라우저 캐시를 비웁니다.
Q2. 값은 갱신되는데 한 박자 늦게 반영됩니다.
트리거 필드가 잘못 지정된 전형적 증상입니다. 사용자가 실제로 편집하는 필드(예: DiscountPercent)가 side effects의 트리거인지, Determination의 트리거 필드와 일치하는지 대조하세요.
Q3. 브라우저 개발자 도구로 무엇을 봐야 하나요?
Network 탭에서 PATCH 직후 같은 batch 안에 대상 필드를 $select로 요청하는 GET이 따라붙는지 확인하세요. GET 자체가 없으면 어노테이션 노출 문제(메타데이터), GET은 있는데 값이 옛것이면 서버 로직(Determination) 문제로 원인을 양분할 수 있습니다.
Q4. 검증 메시지가 팝오버에 안 나타납니다.
affects 대상에 messages를 빠뜨린 경우가 대부분입니다. 필드 값과 메시지는 별개 대상이므로 각각 선언해야 합니다.
이어서 살펴볼 주제
Side Effects를 익혔다면 다음 주제로 확장해 보세요. Determine Action(executed on 패턴)으로 무거운 재계산을 명시적 시점에 몰아 실행하는 기법, Feature Control과 결합해 필드 변경 시 다른 필드의 편집 가능 여부까지 동적으로 바꾸는 패턴, 그리고 액션·유효성검사와 Side Effect의 상호작용이 자연스러운 다음 단계입니다.
댓글 0
아직 댓글이 없습니다.