RAP

BO 중복 코드 그만 — RAP BO 인터페이스 실전 #shorts #SAP #RAP

▶ YouTube에서 보기

개요 및 이 글에서 다루는 것

SAP RAP(RESTful ABAP Programming Model)에서 동일한 라이프사이클이나 동작 패턴을 가진 Business Object(BO)가 늘어나면, 동일한 동작(action), 결정(determination), 검증(validation) 로직이 여러 BO에 흩어집니다. Business Object Interface(BOI)는 이러한 공통 동작을 추상적으로 정의해 여러 구체 BO에서 재사용하도록 만드는 구조입니다. 이 글에서는 구매주문(PurchaseOrder)과 판매계약(SalesContract)이라는 두 BO가 공통으로 가지는 "결재 승인" 라이프사이클을 BOI로 추출하는 과정을 단계별로 살펴봅니다.

  • BOI의 동작 원리와 abstract BDEF의 구조 이해
  • 공통 행동 추출(action/validation/determination) 설계 방법
  • 실전 시나리오 — PurchaseOrder, SalesContract 동시 적용
  • 프로덕션 환경에서 인터페이스 버전 관리와 테스트 전략
  • 흔히 발생하는 컴파일 오류와 트러블슈팅 포인트

알고 있으면 좋은 배경 지식

이 글은 RAP의 기본 구조(BDEF, Behavior Implementation Class, Projection View)와 managed/unmanaged 시나리오의 차이를 이미 이해하고 있다는 전제하에 작성되었습니다. CDS View Entity, Behavior Definition 문법, EML(Entity Manipulation Language)의 기본 사용법 그리고 ABAP OO에서 인터페이스(interface) 개념에 익숙해야 합니다.

환경과 버전 준비물

BOI는 비교적 최신 RAP 기능으로, 사용하려면 다음 환경이 권장됩니다.

  • ABAP Platform: SAP BTP, ABAP Environment(Steampunk) 2208 릴리스 이상 또는 S/4HANA 2022 이상
  • ADT(Eclipse): 2022-09 이상 (BOI 관련 신택스 하이라이팅 지원)
  • 개발 패키지: ABAP Cloud Development 모델 사용 시 소프트웨어 컴포넌트 ZLOCAL 또는 별도 ABAP Cloud 패키지
  • 참고: 7.57 이전 릴리스에서는 BOI 신택스가 인식되지 않을 수 있습니다

핵심 개념 — BOI는 왜 필요한가

전통적인 RAP 모델에서 두 BO가 동일한 동작을 가진다면, 각 BO의 Behavior Definition과 Behavior Implementation Class에 동일한 로직이 두 번 작성됩니다. 신규 BO인 ServiceQuotation을 추가하면 같은 코드가 세 번 복사됩니다. 이는 전형적인 DRY(Don't Repeat Yourself) 원칙 위반입니다.

BOI를 비유하자면 ABAP OO의 인터페이스(interface)와 유사합니다. OO에서 interface가 메서드 시그니처만 정의하고 구현은 implementing class에 위임하듯, BOI는 액션과 필드 시그니처만 abstract BDEF로 정의하고 실제 구현은 구체 BO가 위임받아 처리합니다.


          [Abstract BDEF: I_ApprovableObject]
          - field: ApprovalStatus
          - action: submitForApproval
          - action: approve / reject
                  |
    +-------------+-------------+
    |                           |
[PurchaseOrder BO]    [SalesContract BO]
 implements interface  implements interface
 + 자체 필드/액션       + 자체 필드/액션

실전 예제 1단계 — 기본 BOI 정의

interface;
strict ( 2 );

define behavior for I_ApprovableObject abstract
{
  field ( read only ) ApprovalStatus;
  field ( read only ) ApprovalRequestedAt;

  action ( features : instance ) submitForApproval
    result [1] ;
  action ( features : instance ) approve
    parameter ApprovalDecisionParam
    result [1] ;
  action ( features : instance ) reject
    parameter ApprovalDecisionParam
    result [1] ;
}

BDEF 첫 줄의 interface 키워드와 abstract 키워드가 핵심입니다. I_ApprovableObject는 실제 테이블에 매핑된 BO가 아니라 abstract CDS view entity로 선언됩니다.

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Approvable Object Interface'
@ObjectModel.supportedCapabilities: [#ABSTRACT_ENTITY]
define abstract entity I_ApprovableObject
{
  key ObjectUUID            : sysuuid_x16;
      ApprovalStatus        : abap.char(2);
      ApprovalRequestedAt   : timestampl;
}

실전 예제 2단계 — PurchaseOrder가 인터페이스를 구현

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

define behavior for ZR_PurchaseOrder alias PurchaseOrder
persistent table zpurchase_order
lock master
authorization master ( instance )
{
  implements interface I_ApprovableObject
    mapping
      ObjectUUID          = PoUuid
      ApprovalStatus      = ApprovalState
      ApprovalRequestedAt = RequestedTimestamp
    actions
      submitForApproval = submit_po_approval
      approve           = approve_po
      reject            = reject_po;

  field ( numbering : managed, read only ) PoUuid;
  field ( mandatory ) Supplier, OrderDate;
  create; update; delete;
}

mapping 절이 핵심입니다. 인터페이스의 추상 필드명을 구체 BO의 실제 필드명으로 매핑합니다. Behavior Implementation Class에서 인터페이스 액션을 구현합니다.

CLASS lhc_purchaseorder DEFINITION INHERITING FROM cl_abap_behavior_handler.
  PRIVATE SECTION.
    METHODS submit_po_approval FOR MODIFY
      IMPORTING keys FOR ACTION PurchaseOrder~submitForApproval
      RESULT result.
ENDCLASS.

CLASS lhc_purchaseorder IMPLEMENTATION.
  METHOD submit_po_approval.
    READ ENTITIES OF ZR_PurchaseOrder IN LOCAL MODE
      ENTITY PurchaseOrder FIELDS ( ApprovalState )
        WITH CORRESPONDING #( keys )
      RESULT DATA(orders) FAILED failed.

    LOOP AT orders ASSIGNING FIELD-SYMBOL(<order>).
      IF <order>-ApprovalState <> 'DR'.
        APPEND VALUE #( %tky = <order>-%tky
                        %msg = new_zcm_approval(
                          severity = if_abap_behv_message=>severity-error
                          textid   = zcm_approval=>invalid_state ) )
          TO reported-purchaseorder.
        CONTINUE.
      ENDIF.
      MODIFY ENTITIES OF ZR_PurchaseOrder IN LOCAL MODE
        ENTITY PurchaseOrder
          UPDATE FIELDS ( ApprovalState RequestedTimestamp )
            WITH VALUE #( ( %tky = <order>-%tky
                            ApprovalState = 'SU'
                            RequestedTimestamp = cl_abap_tstmp=>utclong2tstmp_short(
                                                   utclong_current( ) ) ) ).
    ENDLOOP.
    READ ENTITIES OF ZR_PurchaseOrder IN LOCAL MODE
      ENTITY PurchaseOrder ALL FIELDS WITH CORRESPONDING #( keys )
      RESULT DATA(updated).
    result = VALUE #( FOR row IN updated ( %tky = row-%tky %param = row ) ).
  ENDMETHOD.
ENDCLASS.

실전 예제 3단계 — 인터페이스 EML로 여러 BO 일괄 처리

인터페이스의 진가는 여러 BO를 동일한 EML로 다룰 때 드러납니다.

METHOD if_apj_rt_exec_object~execute.
  MODIFY ENTITIES OF I_ApprovableObject
    ENTITY ApprovableObject
      EXECUTE approve
        FROM VALUE #(
          ( %key-ObjectUUID = lv_po_uuid
            %param = VALUE #( ApprovalDecisionParam = 'A' ) )
          ( %key-ObjectUUID = lv_sc_uuid
            %param = VALUE #( ApprovalDecisionParam = 'A' ) ) )
    FAILED   DATA(failed)
    REPORTED DATA(reported).

  COMMIT ENTITIES RESPONSES
    FAILED DATA(commit_failed) REPORTED DATA(commit_reported).

  IF commit_failed IS NOT INITIAL.
    cl_bali_log_db_writer=>get_instance( )->save_log(
      log = build_failure_log( commit_failed ) ).
  ENDIF.
ENDMETHOD.

MODIFY ENTITIES OF I_ApprovableObject 구문은 런타임에 각 키가 PurchaseOrder인지 SalesContract인지에 따라 적절한 구현체로 디스패치됩니다. 호출자는 BO 종류를 알 필요가 없습니다.

프로덕션 적용 시 권장 사항입니다.

  • 단위 테스트: cl_abap_behv_test_runner로 인터페이스 호출 단위 테스트 작성
  • 권한 분리: 인터페이스 레벨 authorization은 정의 불가 — 각 구체 BO에서 권한 객체 명시
  • 버전 관리: 인터페이스에 필드 추가 시 모든 구현 BO에 mapping 추가 강제 — ATC 검사 후 일괄 배포
  • 성능: 단일 BO 처리 시에는 직접 BO 호출이 인터페이스 디스패치보다 빠릅니다

흔한 실수와 트러블슈팅

Q: "Mapping incomplete for interface field XYZ" 오류가 발생합니다.
A: 인터페이스에 정의된 모든 필드와 액션을 mapping 절에서 매핑해야 합니다. 추상 필드 이름이 BO와 일치하지 않으면 반드시 명시적 매핑이 필요합니다.

Q: abstract entity 정의 후 ADT가 "View has no select clause" 경고를 띄웁니다.
A: 어노테이션 @ObjectModel.supportedCapabilities: [#ABSTRACT_ENTITY]를 추가하고, 필드는 select list 없이 직접 선언해야 합니다.

Q: 인터페이스를 통해 EML 호출하면 dump가 발생합니다.
A: 인터페이스 ObjectUUID에 등록되지 않은 키를 호출했기 때문입니다. CX_RAP_QUERY_PROV_RUNTIME이 발생합니다. SELECT로 존재 여부를 사전 확인하거나 FAILED 응답을 검사하는 방어 코드를 권장합니다.

더 넓은 RAP 아키텍처로 확장하기

BOI를 익혔다면 RAP Generic Consumption — 인터페이스를 받아 동작하는 일반화된 Fiori 앱 작성, Side Effects와 Behavior Pool 분리로 구현 클래스 모듈화, ABAP Unit과 RAP BO Test Double Framework를 활용한 인터페이스 단위 테스트 자동화, 그리고 ABAP Cloud Release Contract 모델에서 인터페이스를 C1/C2 릴리스 콘트랙트로 공개해 확장 가능한 BO API를 설계하는 패턴으로 확장할 수 있습니다.

댓글 0

아직 댓글이 없습니다.