Introduction
When people talk about RAP (RESTful ABAP Programming Model), the conversation usually jumps straight to behavior definitions, actions, and determinations. The CDS layer gets a brief mention and everyone moves on.
That’s a mistake.
The CDS view architecture is the contract your entire application is built on. Get it right and everything above it — behaviors, OData services, Fiori UIs — falls into place naturally. Get it wrong and you’ll spend hours debugging problems that were actually design issues.
In this post I’ll walk through the complete CDS layer of a Mini Inventory & Stock Movement System I built on SAP BTP Trial, covering four entities across three business objects. I’ll explain the design decisions, not just the code.
The System at a Glance
The application manages warehouse stock movements. Three business objects cover the full domain:
Each entity has a matching projection view (ZC_ prefix) used to expose the OData service.
The Two-Layer Architecture
RAP enforces a clean separation between two CDS view types. Understanding why this separation exists is more important than understanding the syntax.
Interface views (ZI_) are the authoritative source of truth. They sit directly on the database tables and carry everything the framework needs to understand your data: semantic annotations, associations, compositions, and quantity/currency references. These views should be stable — changing them has downstream impact.
Projection views (ZC_) consume the interface views and add presentation concerns: the provider contract, @Metadata.allowExtensions for metadata extensions, and the redirection of compositions so that OData navigation points to the correct projection layer. Projection views can evolve more freely.
A useful mental model: ZI_ is the API contract, ZC_ is the UI adapter.
Composition: Stock Document Header and Item
The most important structural relationship in this model is the header-item composition between ZI_MiniMM_StockDocument and ZI_MiniMM_StockDocumentItem.
In the header (root entity):
In the item (child entity):
The [0..*] cardinality means a document can have zero or more items — appropriate here since a newly created document starts empty. The parent association on the item side creates the back-navigation link.
Why does this matter? Composition in RAP is not just a modelling convenience. It has runtime implications: the managed runtime treats the composition tree as a single transactional unit. When you delete a header, items are automatically deleted. When you lock a header for editing, the entire tree is locked. This is the correct behaviour for a stock document.
Associations: Two Aliases, One Target View
The item view introduces an interesting challenge: a stock movement can have both a source storage location and a target storage location, and both must be validated against the same ZI_MiniMM_Storage entity.
The solution is two separate associations pointing to the same underlying view:
association [0..1] to ZI_MiniMM_Storage as _SourceStorage on $projection.SourceStorage = _SourceStorage.StorageID
association [0..1] to ZI_MiniMM_Storage as _TargetStorage on $projection.TargetStorage = _TargetStorage.StorageID
A few design points worth noting here.
The _Material association is [1..1] — every item line must reference a valid material. _SourceStorage and _TargetStorage are both [0..1] because not every movement type uses both. A goods receipt (GR) has no source; a goods issue (GI) has no target. Making both optional at the CDS level correctly reflects the business reality.
The alias names (_SourceStorage, _TargetStorage) are different even though both navigate to ZI_MiniMM_Storage. This is required by the framework — CDS does not allow two associations with identical names on the same view, even if the target differs.
Semantic Annotations: Free Administrative Data
RAP’s managed runtime will auto-populate administrative fields if you annotate them correctly. No custom code required.
@Semantics.systemDateTime.createdAt: truecreated_at as CreatedAt,
@Semantics.user.lastChangedBy: truelast_changed_by as ChangedBy,
@Semantics.systemDateTime.lastChangedAt: truelast_changed_at as ChangedAt
These annotations go on the interface view, not the projection. The framework reads them at runtime and fills the mapped database columns on every create and update operation.
The only requirement is that the underlying database table columns have compatible data types (SYUNAME, TIMESTAMPL respectively) and that you declare with_draft or standard managed behaviour in the behavior definition. If those conditions are met, this is genuinely zero-effort audit trail data.
Projection Views: What Most Tutorials Skip
Every interface view has a corresponding projection view. The projection view adds three things that the interface view cannot provide: the provider contract transactional_query declaration (required for OData V4 exposure), @Metadata.allowExtensions: true (required for metadata extensions to attach), and — crucially — the composition redirect.
The composition redirect is the step that most beginner tutorials either skip or explain poorly.
In the projection header:
In the projection item:
Without this redirect, OData navigation from the header to its items would traverse back to the interface entities (ZI_), not the projection entities (ZC_). The service binding would still activate, but navigation in Fiori would either fail silently or return data without the metadata annotations your UI depends on.
Value Help Annotations
The final piece of the CDS layer is the value help definitions, which live in the metadata extension of the item projection view.
entity.name: ‘ZC_MiniMM_Material’,
entity.element: ‘MaterialID’ }]MaterialID;
@Consumption.valueHelpDefinition: [{
entity.name: ‘ZC_MiniMM_Storage’,
entity.element: ‘StorageID’ }]SourceStorage;
@Consumption.valueHelpDefinition: [{
entity.name: ‘ZC_MiniMM_Storage’,
entity.element: ‘StorageID’ }]TargetStorage;
The annotation always points to the projection view (ZC_), not the interface view. This is because the value help dropdown in Fiori is itself an OData call, and OData services are generated from projection views. Pointing at ZI_ would fail at activation or produce no results.
Both SourceStorage and TargetStorage reference the same ZC_MiniMM_Storage projection — same entity, same element, different field. The framework handles this correctly.
Running This on BTP Trial
Everything described in this post runs on a free SAP BTP Trial account with the ABAP environment service instance. No S/4HANA system required. No license. The ABAP Development Tools (ADT) in Eclipse is the only tool you need locally.
If you’ve been waiting for the right time to start learning ABAP Cloud and RAP, a BTP trial account is genuinely the right place to start. The constraints of the cloud environment (no classic ABAP, no access to SAP standard tables directly) force you to learn the correct patterns from the beginning.
ArchitectureMaterial View InterfaceMaterial Create InterfaceStock Movement Interface
IntroductionWhen people talk about RAP (RESTful ABAP Programming Model), the conversation usually jumps straight to behavior definitions, actions, and determinations. The CDS layer gets a brief mention and everyone moves on.That’s a mistake.The CDS view architecture is the contract your entire application is built on. Get it right and everything above it — behaviors, OData services, Fiori UIs — falls into place naturally. Get it wrong and you’ll spend hours debugging problems that were actually design issues.In this post I’ll walk through the complete CDS layer of a Mini Inventory & Stock Movement System I built on SAP BTP Trial, covering four entities across three business objects. I’ll explain the design decisions, not just the code.The System at a GlanceThe application manages warehouse stock movements. Three business objects cover the full domain:Business Object Interface View Database Table Material MasterZI_MiniMM_MaterialZRMZ_MM_MATERIALStorage LocationZI_MiniMM_StorageZRMZ_MM_STORAGEStock Document (Header)ZI_MiniMM_StockDocumentZRMZ_MM_DOC_HDRStock Document (Item)ZI_MiniMM_StockDocumentItemZRMZ_MM_DOC_ITEMCurrent Stock (read-only)ZI_MiniMM_CurrentStockZRMZ_MM_STOCKEach entity has a matching projection view (ZC_ prefix) used to expose the OData service.The Two-Layer ArchitectureRAP enforces a clean separation between two CDS view types. Understanding why this separation exists is more important than understanding the syntax.Interface views (ZI_) are the authoritative source of truth. They sit directly on the database tables and carry everything the framework needs to understand your data: semantic annotations, associations, compositions, and quantity/currency references. These views should be stable — changing them has downstream impact.Projection views (ZC_) consume the interface views and add presentation concerns: the provider contract, @Metadata.allowExtensions for metadata extensions, and the redirection of compositions so that OData navigation points to the correct projection layer. Projection views can evolve more freely.A useful mental model: ZI_ is the API contract, ZC_ is the UI adapter.Composition: Stock Document Header and ItemThe most important structural relationship in this model is the header-item composition between ZI_MiniMM_StockDocument and ZI_MiniMM_StockDocumentItem.In the header (root entity): abapcomposition [0..*] of ZI_MiniMM_StockDocumentItem as _ItemsIn the item (child entity): abapassociation to parent ZI_MiniMM_StockDocument as _Header on $projection.DocumentID = _Header.DocumentIDThe [0..*] cardinality means a document can have zero or more items — appropriate here since a newly created document starts empty. The parent association on the item side creates the back-navigation link.Why does this matter? Composition in RAP is not just a modelling convenience. It has runtime implications: the managed runtime treats the composition tree as a single transactional unit. When you delete a header, items are automatically deleted. When you lock a header for editing, the entire tree is locked. This is the correct behaviour for a stock document.Associations: Two Aliases, One Target ViewThe item view introduces an interesting challenge: a stock movement can have both a source storage location and a target storage location, and both must be validated against the same ZI_MiniMM_Storage entity.The solution is two separate associations pointing to the same underlying view: abapassociation [1..1] to ZI_MiniMM_Material as _Material on $projection.MaterialID = _Material.MaterialID
association [0..1] to ZI_MiniMM_Storage as _SourceStorage on $projection.SourceStorage = _SourceStorage.StorageID
association [0..1] to ZI_MiniMM_Storage as _TargetStorage on $projection.TargetStorage = _TargetStorage.StorageIDA few design points worth noting here.The _Material association is [1..1] — every item line must reference a valid material. _SourceStorage and _TargetStorage are both [0..1] because not every movement type uses both. A goods receipt (GR) has no source; a goods issue (GI) has no target. Making both optional at the CDS level correctly reflects the business reality.The alias names (_SourceStorage, _TargetStorage) are different even though both navigate to ZI_MiniMM_Storage. This is required by the framework — CDS does not allow two associations with identical names on the same view, even if the target differs.Semantic Annotations: Free Administrative DataRAP’s managed runtime will auto-populate administrative fields if you annotate them correctly. No custom code required. abap@Semantics.user.createdBy: truecreated_by as CreatedBy,
@Semantics.systemDateTime.createdAt: truecreated_at as CreatedAt,
@Semantics.user.lastChangedBy: truelast_changed_by as ChangedBy,
@Semantics.systemDateTime.lastChangedAt: truelast_changed_at as ChangedAtThese annotations go on the interface view, not the projection. The framework reads them at runtime and fills the mapped database columns on every create and update operation.The only requirement is that the underlying database table columns have compatible data types (SYUNAME, TIMESTAMPL respectively) and that you declare with_draft or standard managed behaviour in the behavior definition. If those conditions are met, this is genuinely zero-effort audit trail data.Projection Views: What Most Tutorials SkipEvery interface view has a corresponding projection view. The projection view adds three things that the interface view cannot provide: the provider contract transactional_query declaration (required for OData V4 exposure), @Metadata.allowExtensions: true (required for metadata extensions to attach), and — crucially — the composition redirect.The composition redirect is the step that most beginner tutorials either skip or explain poorly.In the projection header: abap_Items : redirected to composition child ZC_MiniMM_StockDocumentItemIn the projection item: abap_Header : redirected to parent ZC_MiniMM_StockDocumentWithout this redirect, OData navigation from the header to its items would traverse back to the interface entities (ZI_), not the projection entities (ZC_). The service binding would still activate, but navigation in Fiori would either fail silently or return data without the metadata annotations your UI depends on.Value Help AnnotationsThe final piece of the CDS layer is the value help definitions, which live in the metadata extension of the item projection view. abap@Consumption.valueHelpDefinition: [{
entity.name: ‘ZC_MiniMM_Material’,
entity.element: ‘MaterialID’ }]MaterialID;
@Consumption.valueHelpDefinition: [{
entity.name: ‘ZC_MiniMM_Storage’,
entity.element: ‘StorageID’ }]SourceStorage;
@Consumption.valueHelpDefinition: [{
entity.name: ‘ZC_MiniMM_Storage’,
entity.element: ‘StorageID’ }]TargetStorage;The annotation always points to the projection view (ZC_), not the interface view. This is because the value help dropdown in Fiori is itself an OData call, and OData services are generated from projection views. Pointing at ZI_ would fail at activation or produce no results.Both SourceStorage and TargetStorage reference the same ZC_MiniMM_Storage projection — same entity, same element, different field. The framework handles this correctly.Running This on BTP TrialEverything described in this post runs on a free SAP BTP Trial account with the ABAP environment service instance. No S/4HANA system required. No license. The ABAP Development Tools (ADT) in Eclipse is the only tool you need locally.If you’ve been waiting for the right time to start learning ABAP Cloud and RAP, a BTP trial account is genuinely the right place to start. The constraints of the cloud environment (no classic ABAP, no access to SAP standard tables directly) force you to learn the correct patterns from the beginning.ArchitectureMaterial View InterfaceMaterial Create InterfaceStock Movement Interface Read More Technology Blog Posts by Members articles
#SAP
#SAPTechnologyblog