1. SAP OData 서비스 개발의 역사와 패러다임 전환
SAP의 ABAP 기반 서비스 개발은 지난 15년간 큰 변화를 겪었습니다. 초기 SAP NetWeaver Gateway가 등장하면서 SAP는 SOAP 기반 RFC/BAPI 호출에서 벗어나 OData(REST 기반) 프로토콜을 표준 채택했고, S/4HANA가 등장하면서 in-memory 컬럼 스토어와 CDS(Core Data Services)를 중심으로 한 데이터 모델링이 자리잡았습니다.
이 글에서 다룰 RAP(RESTful ABAP Programming Model)은 그 흐름의 정점에 위치한 모델로, ABAP Platform 1909 이후부터 SAP가 권장하는 표준 비즈니스 객체 개발 프레임워크입니다. RAP를 이해하려면 그 직전 세대인 BOPF(Business Object Processing Framework)가 어떤 한계를 가졌고, 왜 SAP가 새로운 모델을 만들어야 했는지를 먼저 짚어볼 필요가 있습니다.
- BOPF의 동작 원리와 메타데이터 기반 처리 방식 이해
- RAP가 CDS 중심 아키텍처를 채택한 배경
- Behavior Definition(BDEF)과 Behavior Implementation(BIL)의 분리 원칙
- Service Definition / Service Binding을 통한 OData 노출
- BOPF 레거시 프로젝트를 RAP로 전환할 때 주의해야 할 포인트
2. 이 글을 읽기 전에 알아두면 좋은 기반 지식
이 글은 beginner 레벨을 대상으로 하지만, ABAP OO(클래스/인터페이스), CDS View 기본 문법(@Annotation, association, projection), 그리고 OData v2/v4 개념(Entity Set, Navigation Property)에 대한 기초 이해가 있으면 훨씬 빠르게 흡수할 수 있습니다. SAP Fiori Elements가 어떤 메타데이터를 소비하는지 한번이라도 접해본 경험이 있다면 RAP의 설계 의도가 매우 자연스럽게 다가올 것입니다.
3. 실습 환경과 권장 버전
RAP는 ABAP 릴리즈와 강하게 결합되어 있으므로 버전 확인이 가장 중요합니다. 일반적으로 다음 환경에서 실습이 권장됩니다.
- 플랫폼: SAP BTP ABAP Environment (Steampunk) 최신 릴리즈, 또는 S/4HANA 2022 이상의 ABAP Platform
- IDE: ABAP Development Tools(ADT) for Eclipse 3.30 이상
- 모델 종류: Managed / Unmanaged / Managed with unmanaged save — 본 예제는 Managed 시나리오 기준
- 전제 패키지: 소프트웨어 컴포넌트 ZLOCAL 또는 Cloud Development 패키지 권한
- 샘플 데이터: 데모용 ZSALES_ORDER 테이블(주문번호, 고객, 금액, 상태 컬럼)
온프레미스 시스템의 경우 ABAP Platform 1909 이전 릴리즈는 RAP가 부분적으로만 지원되거나 미지원이므로 RAP 학습용으로는 Cloud Trial 환경이 일반적으로 더 안정적인 출발점이 됩니다.
4. BOPF는 어떻게 동작했는가 — 메타데이터 트리와 액션 디스패처
BOPF는 2010년대 초반부터 SAP CRM, Travel Management, S/4HANA 초기 모델에서 비즈니스 객체의 표준 백엔드 프레임워크로 사용되었습니다. 동작 방식은 비유하자면 "트랜잭션을 처리하는 거대한 디스패처 엔진"에 가깝습니다.
- Configuration: BOBT 트랜잭션에서 노드(Node), 액션(Action), Determination, Validation을 GUI 형태로 등록
- Runtime: /BOBF/IF_TRA_SERVICE_MANAGER 인터페이스를 통해 호출, 내부적으로 메타데이터를 읽어 해당 클래스 메서드를 디스패치
- Persistence: BOPF가 직접 DB INSERT/UPDATE/DELETE 수행, 트랜잭션 버퍼는 프레임워크 내장
BOPF의 강점은 일관된 트랜잭션 처리와 풍부한 lifecycle 훅이었습니다. 그러나 단점도 명확했습니다.
- 설정이 SE80/BOBT GUI에 의존적이라 Git 기반 형상관리가 어려움
- CDS와 결합도가 낮아 OData 서비스(SADL) 노출 시 별도 매핑 레이어 필요
- 클라우드 환경(Steampunk)에서 사용 제한 — 일부 BOPF API가 not released
- Fiori Elements가 요구하는 메타데이터(@UI annotation, Draft 등)와 자연스럽게 통합되지 않음
특히 Fiori Elements의 Draft 패턴(편집 중 임시저장)을 BOPF로 구현하면 별도 Draft Table과 BOPF Side-by-side 로직을 직접 짜야 했고, 이는 매우 큰 부담이었습니다.
5. RAP는 왜 만들어졌는가 — One-Stack Model의 약속
SAP가 RAP를 발표하면서 강조한 슬로건이 "One development model for both cloud and on-premise"였습니다. 이 한 줄에 RAP 설계 철학이 모두 담겨 있습니다.
- CDS 중심: 데이터 모델(Entity)과 UI 메타데이터(@UI), 권한(@AccessControl)을 CDS에 모두 선언
- Behavior Definition 분리: 동작(생성/수정/삭제/액션)은 별도의 BDEF 파일로 선언적 작성
- Cloud-ready: 모든 RAP API는 released 상태로 명시적 관리, Steampunk에서 동일하게 동작
- Draft 내장: managed draft 키워드 한 줄로 Fiori Elements Draft 완전 지원
- 코드 기반 형상관리: BDEF/Service Definition이 텍스트 파일이므로 abapGit으로 관리 용이
아키텍처를 비유하자면, BOPF가 "GUI 설정과 트랜잭션 엔진을 합친 거대한 디스패처"였다면, RAP는 "CDS 모델 위에 선언형 동작 명세를 얹은 얇은 명세 레이어"에 가깝습니다.
[ Service Binding ] -- OData v2/v4, Web API or UI
|
[ Service Definition ] -- 어떤 entity를 노출할지 선언
|
[ Projection View (C_) ] -- @UI 어노테이션, 필드 선택
|
[ Behavior Projection ] -- 노출할 동작 정의
|
[ Behavior Definition (BDEF) ] -- create/update/delete/action
|
[ Behavior Implementation (BIL) ] -- ABAP 클래스에서 핸들러 구현
|
[ Root CDS View Entity (R_) ] -- 데이터 모델
|
[ DB Table (ZSALES_ORDER) ]
6. RAP Behavior Definition 실전 예제 — SalesOrder 시나리오
SalesOrder 시나리오를 예로 들어보겠습니다. DB 테이블 ZSALES_ORDER은 order_id(주문번호, key), customer_id, total_amount, currency_code, order_status, created_by, created_at, last_changed_by, last_changed_at, local_last_changed_at 필드를 갖습니다.
Root View Entity 작성:
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales Order Root View'
define root view entity ZR_SALESORDER
as select from zsales_order
{
key order_id as OrderId,
customer_id as CustomerId,
total_amount as TotalAmount,
currency_code as CurrencyCode,
order_status as OrderStatus,
@Semantics.user.createdBy: true
created_by as CreatedBy,
@Semantics.systemDateTime.createdAt: true
created_at as CreatedAt,
@Semantics.user.lastChangedBy: true
last_changed_by as LastChangedBy,
@Semantics.systemDateTime.lastChangedAt: true
last_changed_at as LastChangedAt,
@Semantics.systemDateTime.localInstanceLastChangedAt: true
local_last_changed_at as LocalLastChangedAt
}
이어서 Behavior Definition(BDEF)을 작성합니다. SalesOrder를 생성/수정/삭제할 수 있고, "주문 확정" 액션과 "총액이 0보다 큰지" 검증을 추가합니다.
managed implementation in class zbp_r_salesorder unique;
strict ( 2 );
with draft;
define behavior for ZR_SALESORDER alias SalesOrder
persistent table zsales_order
draft table zsales_order_d
lock master
total etag LocalLastChangedAt
authorization master ( instance )
etag master LocalLastChangedAt
{
field ( readonly ) OrderId, CreatedBy, CreatedAt,
LastChangedBy, LastChangedAt, LocalLastChangedAt;
field ( mandatory ) CustomerId, TotalAmount, CurrencyCode;
create;
update;
delete;
action ( features : instance ) confirmOrder result [1] $self;
validation validateAmount on save { create; update; field TotalAmount; }
draft action Activate;
draft action Edit;
draft action Discard;
draft action Resume;
draft determine action Prepare;
mapping for zsales_order
{
OrderId = order_id;
CustomerId = customer_id;
TotalAmount = total_amount;
CurrencyCode = currency_code;
OrderStatus = order_status;
CreatedBy = created_by;
CreatedAt = created_at;
LastChangedBy = last_changed_by;
LastChangedAt = last_changed_at;
LocalLastChangedAt = local_last_changed_at;
}
}
BOPF와 비교하면 한눈에 드러나는 차이가 있습니다. BOPF에서는 동일한 정의를 BOBT GUI에서 수십 개 화면을 클릭해서 등록해야 했지만, RAP에서는 위 텍스트 한 장으로 모든 메타데이터가 끝납니다.
7. Service Definition과 Binding으로 OData 서비스 노출하기
Behavior 구현이 끝났으면 외부에 노출할 차례입니다. RAP는 Service Definition(어떤 entity를 노출할지)과 Service Binding(어떤 프로토콜로 노출할지)을 분리합니다.
@EndUserText.label: 'SalesOrder Service Definition'
define service ZUI_SALESORDER_SRV {
expose ZC_SALESORDER as SalesOrder;
}
Service Binding 오브젝트를 ADT에서 생성할 때 바인딩 타입을 선택합니다.
- OData V2 - UI: 기존 Fiori Elements UI5 1.x 앱 호환
- OData V4 - UI: 최신 Fiori Elements V4, Draft 자연스러운 지원
- OData V4 - Web API: 외부 시스템 API 연동(UI 메타데이터 제외)
Service Binding을 활성화한 뒤 "Service URL → Preview" 버튼을 눌러 즉시 Fiori Elements 미리보기가 가능하다는 점이 BOPF/SADL 시절과 가장 큰 개발 생산성 차이입니다.
| 관점 | BOPF | RAP |
|---|---|---|
| 설정 방식 | BOBT GUI 기반 | BDEF 텍스트 파일 |
| 데이터 모델 | DDIC 테이블 + 별도 매핑 | CDS View Entity 일급 시민 |
| Draft 지원 | 수동 구현 | managed draft 키워드 한 줄 |
| 클라우드 사용 | 제한적 | 완전 지원(Steampunk) |
| 형상관리 | SE80/전송요청 기반 | abapGit 친화적 |
| OData 노출 | SADL + Gateway 별도 | Service Binding 한 번 |
8. 실무에서 자주 마주치는 함정과 해결 패턴
RAP를 처음 도입할 때 흔히 부딪히는 문제 세 가지를 정리합니다.
- managed vs unmanaged 혼동: managed는 RAP가 영속화(INSERT/UPDATE/DELETE)까지 자동 처리하는 모드이고, unmanaged는 개발자가 SAVE 단계의 DB 쓰기를 직접 책임지는 모드입니다. 레거시 BAPI나 기존 BOPF 객체를 감싸야 하는 경우 unmanaged 또는 "managed with unmanaged save"를 선택하는 것이 권장됩니다.
- validation이 실행되지 않는 문제: validation 메서드에서 failed/reported를 채웠는데도 저장이 됩니다 — 핸들러 클래스만 만들고 BDEF 선언(
validation ... on save { create; update; })을 빼먹으면 메서드가 호출조차 되지 않습니다. 또한 strict(2) 모드에서는 메시지 textid가 반드시 등록되어 있어야 런타임 오류를 피할 수 있습니다. - ETag/Lock 충돌 반복 발생: RAP는 OData 동시성 제어를 위해 ETag와 Lock master를 강하게 요구합니다. LocalLastChangedAt 필드를 BDEF에
etag master로 선언하지 않거나 DB 저장 시 갱신하지 않으면 "Resource has been changed by another user" 오류가 반복됩니다. SAVE 단계의 update_buffer 시 항상 LocalLastChangedAt를 현재 시각으로 갱신하는 패턴이 권장됩니다.
BOPF에서 RAP로 마이그레이션할 때는 한꺼번에 옮기기보다, 우선 BOPF 객체 위에 RAP unmanaged wrapper를 두고 OData 표면만 RAP로 전환하는 점진 전략이 일반적으로 안전합니다. 그 다음 핵심 노드부터 managed RAP으로 재작성하면 됩니다.
댓글 0
아직 댓글이 없습니다.