RAP: Composition and Association — A Practical Guide
One of the most important modelling decisions when building a RAP service is knowing when to use a Composition and when an Association. Getting it wrong means fighting the framework instead of leveraging it. In this practical guide we build a complete header/items service — header and items — and walk step by step through CDS, behavior, service definition and CRUD.
Core concepts: Composition vs Association
Before writing any code, you need to understand the fundamental difference between the two kinds of relationship.
Composition is a parent → child relationship where the child does not exist without the parent. The lifecycle is shared: if you delete the header, the framework automatically deletes the items. The child's lock is derived from the parent. It's the classic SAP document pattern (header/items).
Association is a simple reference toward an entity that exists independently. It doesn't influence the root's CRUD; it's typically read-only navigation toward external data. Classic example: the "owner" field pointing to a Business User in the system.
Rule of thumb: if the child entity only makes sense in the context of the parent → Composition. If it's just a reference to something that exists on its own → Association.
In our example:
ZRAP_SG_HEADER→ZRAP_SG_ITEM: Composition (items don't exist without the header)ZRAP_SG_HEADER→I_BusinessUser: Association (the user exists independently)
Step 1 — CDS Base Views (ZRAP_SG_I_*)
Base views read directly from the DB table. No logic, no associations here — just clean field mapping.
ZRAP_SG_I_Header — header base view
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Header - Base View'
define view entity ZRAP_SG_I_Header
as select from ZRAP_SG_header
{
key header_uuid as HeaderUuid,
header_id as HeaderId,
title as Title,
status as Status,
priority as Priority,
owner as Owner,
start_date as StartDate,
end_date as EndDate,
created_by as CreatedBy,
created_at as CreatedAt,
last_changed_by as LastChangedBy,
last_changed_at as LastChangedAt
}
ZRAP_SG_I_Item — items base view
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Item - Base View'
define view entity ZRAP_SG_I_Item
as select from ZRAP_SG_item
{
key item_uuid as ItemUuid,
header_uuid as HeaderUuid,
item_pos as ItemPos,
item_text as ItemText,
item_status as ItemStatus,
assignee as Assignee,
remark as Remark,
created_by as CreatedBy,
created_at as CreatedAt,
last_changed_by as LastChangedBy,
last_changed_at as LastChangedAt
}
Step 2 — CDS Interface Views: Composition and Association
This is the heart of the architecture. The interface views (ZRAP_SG_C_*) declare composition and association. The RAP framework uses these declarations to drive the entire transactional behavior.
Key syntax rules:
- The composition is declared in the parent.
- The association to parent is declared in the child — they always come as a mandatory pair.
- Only the root uses
define root view entity; the child usesdefine view entity.
ZRAP_SG_C_Header — header interface view (ROOT)
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Header - Interface View'
@Metadata.allowExtensions: true
define root view entity ZRAP_SG_C_Header
as projection on ZRAP_SG_I_Header
// COMPOSITION: the parent declares its children.
// The framework handles lock and draft automatically.
composition [0..*] of ZRAP_SG_C_Item as _Items
// ASSOCIATION: reference to an external entity.
// Owner exists independently of the header.
association [0..1] to I_BusinessUser as _Owner
on $projection.Owner = _Owner.UserID
{
key HeaderUuid,
HeaderId,
Title,
Status,
Priority,
Owner,
StartDate,
EndDate,
CreatedBy,
CreatedAt,
LastChangedBy,
LastChangedAt,
// Expose the navigations
_Items,
_Owner
}
ZRAP_SG_C_Item — items interface view (CHILD)
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Item - Interface View'
@Metadata.allowExtensions: true
define view entity ZRAP_SG_C_Item
as projection on ZRAP_SG_I_Item
// ASSOCIATION TO PARENT: mandatory in the child.
// Enables the reverse navigation child → parent.
association to parent ZRAP_SG_C_Header as _Header
on $projection.HeaderUuid = _Header.HeaderUuid
// Association to another external entity (assigned user)
association [0..1] to I_BusinessUser as _Assignee
on $projection.Assignee = _Assignee.UserID
{
key ItemUuid,
HeaderUuid,
ItemPos,
ItemText,
ItemStatus,
Assignee,
Remark,
CreatedBy,
CreatedAt,
LastChangedBy,
LastChangedAt,
_Header,
_Assignee
}
Step 3 — Behavior Definition (BDEF)
The BDEF is the contract that describes which CRUD operations are allowed. In RAP managed, the framework implements persistence automatically: for a pure CRUD service like this one there's no custom logic to write — you just declare the operations.
managed;
strict ( 2 );
* ROOT ENTITY: HEADER
define behavior for ZRAP_SG_C_Header alias Header
persistent table ZRAP_SG_header
lock master
authorization master ( instance )
{
" Standard CRUD operations
create;
update;
delete;
" UUID handled automatically by the framework
field ( numbering : managed, readonly ) HeaderUuid;
" Framework-managed fields (timestamp/user)
field ( readonly ) CreatedBy, CreatedAt, LastChangedBy, LastChangedAt;
" HeaderId is read-only after creation
field ( readonly : update ) HeaderId;
" Expose navigation toward the items
association _Items { create; }
}
* CHILD ENTITY: ITEM
define behavior for ZRAP_SG_C_Item alias Item
persistent table ZRAP_SG_item
lock dependent by _Header
authorization dependent by _Header
{
update;
delete;
field ( numbering : managed, readonly ) ItemUuid;
field ( readonly ) HeaderUuid;
field ( readonly ) CreatedBy, CreatedAt, LastChangedBy, LastChangedAt;
" Navigation toward the parent (mandatory for composition)
association _Header;
}
Step 4 — Service Definition and Service Binding
The Service Definition exposes root and child; the _Owner association stays accessible via OData navigation automatically.
@EndUserText.label: 'Service Definition Document'
define service ZRAP_SG_SD_Document {
expose ZRAP_SG_C_Header as Header;
expose ZRAP_SG_C_Item as Item;
expose I_BusinessUser as BusinessUser;
}
The Service Binding is created through ADT (not from code):
Binding Type : OData V4 - UI
Service Def : ZRAP_SG_SD_Document
Service Name : ZRAP_SG_SB_DOCUMENT
Version : 0001
Generated endpoint:
/sap/opu/odata4/sap/ZRAP_SG_sb_document/srvd/sap/ZRAP_SG_sd_document/0001/
Step 5 — CRUD in practice
CREATE — create header
POST /sap/opu/odata4/.../Header
Content-Type: application/json
{
"Title" : "Sample document",
"Status" : "OP",
"Priority" : "1",
"Owner" : "USER01",
"StartDate" : "2025-06-01",
"EndDate" : "2025-12-31"
}
CREATE — create an item in the header (composition navigation)
POST /Header(HeaderUuid=guid'A1B2C3D4-...')/_Items
Content-Type: application/json
{
"ItemPos" : 10,
"ItemText" : "First sample line",
"ItemStatus" : "OP",
"Assignee" : "USER02"
}
READ — read with $expand (header + items + owner)
# Read the header with expand of the items (composition)
# and the owner (association)
GET /Header(HeaderUuid=guid'A1B2C3D4-...')?$expand=_Items,_Owner
# All open, high-priority headers, items included
GET /Header?$filter=Status eq 'OP' and Priority eq '1'
&$expand=_Items
&$orderby=EndDate asc
UPDATE — update header (only the sent fields, with ETag)
PATCH /Header(HeaderUuid=guid'A1B2C3D4-...')
Content-Type: application/json
If-Match: W/"20250617120000.0000000"
{
"Priority" : "2",
"EndDate" : "2025-11-30"
}
DELETE — deletion and cascade
# Delete a single item
DELETE /Item(ItemUuid=guid'E5F6G7H8-...')
If-Match: W/"20250617130000.0000000"
# DELETE on the HEADER — automatic cascade: the framework deletes
# ALL child items FIRST, then deletes the header.
# No code needed for this.
DELETE /Header(HeaderUuid=guid'A1B2C3D4-...')
If-Match: W/"20250617120000.0000000"
# Expected response: 204 No Content
In summary
The distinction between Composition and Association isn't a syntax detail: it defines who owns the data's lifecycle. Composition ties header and items into a single transactional business object — lock, draft and cascade are handled by the framework. Association stays a lightweight reference toward data that lives on its own. Choosing correctly right from the CDS model means letting RAP work for you, instead of hand-reimplementing what the framework already provides.