개요 및 이 글에서 다루는 것
SAP RAP(ABAP RESTful Application Programming Model)을 처음 시작하면 "도대체 어디부터 손을 대야 하지?"라는 질문이 가장 먼저 떠오릅니다. CDS View, Behavior Definition(BDEF), Behavior Implementation, Service Definition, Service Binding 등 생소한 객체가 줄줄이 등장하는데, 잘못된 순서로 만들면 활성화 단계에서 줄줄이 에러가 쏟아집니다. 이 글은 신규 프로젝트에서 RAP 5단계를 어떤 순서로 만들어야 하는지, 각 단계에서 무엇을 결정해야 하는지를 SalesOrder 시나리오로 풀어냅니다.
- RAP의 5단계 개발 순서를 머릿속에 그릴 수 있다
- 각 단계 객체(테이블, CDS, BDEF, BIMP, SD, SB)의 역할을 구분할 수 있다
- 흔히 실수하는 활성화 순서/네이밍 충돌을 회피할 수 있다
- Managed / Unmanaged / Read-only 시나리오를 분기할 수 있다
이 글을 보기 전에 알아두면 좋은 것
ABAP 기본 문법(CLASS, METHOD, TYPES), CDS View 기본 구조(@AccessControl, define view entity), Eclipse 기반 ADT(ABAP Development Tools) 사용법 정도면 충분합니다. OData가 무엇인지 들어본 적이 있다면 Service Binding 단계가 훨씬 빠르게 이해됩니다. RAP에서 사용되는 root entity, child entity, association 개념은 본문에서 풀어 설명합니다.
환경과 준비물
이 글은 다음 환경을 기준으로 작성했습니다. 다른 버전에서도 큰 흐름은 동일하지만, 어노테이션 일부와 ADT 마법사 UI는 차이가 있을 수 있습니다.
- SAP S/4HANA Cloud Public Edition 2402 또는 ABAP Cloud(ABAP Environment, BTP) 기준
- 온프레미스의 경우 S/4HANA 2022 FPS02 이상, NetWeaver 7.56 이상 권장
- Eclipse 2024-03 + ABAP Development Tools 3.40 이상
- Z 또는 Y 네임스페이스 사용 가능한 개발 계정
- Business Catalog: SAP_CORE_BC_EXT (ABAP Cloud)
실습 패키지는 ZRAP_SO_DEMO로 가정하고, 소프트웨어 컴포넌트는 ABAP Cloud의 경우 ZLOCAL, 온프레미스는 LOCAL 또는 전용 컴포넌트를 권장합니다.
RAP 5단계의 큰 그림과 핵심 개념
RAP를 처음 접하면 객체가 너무 많아 보이지만, 실제로는 "데이터 → 모델 → 동작 → 노출"이라는 4개의 층을 6~7개의 객체로 표현한 것뿐입니다. 다음과 같이 비유하면 외우기 쉽습니다.
테이블은 식자재, CDS는 레시피, BDEF는 주방 규칙, BIMP는 요리사의 손놀림, Service Definition은 메뉴판, Service Binding은 식당 간판이다.
5단계 순서를 도식으로 정리하면 이렇습니다.
[1] DDIC 테이블 정의 (ZTSALES_ORDER)
|
v
[2] CDS View Entity 작성 (Interface View → Projection View)
| @ObjectModel.semanticKey, association
v
[3] Behavior Definition (BDEF, .bdef) ← Managed / Unmanaged 결정
| create / update / delete / action / determination / validation
v
[4] Behavior Implementation (BIMP, ABAP 클래스 두 개: Behavior Pool + Saver)
|
v
[5] Service Definition + Service Binding (.srvd + .srvb)
|
v
OData V4 (UI/API) 또는 V2 엔드포인트 노출
여기서 헷갈리기 쉬운 포인트가 두 가지 있습니다. 첫째, CDS는 보통 두 개를 만듭니다. 하부의 Interface View(데이터 모델, R_)와 상부의 Projection View(UI/외부 노출용, C_)로 나누는 것이 권장 패턴입니다. 둘째, BDEF도 두 개를 만드는 경우가 많습니다. Interface 레벨의 BDEF가 비즈니스 동작을 정의하고, Projection 레벨의 BDEF가 외부에 어떤 동작을 노출할지 선택합니다.
Managed 시나리오는 RAP 런타임이 SAVE/COMMIT을 직접 처리하므로 BIMP 작성량이 적고, Unmanaged 시나리오는 기존 레거시 함수모듈/클래스를 감싸야 할 때 사용합니다. 신규라면 Managed부터 시작하는 것이 일반적으로 권장됩니다.
실전 예제 1단계 — 테이블과 CDS 골격 만들기
SalesOrder 헤더를 저장할 테이블을 먼저 만듭니다. 키 필드와 관리 필드(Created/LastChanged)는 RAP가 자동으로 채워주도록 admin 데이터 타입을 사용합니다.
@EndUserText.label : 'Sales Order Header'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #LIMITED
define table ztsales_order {
key client : abap.clnt not null;
key order_uuid : sysuuid_x16 not null;
order_id : abap.numc(10);
customer_id : abap.char(10);
order_date : abap.dats;
total_amount : abap.curr(15,2);
currency_code : abap.cuky;
overall_status : abap.char(2);
created_by : abp_creation_user;
created_at : abp_creation_tstmpl;
last_changed_by : abp_lastchange_user;
last_changed_at : abp_lastchange_tstmpl;
local_last_changed_at : abp_locinst_lastchange_tstmpl;
}
다음으로 Interface View를 정의합니다. RAP에서는 반드시 define root view entity 키워드를 사용하고, 키 필드를 명확히 표시합니다.
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'SalesOrder Root View'
define root view entity ZR_SalesOrderTP
as select from ztsales_order
{
key order_uuid as OrderUUID,
order_id as OrderID,
customer_id as CustomerID,
order_date as OrderDate,
total_amount as TotalAmount,
currency_code as CurrencyCode,
overall_status as OverallStatus,
@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
}
여기까지가 1단계이며 이 시점에서 한 번 활성화해서 빨간 줄이 없는지 확인합니다.
실전 예제 2단계 — BDEF와 Projection 레이어 추가
이제 동작(BDEF)을 정의합니다. Managed 시나리오를 기준으로 하고, 향후 확장을 위해 ETag, lock master, draft 옵션을 함께 둡니다.
managed implementation in class zbp_r_salesordertp unique;
strict ( 2 );
with draft;
define behavior for ZR_SalesOrderTP alias SalesOrder
persistent table ztsales_order
draft table ztsales_order_d
lock master
total etag LastChangedAt
authorization master ( instance )
etag master LocalLastChangedAt
{
create;
update;
delete;
field ( readonly ) OrderUUID, OrderID, CreatedBy, CreatedAt,
LastChangedBy, LastChangedAt, LocalLastChangedAt;
field ( mandatory ) CustomerID, OrderDate, CurrencyCode;
determination calculateOrderID on save { create; }
validation validateCustomer on save { create; field CustomerID; }
action confirmOrder result [1] $self;
mapping for ztsales_order
{
OrderUUID = order_uuid;
OrderID = order_id;
CustomerID = customer_id;
OrderDate = order_date;
TotalAmount = total_amount;
CurrencyCode = currency_code;
OverallStatus = overall_status;
CreatedBy = created_by;
CreatedAt = created_at;
LastChangedBy = last_changed_by;
LastChangedAt = last_changed_at;
LocalLastChangedAt = local_last_changed_at;
}
}
이어서 외부 노출용 Projection View를 만듭니다. UI나 API 사용자는 항상 Projection을 통해 접근하도록 분리하는 것이 유지보수에 유리합니다.
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'SalesOrder Projection'
@Metadata.allowExtensions: true
define root view entity ZC_SalesOrderTP
provider contract transactional_query
as projection on ZR_SalesOrderTP
{
key OrderUUID,
OrderID,
CustomerID,
OrderDate,
TotalAmount,
CurrencyCode,
OverallStatus,
CreatedBy, CreatedAt,
LastChangedBy, LastChangedAt,
LocalLastChangedAt
}
Projection BDEF는 인터페이스 BDEF를 상속받아 외부에 무엇을 노출할지 결정합니다.
projection;
strict ( 2 );
use draft;
define behavior for ZC_SalesOrderTP alias SalesOrder
{
use create;
use update;
use delete;
use action confirmOrder;
use action Edit;
use action Activate;
use action Discard;
use action Resume;
use action Prepare;
}
실전 예제 3단계 — Behavior Pool 구현, Service Definition·Binding, 운영 점검
BIMP는 일반적으로 두 개의 ABAP 클래스로 나뉩니다. ZBP_R_SALESORDERTP(Behavior Pool, 로컬 클래스 lhc_SalesOrder가 동작을 구현)와 SAVER 클래스입니다. Managed라면 SAVER는 대부분 비워둬도 RAP가 처리해줍니다. 핵심 동작인 determination과 validation, action을 보여드립니다.
CLASS lhc_salesorder DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS:
calculateOrderID FOR DETERMINE ON SAVE
IMPORTING keys FOR SalesOrder~calculateOrderID,
validateCustomer FOR VALIDATE ON SAVE
IMPORTING keys FOR SalesOrder~validateCustomer,
confirmOrder FOR MODIFY
IMPORTING keys FOR ACTION SalesOrder~confirmOrder RESULT result.
ENDCLASS.
CLASS lhc_salesorder IMPLEMENTATION.
METHOD calculateOrderID.
READ ENTITIES OF zr_salesordertp IN LOCAL MODE
ENTITY SalesOrder
FIELDS ( OrderID )
WITH CORRESPONDING #( keys )
RESULT DATA(orders).
DATA updates TYPE TABLE FOR UPDATE zr_salesordertp.
LOOP AT orders ASSIGNING FIELD-SYMBOL(<ord>) WHERE OrderID IS INITIAL.
APPEND VALUE #(
%tky = <ord>-%tky
OrderID = |SO{ cl_system_uuid=>create_uuid_c22_static( ) WIDTH = 10 }|
) TO updates.
ENDLOOP.
MODIFY ENTITIES OF zr_salesordertp IN LOCAL MODE
ENTITY SalesOrder UPDATE FIELDS ( OrderID )
WITH updates.
ENDMETHOD.
METHOD validateCustomer.
READ ENTITIES OF zr_salesordertp IN LOCAL MODE
ENTITY SalesOrder
FIELDS ( CustomerID )
WITH CORRESPONDING #( keys )
RESULT DATA(orders).
LOOP AT orders INTO DATA(order).
IF order-CustomerID IS INITIAL.
APPEND VALUE #( %tky = order-%tky ) TO failed-salesorder.
APPEND VALUE #(
%tky = order-%tky
%msg = NEW zcm_sales_order(
severity = if_abap_behv_message=>severity-error
textid = zcm_sales_order=>customer_missing )
%element-CustomerID = if_abap_behv=>mk-on
) TO reported-salesorder.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD confirmOrder.
MODIFY ENTITIES OF zr_salesordertp IN LOCAL MODE
ENTITY SalesOrder UPDATE FIELDS ( OverallStatus )
WITH VALUE #( FOR k IN keys ( %tky = k-%tky OverallStatus = 'CO' ) ).
READ ENTITIES OF zr_salesordertp IN LOCAL MODE
ENTITY SalesOrder ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(confirmed).
result = VALUE #( FOR c IN confirmed ( %tky = c-%tky %param = c ) ).
ENDMETHOD.
ENDCLASS.
마지막으로 Service Definition과 Service Binding을 만듭니다. SD는 "어떤 엔티티를 묶어서 한 서비스로 보낼 것인가"를, SB는 "어떤 프로토콜(V2/V4, UI/Web API)로 노출할 것인가"를 결정합니다.
@EndUserText.label: 'SalesOrder OData Service'
define service ZUI_SALES_ORDER {
expose ZC_SalesOrderTP as SalesOrder;
}
SB는 ADT에서 New → Service Binding으로 마법사를 통해 생성합니다. Binding Type을 OData V4 - UI로 선택하면 Fiori Elements 프리뷰가, OData V4 - Web API로 선택하면 외부 통합용 엔드포인트가 활성화됩니다. 활성화 후 "Service URL"을 클릭하면 즉시 메타데이터 XML을 확인할 수 있습니다.
자주 막히는 지점과 트러블슈팅 FAQ
RAP 입문자가 가장 많이 마주치는 에러를 모아봤습니다.
- 활성화 순서 꼬임: 테이블이 활성화되지 않은 상태에서 CDS를 활성화하면 "data source not found"가 발생합니다. 항상 1단계 객체부터 차근차근 활성화하세요.
- BDEF strict(2) 경고: 신규 RAP에서는 strict 모드가 기본 권장이며, mapping 누락이나 admin 필드 누락 시 활성화가 실패합니다. 메시지를 그대로 무시하지 말고 fields/mapping 절을 보강하세요.
- SAVER 클래스 누락: Managed라도 SAVER 클래스(
zbp_r_salesordertp의 saver 영역)는 반드시 존재해야 합니다. ADT가 자동 생성한 빈 메서드를 지우지 마세요.
Q1. Managed와 Unmanaged 중 무엇을 골라야 하나요?
신규 테이블이고 SAP 표준 패턴을 따른다면 Managed가 일반적으로 권장됩니다. 기존 BAPI/FM/클래스가 이미 있고 SAVE 로직을 재사용해야 한다면 Unmanaged가 적합합니다.
Q2. CDS와 BDEF를 한 번에 다 만들고 마지막에 활성화하면 안 되나요?
가능은 하지만 권장하지 않습니다. 한 객체에서 발생한 에러가 다른 객체의 에러를 가리므로, 단계별로 활성화하면서 진행하는 것이 디버깅이 훨씬 빠릅니다.
Q3. Service Binding이 활성화는 되는데 Fiori Preview에서 404가 나옵니다.
ABAP Cloud에서는 Business Catalog 할당이 누락된 경우가 많습니다. SAP_CORE_BC_EXT 등 적절한 카탈로그를 비즈니스 역할에 할당하고, IAM App을 함께 등록했는지 확인하세요. 온프레미스라면 /IWFND/MAINT_SERVICE에서 서비스가 활성화되었는지 점검합니다.
이어서 학습하면 좋은 주제
5단계의 흐름이 손에 익었다면 다음 주제로 확장해보세요. (1) Child Entity와 Composition을 추가해 SalesOrder Item을 붙이는 multi-entity RAP, (2) Side Effects 어노테이션으로 UI 자동 갱신 처리, (3) RAP Generator(ADT의 Business Object Generator) 사용법, (4) Behavior에서 EML(Entity Manipulation Language)로 다른 BO를 호출하는 패턴, (5) Unit Test를 위한 CL_CDS_TEST_ENVIRONMENT와 CL_ABAP_BEHV_TEST_DOUBLE 활용. 특히 EML과 테스트 더블은 실제 운영 코드를 안정화하는 데 필수입니다.
더 깊이 파고들 수 있는 링크
- help.sap.com — ABAP RESTful Application Programming Model 가이드
- help.sap.com — RAP Business Object 개념
- help.sap.com — Behavior Definition 레퍼런스
- help.sap.com — Service Definition / Service Binding
- SAP Developers — Managed BO 핸즈온 그룹
- SAP Community — ABAP RAP 토픽
- GitHub — SAP-samples/abap-platform-rap100 (실습 리포지토리)
댓글 0
아직 댓글이 없습니다.