Projection Views form the top layer of the RAP architecture and enable exposing a single Business Object for different use cases. With projections, you can build different UIs, authorizations, and behaviors on the same data model.
The Projection Layer Concept
In RAP, the architecture follows the Layered Architecture Pattern:
┌─────────────────────────────────────────────────────────┐│ Service Layer ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │ Service A │ │ Service B │ │ Service C │ ││ │ (Fiori App) │ │ (API) │ │ (Analytics) │ ││ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │├─────────┼────────────────┼────────────────┼─────────────┤│ │ Projection Layer (C_*) │ ││ ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐ ││ │ ZC_Travel │ │ ZC_TravelAPI│ │ ZC_TravelRpt│ ││ │ (UI-focus) │ │ (minimal) │ │ (readonly) │ ││ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │├─────────┼────────────────┼────────────────┼─────────────┤│ │ Business Object Layer (I_*) │ ││ │ ┌────────▼────────┐ │ ││ └───────► ZI_Travel ◄───────┘ ││ │ (Core logic) │ ││ └─────────────────┘ │├─────────────────────────────────────────────────────────┤│ Database Layer │└─────────────────────────────────────────────────────────┘Naming Conventions
| Prefix | Layer | Description |
|---|---|---|
| I_ or ZI_ | Interface | Business Object with complete logic |
| C_ or ZC_ | Consumption | Projection for specific use case |
| R_ or ZR_ | Root | Alternative for interface views |
Why Projection Views?
Use Cases for Multi-UI Scenarios
-
Different User Groups
- Clerk: Full access to all fields
- Manager: Only approval and overview
- External partners: Restricted view
-
Different Devices
- Desktop: Feature-rich Fiori app
- Mobile: Simplified view
-
Different Purposes
- Transactional app: CRUD operations
- Reporting: Read-only access
- API: Technical interface
Creating Projection CDS Views
Interface View (Base)
@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Travel - Interface View'define root view entity ZI_Travel as select from ztravel composition [0..*] of ZI_Booking as _Booking association [1..1] to ZI_Agency as _Agency on $projection.AgencyId = _Agency.AgencyId association [1..1] to ZI_Customer as _Customer on $projection.CustomerId = _Customer.CustomerId{ key travel_id as TravelId, agency_id as AgencyId, customer_id as CustomerId, begin_date as BeginDate, end_date as EndDate, @Semantics.amount.currencyCode: 'CurrencyCode' total_price as TotalPrice, currency_code as CurrencyCode, description as Description, status as Status,
@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,
// Associations _Booking, _Agency, _Customer}Projection View for Fiori UI
@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Travel - Fiori App'@Metadata.allowExtensions: truedefine root view entity ZC_Travel provider contract transactional_query as projection on ZI_Travel{ key TravelId, AgencyId, CustomerId, BeginDate, EndDate, TotalPrice, CurrencyCode, Description, Status,
// Calculated fields only for this projection case Status when 'A' then 'Accepted' when 'X' then 'Rejected' when 'O' then 'Open' else 'Unknown' end as StatusText,
CreatedBy, CreatedAt, LastChangedBy, LastChangedAt,
// Expose associations _Booking : redirected to composition child ZC_Booking, _Agency, _Customer}Projection View for API Consumers
@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Travel - API'define root view entity ZC_TravelAPI provider contract transactional_query as projection on ZI_Travel{ key TravelId, AgencyId, CustomerId, BeginDate, EndDate, TotalPrice, CurrencyCode, Status,
// No UI-specific fields // No calculated fields for status text
LastChangedAt,
_Booking : redirected to composition child ZC_BookingAPI}Projection View for Reporting (Read-Only)
@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Travel - Reporting'@ObjectModel.usageType: { serviceQuality: #A, sizeCategory: #L, dataClass: #TRANSACTIONAL}define root view entity ZC_TravelReport provider contract transactional_query as projection on ZI_Travel{ key TravelId, AgencyId, _Agency.Name as AgencyName, CustomerId, _Customer.LastName as CustomerName, BeginDate, EndDate,
// Calculate duration dats_days_between( BeginDate, EndDate ) as TravelDuration,
TotalPrice, CurrencyCode, Status, CreatedAt,
// For aggregations @DefaultAggregation: #SUM TotalPrice as SumTotalPrice,
@DefaultAggregation: #COUNT cast( 1 as abap.int4 ) as TravelCount}Behavior Projection
Each Projection View needs its own Behavior Projection that adapts the Business Object behavior for this use case.
Interface Behavior Definition (Base)
managed implementation in class zbp_i_travel unique;strict ( 2 );
define behavior for ZI_Travel alias Travelpersistent table ztravellock masterauthorization master ( instance )etag master LastChangedAt{ create; update; delete;
field ( readonly ) TravelId; field ( mandatory ) AgencyId, CustomerId, BeginDate, EndDate; field ( readonly ) CreatedBy, CreatedAt, LastChangedBy, LastChangedAt;
action acceptTravel result [1] $self; action rejectTravel result [1] $self;
determination setTravelId on save { create; } validation validateDates on save { create; update; } validation validateStatus on save { create; update; }
association _Booking { create; }
mapping for ztravel { TravelId = travel_id; AgencyId = agency_id; CustomerId = customer_id; BeginDate = begin_date; EndDate = end_date; TotalPrice = total_price; CurrencyCode = currency_code; Description = description; Status = status; CreatedBy = created_by; CreatedAt = created_at; LastChangedBy = last_changed_by; LastChangedAt = last_changed_at; }}
define behavior for ZI_Booking alias Bookingpersistent table zbookinglock dependent by _Travelauthorization dependent by _Traveletag master LastChangedAt{ update; delete;
field ( readonly ) BookingId, TravelId;
association _Travel;}Behavior Projection for Fiori App
The projection exposes all operations for the Fiori application:
projection;strict ( 2 );
define behavior for ZC_Travel alias Travel{ use create; use update; use delete;
use action acceptTravel; use action rejectTravel;
use association _Booking { create; }}
define behavior for ZC_Booking alias Booking{ use update; use delete;
use association _Travel;}Behavior Projection for API (Restricted)
The API projection exposes only selected operations:
projection;strict ( 2 );
define behavior for ZC_TravelAPI alias Travel{ use create; use update; // No delete - API cannot delete
use action acceptTravel; // No rejectTravel for API
use association _Booking { create; }}
define behavior for ZC_BookingAPI alias Booking{ use update; // No delete for bookings via API
use association _Travel;}Behavior Projection for Reporting (Read-Only)
The reporting projection exposes no modification operations:
projection;strict ( 2 );
define behavior for ZC_TravelReport alias Travel{ // No operations - read-only access // Read is automatically supported}Metadata Extensions
Metadata Extensions enable different UI annotations per projection without changing the projection view itself.
Metadata Extension for Fiori App
@Metadata.layer: #COREannotate entity ZC_Travel with{ @UI.facet: [ { id: 'TravelHeader', purpose: #STANDARD, type: #IDENTIFICATION_REFERENCE, label: 'Travel Details', position: 10 }, { id: 'Bookings', purpose: #STANDARD, type: #LINEITEM_REFERENCE, label: 'Bookings', position: 20, targetElement: '_Booking' } ]
@UI: { headerInfo: { typeName: 'Travel', typeNamePlural: 'Travels', title: { type: #STANDARD, value: 'Description' }, description: { type: #STANDARD, value: 'TravelId' } } }
@UI: { lineItem: [{ position: 10, importance: #HIGH }], identification: [{ position: 10 }], selectionField: [{ position: 10 }] } TravelId;
@UI: { lineItem: [{ position: 20, importance: #HIGH }], identification: [{ position: 20 }], selectionField: [{ position: 20 }] } @Consumption.valueHelpDefinition: [{ entity: { name: 'ZI_Agency', element: 'AgencyId' } }] AgencyId;
@UI: { lineItem: [{ position: 30, importance: #HIGH }], identification: [{ position: 30 }], selectionField: [{ position: 30 }] } @Consumption.valueHelpDefinition: [{ entity: { name: 'ZI_Customer', element: 'CustomerId' } }] CustomerId;
@UI: { lineItem: [{ position: 40, importance: #MEDIUM }], identification: [{ position: 40 }] } BeginDate;
@UI: { lineItem: [{ position: 50, importance: #MEDIUM }], identification: [{ position: 50 }] } EndDate;
@UI: { lineItem: [{ position: 60, importance: #HIGH, criticality: 'StatusCriticality' }], identification: [{ position: 60, criticality: 'StatusCriticality' }], selectionField: [{ position: 40 }] } Status;
@UI: { lineItem: [{ position: 70, importance: #HIGH }], identification: [{ position: 70 }] } TotalPrice;
@UI.identification: [ { type: #FOR_ACTION, dataAction: 'acceptTravel', label: 'Accept', position: 10 }, { type: #FOR_ACTION, dataAction: 'rejectTravel', label: 'Reject', position: 20 } ] StatusText;}Metadata Extension for Manager View
@Metadata.layer: #COREannotate entity ZC_TravelManager with{ @UI.facet: [ { id: 'Overview', purpose: #STANDARD, type: #IDENTIFICATION_REFERENCE, label: 'Overview', position: 10 } ]
@UI: { headerInfo: { typeName: 'Approval', typeNamePlural: 'Approvals', title: { type: #STANDARD, value: 'Description' } } }
// Only relevant fields for managers @UI.lineItem: [{ position: 10, importance: #HIGH }] TravelId;
@UI.lineItem: [{ position: 20, importance: #HIGH }] CustomerName;
@UI.lineItem: [{ position: 30, importance: #HIGH }] TotalPrice;
@UI.lineItem: [{ position: 40, importance: #HIGH, criticality: 'StatusCriticality' }] Status;
// Prominent action buttons @UI.lineItem: [ { type: #FOR_ACTION, dataAction: 'acceptTravel', label: 'Approve', position: 50 }, { type: #FOR_ACTION, dataAction: 'rejectTravel', label: 'Reject', position: 60 } ] Description;}Feature Control per Projection
Through Dynamic Feature Control, you can implement different rules per projection. The logic is defined centrally in the Behavior Implementation but controlled via the Behavior Projection.
Example: Different Feature Control
In the Interface Behavior, you define the features:
define behavior for ZI_Travel alias Travel{ // ...
// Features for differentiated control action ( features: instance ) acceptTravel result [1] $self; action ( features: instance ) rejectTravel result [1] $self; delete ( features: instance );
field ( features: instance ) Status;}The implementation checks the context:
CLASS zbp_i_travel IMPLEMENTATION.
METHOD get_instance_features. READ ENTITIES OF ZI_Travel IN LOCAL MODE ENTITY Travel FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(travels).
LOOP AT travels INTO DATA(travel). APPEND VALUE #( %tky = travel-%tky
" Status-dependent features %action-acceptTravel = COND #( WHEN travel-Status = 'O' THEN if_abap_behv=>fc-o-enabled ELSE if_abap_behv=>fc-o-disabled )
%action-rejectTravel = COND #( WHEN travel-Status = 'O' THEN if_abap_behv=>fc-o-enabled ELSE if_abap_behv=>fc-o-disabled )
" Delete only for open travels %delete = COND #( WHEN travel-Status = 'O' THEN if_abap_behv=>fc-o-enabled ELSE if_abap_behv=>fc-o-disabled )
" Status field read-only when not open %field-Status = COND #( WHEN travel-Status = 'O' THEN if_abap_behv=>fc-f-unrestricted ELSE if_abap_behv=>fc-f-read_only )
) TO result. ENDLOOP. ENDMETHOD.
ENDCLASS.In the API projection, you can then completely omit features or override them by adjusting the Behavior Projection accordingly.
Service Definition and Binding
For each projection, you create a separate service:
Service Definition for Fiori App
@EndUserText.label: 'Travel - Fiori Service'define service ZUI_TRAVEL_O4 { expose ZC_Travel as Travel; expose ZC_Booking as Booking; expose ZI_Agency as Agency; expose ZI_Customer as Customer;}Service Definition for API
@EndUserText.label: 'Travel - API Service'define service ZAPI_TRAVEL_O4 { expose ZC_TravelAPI as Travel; expose ZC_BookingAPI as Booking;}Service Binding
For each service, you create a Service Binding with the appropriate protocol:
| Binding | Protocol | Usage |
|---|---|---|
| ZUI_TRAVEL_O4_V4 | OData V4 - UI | Fiori Elements |
| ZAPI_TRAVEL_O4_V4 | OData V4 - Web API | REST clients |
| ZUI_TRAVEL_O2 | OData V2 | Legacy Fiori apps |
Best Practices
1. Clear Separation of Layers
- Interface View: Complete data model, no UI annotations- Projection View: Application-specific fields, no business logic- Metadata Extension: UI annotations separate from view2. Consistent Naming
ZI_Travel → Interface (Base)ZC_Travel → Fiori ConsumptionZC_TravelAPI → API ConsumptionZC_TravelReport → Reporting ConsumptionZC_TravelManager → Manager Consumption3. Redirect Associations Correctly
When projecting compositions, the association must be redirected to the corresponding child projection:
_Booking : redirected to composition child ZC_Booking4. Mind the Provider Contract
The provider contract defines the purpose of the projection:
| Contract | Usage |
|---|---|
| transactional_query | Standard for UI and API |
| transactional_interface | For A2A integration |
| analytical_query | For Embedded Analytics |
Avoiding Common Errors
Error 1: Missing Behavior Projection
" WRONG: Only CDS Projection without Behaviordefine root view entity ZC_Travel as projection on ZI_Travel { ... }
" CORRECT: CDS Projection WITH Behavior Projection" 1. Create CDS View" 2. Create Behavior Projection" 3. Service Definition and BindingError 2: Association Not Redirected
" WRONG: Association still points to Interface_Booking,
" CORRECT: Redirect association to Projection_Booking : redirected to composition child ZC_Booking,Error 3: Inconsistent Hierarchy
" WRONG: Child projection points to interface parentdefine view entity ZC_Booking as projection on ZI_Booking{ _Travel, " Points to ZI_Travel instead of ZC_Travel}
" CORRECT: Consistent projection hierarchydefine view entity ZC_Booking as projection on ZI_Booking{ _Travel : redirected to parent ZC_Travel,}Further Reading
- RAP Basics - Introduction to RESTful ABAP Programming
- CDS Annotations - UI annotations and Metadata Extensions
- RAP Feature Control - Dynamic control of UI elements
- Authorization Checks - Authorization checks in RAP
Conclusion
Projection Views are a powerful concept for exposing a Business Object for different use cases. Through clean separation of Interface Layer, Projection Layer, and Service Layer, you achieve:
- Reusability: One Business Object, multiple UIs
- Maintainability: Changes to interface affect all projections
- Flexibility: Different features, fields, and annotations per use case
- Security: Restricted operations for specific user groups
With the combination of Projection Views, Behavior Projections, and Metadata Extensions, you have full control over how your Business Object is presented and used in different contexts.