1. Additional Binding이 필요한 이유 — 실무에서 마주치는 상황
SAP S/4HANA Cloud 또는 ABAP Platform 2023 이상에서 RAP(RESTful Application Programming Model)로 Business Object를 개발하다 보면, 동일한 BO를 여러 채널에 노출해야 하는 요구가 자주 발생합니다. 예를 들어 영업 부서는 Fiori Elements UI로 SalesOrder를 조회·편집하고, 외부 파트너 시스템은 OData V4 API로 같은 SalesOrder를 읽어가며, 분석팀은 InA(Information Access) 프로토콜로 집계 데이터를 가져가야 하는 경우입니다.
이때 매번 BO를 복제해서 별도로 만든다면 비즈니스 로직(Determination, Validation, Action)이 여러 곳에 분산되어 유지보수가 어려워집니다. RAP의 Additional Binding 기능은 하나의 BO와 Behavior Definition을 그대로 둔 상태에서, 동일한 Service Definition을 여러 프로토콜·여러 Service Binding에 동시 노출할 수 있게 해 줍니다.
- 동일 BO를 UI / API / Analytics에 분리 노출
- 프로토콜별 권한·필드 노출을 분리 관리
- 외부 파트너용 버전(v1)과 내부 신규 버전(v2)을 병행 운영
- 비즈니스 로직 단일 소스(Single Source of Truth) 유지
2. RAP Service Layer 아키텍처와 Binding 개념의 동작 원리
RAP의 Service Layer는 크게 세 단계로 구성됩니다. Service Definition(SD)은 어떤 CDS 엔터티를 노출할지 선언하는 논리적 정의이고, Service Binding(SB)은 그 정의를 특정 프로토콜(OData V2 UI, OData V4 UI, OData V4 API, InA)에 매핑하는 물리적 엔드포인트입니다. 그 사이를 잇는 것이 Behavior Definition(BDEF)으로, BO의 동작과 권한 제약을 기술합니다.
비유하자면 Service Definition은 "메뉴판", Service Binding은 "배달 경로"입니다. 하나의 메뉴판(SD)으로 매장 내 키오스크(UI Binding)와 배달앱(API Binding)에 동시에 음식을 내보낼 수 있는 구조입니다. Additional Binding은 동일한 메뉴판을 또 다른 채널에 연결하는 행위에 해당합니다.
참고: ABAP Platform 2022 이전 버전에서는 하나의 SD에 단일 Binding만 권장되었으나, 2023 이후 ADT(ABAP Development Tools)에서 Additional Binding을 명시적으로 지원합니다. 자세한 호환성은 사용 중인 릴리스 노트를 확인하는 것이 일반적입니다.
3. 재사용할 Business Object 설계 — SalesOrder BO 예제
먼저 재사용 대상이 될 SalesOrder BO의 Root 엔터티를 정의합니다. 실무에서는 헤더-아이템 구조이지만, 본 예제에서는 헤더 중심으로 다룹니다.
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Root View'
define root view entity ZR_SalesOrderTM
as select from zsorder_hdr_tm
composition [0..*] of ZR_SalesOrderItemTM as _Items
{
key sales_order_id as SalesOrderId,
customer_id as CustomerId,
order_date as OrderDate,
currency_code as CurrencyCode,
@Semantics.amount.currencyCode: 'CurrencyCode'
total_net_amount as TotalNetAmount,
overall_status as OverallStatus,
created_by as CreatedBy,
created_at as CreatedAt,
last_changed_by as LastChangedBy,
last_changed_at as LastChangedAt,
@Semantics.systemDateTime.localInstanceLastChangedAt: true
local_last_changed_at as LocalLastChangedAt,
_Items
}
다음으로 Behavior Definition을 작성합니다. 권한 제약과 Action은 여기 한 곳에만 정의되며, 이후 어떤 Service에 노출되든 동일하게 적용됩니다.
managed implementation in class zbp_r_salesordertm unique;
strict ( 2 );
define behavior for ZR_SalesOrderTM alias SalesOrder
persistent table zsorder_hdr_tm
lock master
authorization master ( instance )
etag master LocalLastChangedAt
{
create;
update;
delete;
field ( readonly ) SalesOrderId, CreatedBy, CreatedAt;
field ( mandatory ) CustomerId, CurrencyCode;
action ( features : instance ) approveOrder result [1] $self;
action ( features : instance ) rejectOrder result [1] $self;
determination calculateTotal on modify { field _Items; }
validation validateCustomer on save { field CustomerId; }
mapping for zsorder_hdr_tm
{
SalesOrderId = sales_order_id;
CustomerId = customer_id;
TotalNetAmount = total_net_amount;
OverallStatus = overall_status;
}
association _Items { create; }
}
4. 첫 번째 Service Definition — 외부 파트너 API용
외부 파트너 시스템에 노출할 API용 Projection View와 Service Definition을 작성합니다. 민감한 내부 필드는 제외하고, 읽기 중심으로 설계합니다.
@EndUserText.label: 'Sales Order API Projection'
@AccessControl.authorizationCheck: #CHECK
define root view entity ZC_SalesOrderApiTM
provider contract transactional_query
as projection on ZR_SalesOrderTM
{
key SalesOrderId,
CustomerId,
OrderDate,
CurrencyCode,
TotalNetAmount,
OverallStatus,
_Items : redirected to composition child ZC_SalesOrderItemApiTM
}
@EndUserText.label: 'Sales Order Partner API'
define service ZUI_SALES_ORDER_API_TM {
expose ZC_SalesOrderApiTM as SalesOrder;
expose ZC_SalesOrderItemApiTM as SalesOrderItem;
}
5. 두 번째 Service Definition — Fiori UI 노출용
같은 BO를 Fiori Elements 화면에 노출할 때는 UI Annotation이 풍부한 별도 Projection을 사용하는 것이 권장됩니다. 헤더와 동일한 BO를 가리키지만, 편집 가능 필드와 어노테이션 구성이 다릅니다.
@EndUserText.label: 'Sales Order UI Projection'
@AccessControl.authorizationCheck: #CHECK
@Metadata.allowExtensions: true
define root view entity ZC_SalesOrderUiTM
provider contract transactional_query
as projection on ZR_SalesOrderTM
{
key SalesOrderId,
CustomerId,
@Search.defaultSearchElement: true
OrderDate,
CurrencyCode,
TotalNetAmount,
OverallStatus,
CreatedBy,
CreatedAt,
LastChangedBy,
LastChangedAt,
_Items : redirected to composition child ZC_SalesOrderItemUiTM
}
@EndUserText.label: 'Sales Order Fiori Service'
define service ZUI_SALES_ORDER_UI_TM {
expose ZC_SalesOrderUiTM as SalesOrder;
expose ZC_SalesOrderItemUiTM as SalesOrderItem;
}
두 Service Definition은 서로 다른 Projection을 노출하지만, 그 아래 동일한 Root BO(ZR_SalesOrderTM)와 Behavior Definition을 공유합니다. 비즈니스 로직 변경은 한 번이면 충분합니다.
6. Service Binding 생성과 Additional Binding 설정
ADT에서 Service Binding을 생성할 때, 동일한 Service Definition을 가리키는 두 번째 Binding을 만드는 방식이 Additional Binding의 핵심입니다. UI용 Binding은 ODATA V4 UI 카테고리로, API용은 ODATA V4 API 카테고리로 각각 만듭니다.
<ServiceBinding name="ZUI_SALES_ORDER_API_TM_O4">
<BindingType>ODATA_V4_API</BindingType>
<ServiceDefinition>ZUI_SALES_ORDER_API_TM</ServiceDefinition>
<ServiceVersion>0001</ServiceVersion>
</ServiceBinding>
<ServiceBinding name="ZUI_SALES_ORDER_UI_TM_O4">
<BindingType>ODATA_V4_UI</BindingType>
<ServiceDefinition>ZUI_SALES_ORDER_UI_TM</ServiceDefinition>
<ServiceVersion>0001</ServiceVersion>
</ServiceBinding>
실무 시나리오에서 자주 사용하는 패턴은 같은 SD를 OData V2 UI와 OData V4 UI에 동시 바인딩하는 것입니다. 레거시 SAPUI5 5.x 앱은 V2를, 신규 Fiori Elements V4 앱은 V4를 사용하므로 Additional Binding으로 이 둘을 병행할 수 있습니다.
" 동일한 Service Definition을 두 프로토콜에 노출
" Binding 1: ZUI_SALES_ORDER_UI_TM_O2 (ODATA_V2_UI) — 레거시 화면
" Binding 2: ZUI_SALES_ORDER_UI_TM_O4 (ODATA_V4_UI) — 신규 Fiori Elements
" 둘 다 ZUI_SALES_ORDER_UI_TM Service Definition을 가리킴
" → BO 로직 변경 시 양쪽 모두 자동 반영
7. Authorization 분리와 보안 처리 — 프로덕션 관점
같은 BO를 외부 API와 내부 UI에 노출하면 권한 분리가 핵심 과제가 됩니다. Behavior Definition의 authorization master 구현 클래스에서 호출 컨텍스트를 식별하여 채널별 권한을 분기 처리합니다.
CLASS lhc_salesorder DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations
FOR SalesOrder RESULT result.
ENDCLASS.
CLASS lhc_salesorder IMPLEMENTATION.
METHOD get_instance_authorizations.
DATA(lv_caller) = cl_abap_context_info=>get_user_technical_name( ).
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
" 외부 API 호출인지 내부 UI 호출인지 권한 객체로 분기
AUTHORITY-CHECK OBJECT 'Z_SO_CHN'
ID 'ACTVT' FIELD '03'
ID 'CHANNEL' FIELD 'API'.
IF sy-subrc <> 0.
APPEND VALUE #( %tky = <key>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |API 채널 접근 권한이 없습니다| )
) TO reported-salesorder.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
또한 외부 API 노출용 SD에서는 민감 필드(예: 내부 마진율, 담당자 평가 코드)를 Projection 단계에서 제외하는 것이 일반적입니다. UI용 Projection에만 해당 필드를 포함시키면, 같은 BO를 사용해도 API 스키마에서는 자동으로 숨겨집니다.
운영 환경 자주 묻는 질문
- Q1. 두 Service Binding의 ETag가 충돌하지 않나요? ETag는 BO 레벨(LocalLastChangedAt)에서 관리되므로 어떤 Binding을 통해 변경되더라도 일관성이 유지됩니다.
- Q2. Draft 활성화 BO를 API에 그대로 노출해도 되나요? Draft는 UI 시나리오를 위한 기능이라 외부 API에는 권장되지 않습니다. API용 Projection에서 Draft를 비활성화하거나 별도 Behavior Projection을 사용하는 방식을 검토하세요.
- Q3. Activation 시 "Service Binding inconsistent" 오류가 납니다. Projection의 필드 노출 범위와 BDEF의 mapping이 일치하지 않을 때 자주 발생합니다. ADT에서 SD를 먼저 활성화한 후 Binding을 다시 게시(Publish)하면 해결되는 경우가 많습니다.
- Q4. 같은 BO에서 액션을 한쪽 Binding에만 노출하고 싶다면? Behavior Projection을 두 개로 분리하여, UI Projection에는 액션을 use하고 API Projection에는 use하지 않으면 됩니다.
8. 운영 환경에서의 버전 관리 전략과 확장 방향
외부 파트너에게 노출된 API는 한 번 공개되면 계약(Contract)이 되므로, 호환성을 깨지 않으면서 진화시키는 전략이 필요합니다. Additional Binding은 이때 강력한 도구가 됩니다. 기존 v1 Service Binding은 그대로 유지하면서, 새로운 필드와 동작을 담은 v2 Service Definition을 만들고 별도 Binding으로 노출하는 패턴이 권장됩니다.
- v1: ZUI_SALES_ORDER_API_TM (기존 파트너 호환)
- v2: ZUI_SALES_ORDER_API_V2_TM (신규 필드 + 추가 액션)
- 두 SD 모두 동일한 ZR_SalesOrderTM BO를 참조 — 로직 단일화
이후 단계로는 RAP BO Test Double Framework로 두 Binding 모두를 대상으로 통합 테스트를 작성하고, gCTS(Git-enabled Change and Transport System)를 통해 Binding별 전송 단위를 분리 관리하는 것이 일반적인 흐름입니다. 분석 시나리오까지 확장하려면 동일 BO에 Analytical Query를 추가하여 InA Binding을 한 번 더 붙이는 방향도 검토할 수 있습니다.
댓글 0
아직 댓글이 없습니다.