Core Data Services (CDS) Data Model @ ABAP RESTful Application Programming Model (On-Premise)
September 12, 2022
ABAP | CDS | RAP | SAP
  1. Design / Prototype
  2. Create Dictionary Objects
  3. Core Data Services (CDS)
  4. Business Service
  5. Business Object Behavior
  6. Create a Fiori App
  7. Source (GitHub) for Data Model/Behaviour/Service
  8. Source (GitHub) for User Interface (Fiori Elements App)

We will define the Interface Views for the Phone Book App using CDS (Core Data Services) in ADT (ABAP Development Tools)

Data Model

We will be creating CDS View Entities for Phone Book. There will be one root node Contacts (Phone Book Head) and one child node Phone Numbers (Phone Book Item).

SAP recommends using CDS view entities instead of DDIC-based view due to technical improvements, such as performance at activation, and so on.
DescriptionCDS DDIC-Based ViewsCDS View Entities
Optimized CDS activationThe CDS entity, CDS DDIC-based view, and database view need to be activated.Only the CDS entity and the respective database view on SAP HANA need to be activated. Consequently, the performance of the activation runs faster.

Simplified handling of CDS extends
Syntax and annotation checksSimple checksStricter checks that indicate critical situations more explicitly
Client handlingBased on different algorithms.

Controlled using the ClientHandling.type and 
ClientHandling.algorithm annotations
Automatized for complete transparency

No annotation allowed for client handling
Database objectView / table functionView
CDS-managed DDIC view in ABAP DictionaryCreation upon activationNo CDS-managed DDIC view is generated upon activation
Naming conventionThree names for the CDS object, CDS entity, and the CDS-managed DDIC view on SAP HANAOne name for the CDS object, CDS entity, and database view on SAP HANA
Source: Compare CDS DDIC-based View vs CDS View Entity

Phone Book Header Interface View

CDS Root view entity ZINTLRA_I_PB_HEAD is created with composition (child) ZINTLRA_I_PB_ITEM

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Phone Book Header Interface View'
define root view entity ZINTLRA_I_PB_HEAD as select from zintlra_pb_d_hdr 
composition [0..*] of ZINTLRA_I_PB_ITEM as _item
association [1..1] to ZINTLRA_I_PB_USER as _owner
                                        on $projection.PbOwner = _owner.OwnerID
association [1..1] to ZINTLRA_I_PB_TAG as _tagText
                   on $projection.PbTagCode = _tagText.PbTagCode
                  and _tagText.PbLanguage = $session.system_language  
{
    key pb_uuid            as PbUuid,
        pb_id              as PbId,
        @Semantics.user.createdBy: true
        pb_owner           as PbOwner,
        _owner.FullName    as PbOwnerName,
        pb_tag_code        as PbTagCode,
        _tagText.PbTagText as PbTagText,
        pb_first_name      as PbFirstName,
        pb_last_name       as PbLastName,
        concat_with_space(pb_first_name, pb_last_name, 1) as PbContactName,
        @Semantics.systemDateTime.createdAt: true
        pb_created_at      as PbCreatedAt,
        @Semantics.systemDateTime.lastChangedAt: true
        pb_changed_at      as PbChangedAt,
        @Semantics.eMail.address: true
        pb_email_id        as PbEmailId,
        pb_favourite       as PbFavourite,
        
        /* Associations */
        _item,
        _owner,
        _tagText
}where pb_owner = $session.user

Phone Book Item Interface View

CDS view entity ZINTLRA_I_PB_ITEM is created with association to parent ZINTLRA_I_PB_HEAD

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Phone Book Items Interface View'
define view entity ZINTLRA_I_PB_ITEM as select from zintlra_pb_d_itm 
association to parent ZINTLRA_I_PB_HEAD as _hdr
                   on $projection.PbUuid = _hdr.PbUuid
association [1..1] to ZINTLRA_I_PB_CAT as _catText
                   on $projection.PbCategory = _catText.PbCategory
                  and _catText.PbLanguage = $session.system_language                
{
    key pb_item_uuid  as PbItemUuid,
    pb_uuid           as PbUuid,
    pb_id             as PbId,
    pb_item_id        as PbItemId,
    pb_category       as PbCategory,
    @Semantics.text: true
    _catText.PbCategoryText,
    pb_telephone      as PbTelephone,
    @Semantics.systemDateTime.createdAt: true
    pb_created_at     as PbCreatedAt,
    @Semantics.systemDateTime.lastChangedAt: true
    pb_changed_at     as PbChangedAt,
    pb_default        as PbDefault,
    
    /* Associations */
    _hdr,
    _catText
}

Phone Book User View

CDS for User Details

@AbapCatalog.sqlViewName: 'ZINTIPBUSER'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Phone Book User View'
define view ZINTLRA_I_PB_USER 
as select from usr21 as _User
  association [0..1] to adrp as _Person                   on  _Person.persnumber = $projection.Person
                                                          and _Person.persnumber <> ''
{
      @ObjectModel.text.element : 'UserDescription'
      @Semantics.contact.type: #PERSON
  key _User.bname                                                                                 as OwnerID,
      _User.persnumber                                                                            as Person,

      @Semantics.name.givenName: true
      _Person.name_first                                                                          as FirstName,

      @Semantics.name.familyName: true
      _Person.name_last                                                                           as LastName,
      
      @Semantics.name.fullName: true
      cast(coalesce( _Person.name_text , _User.techdesc ) as ad_namtext preserving type )         as FullName
}

Phone Book Tags View

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Phone Book Tags'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType:{
    serviceQuality: #X,
    sizeCategory: #S,
    dataClass: #MIXED
}
define view entity ZINTLRA_I_PB_TAG as select from zintlra_pb_d_tag {
    key pb_tag_code as PbTagCode,
    key pb_spras    as PbLanguage,
    pb_tag_text     as PbTagText
}

Phone Book Contact Category View

@AbapCatalog.sqlViewName: 'ZINTIPBCAT'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@VDM.viewType: #BASIC
@EndUserText.label: 'Contact Category View'
define view ZINTLRA_I_PB_CAT as select from zintlra_pb_d_cnt {
    key pb_category  as PbCategory,
    key pb_spras     as PbLanguage,
    pb_category_text as PbCategoryText
}

<< To Top

Data Model Projection

CDS Projection View
A CDS projection view is a special view that is based on another CDS view and exposes only a subset of elements of the projected entity.

Use
A projection view enables you to expose a subset of data from an underlying data model, for example, to be used in an OData service. It is a direct projection of an underlying CDS view without parameters and exposes a subset of elements of the projected entity, which are defined in the list of elements.
In a business application, a projection view allows to restrict access to, denormalize, and fine-tune the underlying data model.

Use Cases
As an application developer, you want to, for example, …
– contemplate a subset of the business object as a business service.
– alias or rename the projected subset.
– omit or hide chosen elements or associations
Source

Phone Book Item Projection View

Consumption.valueHelpDefinition.entity[] 
Defines the binding for the value help to the value help providing entity. It requires specification of the entity and the element providing the value help for the annotated element.
name: Specifies the entity which contains the element that provides the value help.
element: Specifies the element in the entity referenced in the name that provides the value help for the annotated element.

@Search.defaultSearchElement: true
Specifies that the element is to be considered in a freestyle search (for example a SELECT…) where no columns are specified.
Usually, such a search must not operate on all elements – for performance reasons, and because not all elements (e.g. internal keys) do qualify for this kind of access.

@ObjectModel.text.element:
Establishes the conjunction of a field with its descriptive language-independent texts.

CDS ANNOTATIONS
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Phone Book Item Consumption View'
@Search.searchable: true
@Metadata.allowExtensions: true

define view entity ZINTLRA_C_PB_ITEM as projection on ZINTLRA_I_PB_ITEM {
    
    key PbItemUuid,
    PbUuid,
    PbId,
    PbItemId,
    
    @Search.defaultSearchElement: true
    @Consumption.valueHelpDefinition: [{ entity: { name: 'ZINTLRA_I_PB_CAT', element: 'PbCategory' } }]
    @ObjectModel.text.element: ['PbCategoryText']
    PbCategory,
    PbCategoryText,
    
    @Search.defaultSearchElement: true
    PbTelephone,
    PbCreatedAt,
    PbChangedAt,
    PbDefault,
    
    /* Associations */
    _catText,
    _hdr:redirected to parent ZINTLRA_C_PB_HEAD
}

Phone Book Header Projection View

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Phone Book Header Consumption View'
@Search.searchable: true
@Metadata.allowExtensions: true

@ObjectModel.semanticKey: ['PbUuid']

define root view entity ZINTLRA_C_PB_HEAD 
    as projection on ZINTLRA_I_PB_HEAD
 {
    @Search.defaultSearchElement: true
   key PbUuid,
    PbId,    
    PbOwner,
    PbOwnerName,
    
    @Consumption.valueHelpDefinition: [{ entity: { name: 'ZINTLRA_I_PB_TAG', element: 'PbTagCode' } }]
    @ObjectModel.text.element: ['PbTagText']
    @Search.defaultSearchElement: true
    PbTagCode,
    
    PbTagText,
    @Search.defaultSearchElement: true
    PbFirstName,
    @Search.defaultSearchElement: true
    PbLastName,
    PbContactName,    
    PbCreatedAt,
    PbChangedAt,
    PbEmailId,
    PbFavourite,
    
    /* Associations */
    _item: redirected to composition child ZINTLRA_C_PB_ITEM,
    _owner
} 

<< To Top

Metadata Extension

Metadata extensions enable you to add customer-specific annotations to SAP’s CDS entities. Note that these changes do not result in modifications.

Definition
A metadata extension is a development object that provides CDS annotations in order to extend the CDS annotations used in a CDS entity. The standard ABAP Workbench functions (transport, syntax check, activation, and so on) are supported.

Use
Metadata extensions enable you to write the annotations for a CDS entity in a different document to separate them from the CDS entity.

Overview
The metadata of CDS entities is not extensible by default.
To use a metadata extension for a CDS entity, you have to consider the following conditions:

  1. In the definition of the CDS entity, the @Metadata.allowExtensions annotation with the value true is added. This annotation explicitly allows the use of metadata extensions.
  2. In the metadata extension, you have to define the name of the CDS entity to be annotated in the annotate view statement.
  3. In the Switch Framework, metadata extensions are switchable.

Advantages
You can benefit from the following advantages using metadata extensions:

  1. Separation of Concerns: Separating the metadata specified in the annotations from the implementation of the entity:
    • Improves the readability of the source code
    • Simplifies the development and maintenance of the CDS entity in addition, the metadata can be developed and updated independently of the data definition.
  2. ABAP Dictionary-independent activation: When activating a CDS entity, the metadata extensions will be ignored. This results in the following advantages:
    • It reduces the number of ABAP Dictionary (mass) activations required to develop and maintain the CDS entity.
    • It speeds up the overall development process.
    • It facilitates changing the metadata of a CDS entity in a running system, thereby reducing downtime.
  3. Modification-free enhancements: Customers, partners, and industries can customize the metadata without modifying the CDS entity. In addition, metadata extensions are switchable. This means the metadata can be specifically enabled or disabled depending on the use case.

Activation
In general, in a metadata extension, only those annotations are permitted that do not affect the ABAP Dictionary activation/generation or the activation/generation of secondary objects (for example, OData services). For example, the ABAP annotation @EndUserText and the component-specific annotations @UI can be specified in metadata extensions. A syntax error occurs if annotations that are not permitted are specified.

Source

Phone Book Header Extension

UI.headerinfo
Annotations belonging to UI.headerInfo describe an entity, its title and an optional short description, the name of its entity in singular and plural form, and optional image URLs for the individual entity.

UI.lineitem
Annotations belonging to UI.lineItem represent an ordered collection of data fields that are used to represent data from multiple data instances in a table or a list.

UI.identification
Annotation belonging to UI.identification represents an ordered collection of specific data fields that together with headerInfo identifies an entity to an end user. This annotation is displayed in the General Information section in the body of the object view floorplan of an item, for example.

UI.selectionField
Annotations belonging to UI.selectionField allow filtering a list of data. UI.selectionField annotations are usually used in an initial page floorplan as a filter bar.

UI.facet
Is used to manipulate the layout of the Object Page

UI.presentationVariant.sortOrder
Annotations belonging to UI.presentationVariant.sortOrder represent a collection of sorting parameters that can be provided inline or by a reference to a Common.SortOrder annotation (syntax is identical to AnnotationPath).

UI.presentationVariant.visualizations
Annotations belonging to UI.presentationVariant.visualizations represent a collection of available visualization types.
The following types are supported:
– UI.lineItem
– UI.chart
– UI.dataPoint

{ type: #FOR_ACTION, dataAction: ‘<action_name>’, label: ‘<action_button_label>’ }
For exposing actions to the users

Source

 @Metadata.layer: #CORE

@UI:{ 
    headerInfo: {
        typeName: 'Contact',
        typeNamePlural: 'Contacts',
        title: {
            type: #STANDARD,
            label: 'Contact',
            value: 'PbContactName'
        }
    },
    presentationVariant: [{
        sortOrder: [{
            by: 'PbUuid',
            direction: #DESC
        }],
        visualizations: [{
            type: #AS_LINEITEM
        }]
    }]
}
annotate view ZINTLRA_C_PB_HEAD
    with 
{
    @UI.facet: [{ 
                    id:'Contact', 
                    purpose:#STANDARD, 
                    type: #IDENTIFICATION_REFERENCE, 
                    label: 'Contact',
                    position: 10
                 },
                { 
                    id:'PhoneNumber', 
                    purpose:#STANDARD, 
                    type: #LINEITEM_REFERENCE, 
                    label: 'Phone Numbers', 
                    position: 20, 
                    targetElement: '_item'
                }]
   
   @UI.hidden: true
   PbOwner;
   
   @UI.hidden: true
   PbOwnerName;
   
   @UI.hidden: true
   PbId;
   
   @UI: { 
      lineItem: [ { position: 10 } ], 
      identification: [ { position: 10 } ], 
      selectionField: [ { position: 10 } ] 
   }
   PbTagCode;
   
   @UI: { 
      lineItem: [ { position: 20 } ], 
      identification: [ { position: 20 } ], 
      selectionField: [ { position: 20 } ] 
   }
   PbFirstName;
   @UI: { 
      lineItem: [ { position: 30 } ], 
      identification: [ { position: 30 } ], 
      selectionField: [ { position: 30 } ] 
   }
   PbLastName;
   
   @UI.hidden: true
   PbCreatedAt;
   
   @UI.hidden: true
   PbChangedAt;
   
   @UI: { 
      lineItem: [ { position: 40 } ], 
      identification: [ { position: 40 } ]
   }
   PbEmailId;
   
   @UI: { 
      lineItem: [ { position: 50 },
                  { type: #FOR_ACTION, dataAction: 'setFavourite', label: 'Set Favourite' }, 
                  { type: #FOR_ACTION, dataAction: 'removeFavourite', label: 'Not a Favourite' } 
      ], 
      identification: [ { position: 50 },
                        { type: #FOR_ACTION, dataAction: 'setFavourite', label: 'Set Favourite' },
                        { type: #FOR_ACTION, dataAction: 'removeFavourite', label: 'Not a Favourite' }  
      ],
      selectionField: [ { position: 50 } ] 
    }
    PbFavourite;
    
   /* Associations */
   @UI.selectionField: [{ element: '_item.PbTelephone', position: 60 }]
   _item;  
}

Phone Book Item Extension

@Metadata.layer: #CORE

@UI:{ 
    headerInfo:{ 
        typeName: 'Phone Number',
        typeNamePlural: 'Phone Numbers',
        title:{ 
            type: #STANDARD,
            value: 'PbCategoryText'
        }
    }
}
annotate view ZINTLRA_C_PB_ITEM
    with 
{

    @UI.facet: [ { 
                    id: 'Booking', 
                    purpose: #STANDARD, 
                    type: #IDENTIFICATION_REFERENCE, 
                    label: 'Phone Number', 
                    position: 10 
                } ]

    @UI.hidden: true
    PbItemUuid;
    @UI.hidden: true
    PbUuid;
    @UI.hidden: true
    PbId;
    @UI.hidden: true
    PbItemId;
    
    @UI: { 
      lineItem: [ { position: 10 } ], 
      identification: [ { position: 10 } ]
    }
    PbCategory;
    
    @UI: { 
      lineItem: [ { position: 20 } ], 
      identification: [ { position: 20 } ]
    }
    PbTelephone;
    
    @UI: { 
      lineItem: [ { position: 30 },
                  { type: #FOR_ACTION, dataAction: 'setDefault', label: 'Set Default' }
      ], 
      identification: [ { position: 30 },
                        { type: #FOR_ACTION, dataAction: 'setDefault', label: 'Set Default' }
      ]
    }
    PbDefault;
    
    @UI.hidden: true
    PbCreatedAt;
    @UI.hidden: true
    PbChangedAt;
    
}

<< To Top