关于 abap-rap
abap-rap 是面向 SAP ABAP RESTful Application Programming Model(RAP)开发者的最佳实践参考。内容涵盖分层架构、实现方式选择、关键对象及常见错误。
主要功能
- 01RAP 三层架构
- 02托管与非托管实现建议
- 03CDS 视图实体模式
- 04行为定义与实现示例
- 05投影与服务配置建议
- 06常见问题与规避方法
适用场景
- 01选择托管或非托管 RAP 实现
- 02规划 RAP 业务对象结构
- 03检查行为定义与处理器
- 04避免常见 RAP 实现错误
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dankromp/ skilloverflow --skill abap-rap安装后在 Claude Code 中输入/abap-rap确认已加载。
附带文件references/quick-reference.mdtemplates/managed-rap-bo.abap
详细介绍
ABAP RAP — 最佳实践
主要参考:https://help.sap.com/docs/abap-cloud/abap-rap BDEF 语法:https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/abenbdl_define_beh.htm 速查表:https://github.com/SAP-samples/abap-cheat-sheets
RAP 架构——三层
RAP 业务对象通过 CDS 数据建模视图、CDS 行为定义以及 ABAP 类中的行为实现来定义。
Database table (DDIC table — stores active data)
↓
CDS view entity (data model — root + child entities)
↓
Behavior Definition (BDEF — what operations are allowed)
↓
Behavior Implementation (ABAP class — business logic)
↓
Projection view (UI-specific subset + aliases)
↓
Service Definition (what to expose)
↓
Service Binding (OData V2 or V4 protocol)
托管式与非托管式——谨慎选择
在托管式 RAP 中,框架会自动处理所有 CRUD 持久化操作,开发者只需专注于业务逻辑。在非托管式 RAP 中,开发者必须手动实现每个保存操作。托管式 RAP 非常适合新开发;在需要集成复杂的现有逻辑或旧系统时,则选择非托管式 RAP。
使用托管式的场景: 从零开始开发、直接访问表、新建 S/4HANA 或 BTP 应用 使用非托管式的场景: 集成现有 BAPI/函数模块、复杂的旧版逻辑、外部持久化
CDS 视图实体——根节点和子节点
" Root CDS view entity
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Travel'
define root view entity ZI_Travel
as select from ztravel
composition [0..*] of ZI_TravelItem as _Items
{
key travel_uuid as TravelUUID,
travel_id as TravelID,
agency_id as AgencyID,
customer_id as CustomerID,
begin_date as BeginDate,
end_date as EndDate,
booking_fee as BookingFee,
total_price as TotalPrice,
currency_code as CurrencyCode,
overall_status as OverallStatus,
@Semantics.systemDateTime.createdAt: true
created_at as CreatedAt,
@Semantics.user.createdBy: true
created_by as CreatedBy,
@Semantics.systemDateTime.lastChangedAt: true
last_changed_at as LastChangedAt,
@Semantics.user.lastChangedBy: true
last_changed_by as LastChangedBy,
@Semantics.systemDateTime.localInstanceLastChangedAt: true
local_last_changed_at as LocalLastChangedAt,
_Items
}
行为定义——托管式并启用草稿
managed implementation in class ZBP_Travel unique;
strict ( 2 );
with draft;
define behavior for ZI_Travel alias Travel
persistent table ztravel
draft table ztravel_d
etag master LocalLastChangedAt
lock master total etag LastChangedAt
authorization master ( global )
{
field ( readonly ) TravelUUID, TravelID, CreatedAt, CreatedBy, LastChangedAt, LastChangedBy;
field ( numbering : managed, readonly ) TravelUUID;
create;
update;
delete;
action ( features : instance ) acceptTravel result [1] $self;
action ( features : instance ) rejectTravel result [1] $self;
validation validateDates on save { field BeginDate, EndDate; }
validation validateStatus on save { field OverallStatus; }
determination calculateTotalPrice on modify { field BookingFee; }
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
association _Items { create; with draft; }
mapping for ztravel {
TravelUUID = travel_uuid;
TravelID = travel_id;
AgencyID = agency_id;
}
}
行为实现——关键方法
CLASS ZBP_Travel DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF ZI_Travel.
PUBLIC SECTION.
" Validation
METHODS validateDates FOR VALIDATE ON SAVE
IMPORTING keys FOR Travel~validateDates.
" Determination
METHODS calculateTotalPrice FOR DETERMINE ON MODIFY
IMPORTING keys FOR Travel~calculateTotalPrice.
" Action
METHODS acceptTravel FOR MODIFY
IMPORTING keys FOR ACTION Travel~acceptTravel RESULT result.
ENDCLASS.
CLASS ZBP_Travel IMPLEMENTATION.
METHOD validateDates.
READ ENTITIES OF ZI_Travel IN LOCAL MODE
ENTITY Travel
FIELDS ( BeginDate EndDate )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel)
FAILED failed.
LOOP AT lt_travel INTO DATA(ls_travel).
IF ls_travel-EndDate < ls_travel-BeginDate.
APPEND VALUE #(
%tky = ls_travel-%tky )
TO failed-travel.
APPEND VALUE #(
%tky = ls_travel-%tky
%state_area = 'VALIDATE_DATES'
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'End date must be after begin date' )
%element-EndDate = if_abap_behv=>mk-on )
TO reported-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD calculateTotalPrice.
" Always use IN LOCAL MODE inside behavior handlers
MODIFY ENTITIES OF ZI_Travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( TotalPrice )
WITH VALUE #( FOR key IN keys (
%tky = key-%tky
TotalPrice = '0.00' ) ).
ENDMETHOD.
METHOD acceptTravel.
MODIFY ENTITIES OF ZI_Travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( OverallStatus )
WITH VALUE #( FOR key IN keys (
%tky = key-%tky
OverallStatus = 'A' ) )
FAILED failed
REPORTED reported.
READ ENTITIES OF ZI_Travel IN LOCAL MODE
ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys )
RESULT result
FAILED failed.
ENDMETHOD.
ENDCLASS.
投影视图 + BDEF
" Projection CDS view (UI-facing)
@EndUserText.label: 'Travel - Projection'
@AccessControl.authorizationCheck: #NOT_REQUIRED
define root view entity ZC_Travel
provider contract transactional_ui
as projection on ZI_Travel
{
key TravelUUID,
TravelID,
@UI.lineItem: [{ position: 10 }]
@UI.selectionField: [{ position: 10 }]
AgencyID,
BeginDate,
EndDate,
@UI.lineItem: [{ position: 50, criticality: 'OverallStatusCriticality' }]
OverallStatus,
_Items : redirected to composition child ZC_TravelItem
}
" Projection BDEF — thin layer, no business logic here
projection;
strict ( 2 );
use draft;
define behavior for ZC_Travel alias Travel
{
use create;
use update;
use delete;
use action acceptTravel;
use action rejectTravel;
use draft action Edit;
use draft action Activate;
use draft action Discard;
use draft action Resume;
use association _Items { create; with draft; }
}
服务定义 + 绑定
" Service Definition
@EndUserText.label: 'Travel Service'
define service ZUI_Travel_O4 {
expose ZC_Travel as Travel;
expose ZC_TravelItem as TravelItem;
expose /DMO/I_Agency as Agency;
}
在 ADT 中,将服务绑定 ZUI_Travel_O4_VR 激活为 OData V4 UI 绑定。
应避免的常见错误
-
❌ 在投影 BDEF 或投影视图中添加业务逻辑
-
✅ 保持投影精简——仅包含 UI 标签、字段别名和
use语句。接口行为层才是业务逻辑层。 -
❌ 在处理程序中调用
MODIFY ENTITIES时未使用IN LOCAL MODE -
✅ 内部修改时始终使用
IN LOCAL MODE,避免权限检查和锁定问题 -
❌ 跳过草稿处理,打算之后再添加
-
✅ 从一开始就启用草稿——为现有 RAP BO 补加草稿功能很复杂
-
❌ 在新开发中使用
strict ( 1 ) -
✅ 始终使用
strict ( 2 )——启用最全面的 BDEF 语法检查 -
❌ 在可直接访问表时仍使用非托管式实现 CRUD
-
✅ 对于从零开始且可直接访问表的开发,使用托管式实现
-
❌ 忘记在根 BDEF 中定义
etag master和lock master -
✅ 始终定义 ETag 以实现乐观锁定,并为草稿定义 lock master
-
❌ 忘记在管理字段上添加
@Semantics.systemDateTime和@Semantics.user注解 -
✅ 托管式 RAP 需要这些注解才能自动填充时间戳和用户信息