RAP Basics
Architect / Cross-trackintermediate

RAP Basics Part 3: Projection Layer and Metadata Extensions

Key takeaway

Build the projection layer of a RAP BO: projection view entities (ZC ), provider contract transactional query, redirecting compositions, value helps, search and text annotations, and moving UI annotations into metadata extensions so one BO can serve several apps.

Explanation

📌 KEY POINTS
✅ The projection layer is the app-specific view of a BO: it selects fields, adds value helps, search and texts, and restricts operations — it adds no business logic.

✅ Projection views use "as projection on" and, for transactional services, provider contract transactional_query.

✅ Compositions must be redirected in the projection (redirected to composition child ZC_Booking) so navigation stays inside the projection layer.

⚠️ Putting @UI annotations directly into the projection view makes it hard to read and to adapt — use a metadata extension (@Metadata.allowExtensions: true).

💡 One interface BO, many projections: a UI projection for Fiori and an API projection for integration.

━━━━━━━━━━━━━━━━━━━━
▶ WHERE WE ARE

Part 2 built tables and interface views. The projection layer sits on top and is what services expose.

━━━━━━━━━━━━━━━━━━━━
▶ 1. PROJECTION VIEW ENTITY

  • define root view entity ZC_Travel provider contract transactional_query as projection on ZI_Travel.
  • Only list the fields the app needs.
  • Add @Consumption.valueHelpDefinition for fields like AgencyID, CustomerID.

━━━━━━━━━━━━━━━━━━━━
▶ 2. SEARCH AND TEXTS

  • @Search.searchable: true on the view, @Search.defaultSearchElement on fields.
  • @ObjectModel.text.element: ['AgencyName'] shows text next to the ID.
  • Denormalise names through associations (_Agency.Name).

━━━━━━━━━━━━━━━━━━━━
▶ 3. REDIRECTING COMPOSITIONS

  • Interface: composition [0..*] of ZI_Booking as _Booking.
  • Projection: _Booking : redirected to composition child ZC_Booking.
  • Child projection: _Travel : redirected to parent ZC_Travel.

━━━━━━━━━━━━━━━━━━━━
▶ 4. METADATA EXTENSIONS

  • @Metadata.layer: #CORE (or #CUSTOMER) annotate view ZC_Travel with { … }.
  • Holds @UI.headerInfo, @UI.facet, @UI.lineItem, @UI.identification, @UI.selectionField.
  • Layers allow partner/customer overrides without changing the view.

━━━━━━━━━━━━━━━━━━━━
▶ 5. PROJECTION BDEF

  • projection; strict ( 2 ); use draft; define behavior for ZC_Travel { use create; use update; use action … }
  • Leaving out "use delete" removes delete from this app only.

━━━━━━━━━━━━━━━━━━━━
▶ STEP BY STEP

  1. Create ZC_Travel as projection on ZI_Travel with provider contract.
  2. Add value helps, search and text annotations.
  3. Redirect compositions in root and child projections.
  4. Create a metadata extension with UI annotations.
  5. Create the projection BDEF with "use" statements.

✅ CHECKPOINT: you can explain why UI annotations belong in a metadata extension and business logic in the interface BDEF.

Code example

ABAP Code
// ===== STEP 1: projection root view entity =====@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Travel - projection for Fiori'@Metadata.allowExtensions: true@Search.searchable: truedefine root view entity ZC_Travel  provider contract transactional_query  as projection on ZI_Travel{  key TravelUUID,      @Search.defaultSearchElement: true      TravelID,      @Consumption.valueHelpDefinition: [{ entity: { name: '/DMO/I_Agency', element: 'AgencyID' } }]      @ObjectModel.text.element: ['AgencyName']      AgencyID,      _Agency.Name as AgencyName,      TotalPrice,      CurrencyCode,      OverallStatus,      LocalLastChangedAt, // ===== STEP 2: redirect composition to the child projection =====      _Booking : redirected to composition child ZC_Booking,      _Agency} // ===== STEP 3: metadata extension with UI annotations =====@Metadata.layer: #COREannotate view ZC_Travel with{  @UI.facet: [{ id: 'Travel', purpose: #STANDARD, type: #IDENTIFICATION_REFERENCE,                label: 'Travel', position: 10 },              { id: 'Booking', purpose: #STANDARD, type: #LINEITEM_REFERENCE,                label: 'Bookings', position: 20, targetElement: '_Booking' }]  @UI: { lineItem: [{ position: 10 }], identification: [{ position: 10 }], selectionField: [{ position: 10 }] }  TravelID;  @UI: { lineItem: [{ position: 20 }], identification: [{ position: 20 }] }  AgencyID;  @UI: { lineItem: [{ position: 30 }], identification: [{ position: 30 }] }  TotalPrice;} // ===== STEP 4: projection behavior definition =====projection;strict ( 2 );use draft; define behavior for ZC_Travel alias Travel{  use create;  use update;  // no "use delete": this app may not delete travels  use action acceptTravel;  use association _Booking { create; }}

Real project scenario

A consumer-health client needed two apps on the same promotion BO: a planner app with full editing and a read-mostly approver app. The first build duplicated the BO — two interface stacks with copied validations, which drifted apart within a month. The architect consolidated to one interface BO with two projections: ZC_PromoPlanner (use create/update/delete, all actions) and ZC_PromoApprover (only use action approve/reject), each with its own metadata extension. Validations now exist once, and a later rule change needed one edit instead of two.

Common mistakes

  • Writing validations or determinations for a projection instead of the interface BO.
  • Forgetting to redirect compositions, so navigation leaves the projection layer and activation fails.
  • Missing provider contract transactional_query on transactional projections.
  • Putting all UI annotations into the projection view instead of a metadata extension.
  • Copying a whole BO to get a second app instead of adding a second projection.

Best practices

  • Keep projections thin: field selection, value helps, texts, search, "use" statements.
  • Use metadata extensions for all @UI annotations.
  • Create separate projections for UI and API consumers.
  • Denormalise display texts in the projection via associations.
  • Restrict operations per app in the projection BDEF rather than with code.

Interview angle

Questions: "What is the purpose of the projection layer in RAP?", "Why use metadata extensions?", "How do you give two apps different operations on one BO?"
Strong answer: the projection is the consumer-specific view — fields, value helps, texts, search and a subset of operations via "use" statements — with provider contract transactional_query and redirected compositions. Metadata extensions separate UI annotations and support layering. Two apps = one interface BO + two projections + two projection BDEFs, not two BOs.

Practice this lesson

Quick check

1. Where should @UI annotations for a Fiori Elements app on a RAP BO preferably be placed?

2. Why must a composition be redirected in the projection view (e.g. _Booking : redirected to composition child ZC_Booking)?

3. What is the projection layer of a RAP business object used for?

Interview questions

1. Two Fiori apps must work on the same RAP business object: a planner app with full editing and an approver app that may only approve or reject. A colleague proposes copying the BO. What do you propose instead?

Answer it in your own words first, then compare.

2. A team exposed an interface CDS view entity (ZI_*) directly in the service definition. External consumers now depend on internal technical fields, and every data-model change breaks them. How should the layers have been set up?

Answer it in your own words first, then compare.

Practice more with Interview AI →

Reference-verified: the steps are checked against official SAP documentation and tutorials, and object and field names in the sample code are checked for consistency. The code has not been compiled or run on an SAP system by ERPClimb. Syntax marked release-dependent varies by SAP release, so check it in your own system.

More lessons in this topic

Interview questions on this topic

Related SAP topics