Renzo Colle, Ralf Dentzer, Jan Hrastnik
Core Data Services for ABAP®
3rd edition 2024, updated and revised
Imprint
This e-book is a publication many contributed to, specifically:
Editor Megan Fuerst
Acquisitions Editor Hareem Shafi
Copyeditor Julie McNamee
Cover Design Bastian Illerhaus
Photo Credit Unsplash: li-zhang-K-DwbsTXliY
Production E-Book Kelly O’Callaghan
Typesetting E-Book Satz-Pro (Germany)
We hope that you liked this e-book. Please share your feedback with
us and read the Service Pages to find out how to contact us.
Library of Congress Cataloging-in-Publication Control
Number:2023945756
ISBN 978-1-4932-2376-3 (print)
ISBN 978-1-4932-2377-0 (e-book)
ISBN 978-1-4932-2378-7 (print and e-book)
© 2024 by Rheinwerk Publishing Inc., Boston (MA)
3rd edition 2024, updated and revised
Notes on Usage
This e-book is protected by copyright. By purchasing this e-book,
you have agreed to accept and adhere to the copyrights. You are
entitled to use this e-book for personal purposes. You may print and
copy it, too, but also only for personal use. Sharing an electronic or
printed copy with others, however, is not permitted, neither as a
whole nor in parts. Of course, making them available on the internet
or in a company network is illegal as well.
For detailed and legally binding usage conditions, please refer to the
section Legal Notes.
This e-book copy contains a digital watermark, a signature that
indicates which person may use this copy:
Notes on the Screen Presentation
You are reading this e-book in a file format (EPUB or Mobi) that
makes the book content adaptable to the display options of your
reading device and to your personal needs. That’s a great thing; but
unfortunately not every device displays the content in the same way
and the rendering of features such as pictures and tables or
hyphenation can lead to difficulties. This e-book was optimized for
the presentation on as many common reading devices as possible.
If you want to zoom in on a figure (especially in iBooks on the iPad),
tap the respective figure once. By tapping once again, you return to
the previous screen. You can find more recommendations on the
customization of the screen layout on the Service Pages.
Table of Contents
Notes on Usage
Table of Contents
Preface
1 Modeling Your First CDS
Views
1.1 Define the Data Model of the Application
1.2 Implement the Data Model of the
Application
1.2.1
1.2.2
1.2.3
1.2.4
Create Database Tables
Create a CDS View
Edit a CDS View
Create a Hierarchy of CDS Views
1.3 Summary
2 Fundamentals of CDS Data
Modeling
2.1 Overview of CDS Models
2.2 Overview of CDS View Syntax
2.3 Key Fields
2.4 Cast Operations
2.5 Typed Literals
2.6 Simple Types
2.7 Case Statements
2.8 Session Variables
2.9 Client Handling
2.10 Select Distinct Statements
2.11 Union Views
2.11.1 Union Definitions
2.11.2 Union and Union All Logic
2.12 Intersect and Except Statements
2.13 Joins
2.14 SQL Aggregation Functions
2.15 Projection Fields
2.16 Parameters
2.17 Reference Fields
2.18 Conversion Functions for Currencies and
Quantity Units
2.19 Provider Contracts
2.20 Entity Buffer Definitions
2.21 Summary
3 Associations
3.1
3.2
3.3
3.4
3.5
3.6
Define Associations
Expose Associations
Model Compositional Relations
Model M:N Relations
Project Associations
Use Associations in CDS Views
3.6.1 Define Path Expressions
3.6.2 Implicit Joins
3.6.3 Cardinality Changes Resulting from Path
Expressions
3.6.4 Restrictions for Defining Path Expressions
3.7 Use Associations in ABAP Code
3.8 Summary
4 Annotations
4.1 Annotation Definitions
4.1.1 Syntax Overview
4.1.2 Annotation Names
4.1.3
4.1.4
4.1.5
4.1.6
Annotation Types and Values
Enumeration Values
Annotation Default Values
Annotation Scopes
4.2 Effects of Annotations
4.3 Propagation Logic for Annotations
4.3.1 Propagation Logic within Simple Type Hierarchies
4.3.2 Propagation Logic of Element Annotations
4.3.3 Consistency Aspects
4.4 Metadata Extensions
4.5 Active Annotations
4.6 Summary
5 Access Controls
5.1 Fundamentals of Access Controls
5.2 Mode of Action of Access Controls
5.3 Implementation Patterns for Access
Controls
5.3.1 Implement Access Controls with Path Expressions
5.3.2 Inherit Implementation of Access Controls
5.3.3 Implement Access Controls without Using
Authorization Objects
5.3.4 Implement Access Controls for Analytical Queries
5.3.5 Implement Access Controls for Transactional
Applications
5.3.6 Implement Access Controls on the Field Level
5.3.7 Change Access Controls of SAP-Delivered CDS
Models
5.3.8 Block Standard Data Selections from CDS Models
5.3.9 Decouple Access Controls from User Input
5.3.10 Map CDS Fields onto Fields of Authorization
Objects Using Indirection
5.4 Test Access Controls
5.5 Summary
6 Business Services
6.1 Projection Views
6.2 Service Definitions
6.3 Service Bindings
6.3.1
6.3.2
6.3.3
6.3.4
OData UI Services
OData Web API Services
InA UI Services
SQL Web API Services
6.4 Testing Business Services
6.4.1 Use OData Service URLs
6.4.2 Use UI Previews
6.5 Summary
7 Native SAP HANA Functions in
CDS
7.1
7.2
7.3
7.4
Implementation of a CDS Table Function
Application Scenarios
Improve Performance and Avoid Errors
Summary
8 Modeling Application Data
8.1 Application Architecture in SAP S/4HANA
8.2 Field Labels
8.2.1 Determination of a Field Label
8.2.2 Length of a Field Label
8.3 Field Semantics
8.3.1
8.3.2
8.3.3
8.3.4
8.3.5
8.4
8.5
8.6
8.7
Quantities and Amounts
Aggregation Behavior
System Times
Text and Languages
Information for the Fiscal Year
Foreign Key Relations
Text Relations
Composition Relations
Time-Dependent Data
8.8 Summary
9 The Virtual Data Model of SAP
S/4HANA
9.1 Why a Virtual Data Model?
9.2 SAP Object Types and Object Node Types
9.3 Categories of CDS Entities in the Virtual
Data Model
9.3.1
9.3.2
9.3.3
9.3.4
Basic Interface Views
Composite Interface Views
Consumption Views
Other Types of VDMs
9.4 Naming in the Virtual Data Model
9.5 Basic Interface View for the Sales Order
9.5.1
9.5.2
9.5.3
9.5.4
View Annotations
Structure of the Sales Order View
Specialization
Element Annotations
9.6 Tips for Finding Virtual Data Model Views
9.6.1
App
9.6.2
9.6.3
9.6.4
SAP Business Accelerator Hub and View Browser
Search in ABAP Development Tools
Search Views with Specific Annotations
ABAP Where-Used List
9.7 Summary
10 Modeling Analytical
Applications
10.1 Analytics in SAP S/4HANA
10.2 Analytical Views
10.2.1
10.2.2
10.2.3
10.2.4
10.2.5
10.2.6
First Analytical Cube View
Test Environment for Analytical Views
Analytical Cube Views
Analytical Dimension Views
Analytical Model in the Test Environment
Consistency of the Analytical Model
10.3 Analytical Queries
10.3.1
10.3.2
10.3.3
10.3.4
10.3.5
10.3.6
10.3.7
10.3.8
Definition of an Analytical Query
Initial Layout of a Query
Filter, Select Options, Parameters
Calculation of Measures
Restricted Measures
Exception Aggregation
Currencies and Conversion
Analytical Query Selecting from Dimension Views
10.4 Analytical Infrastructure
10.5 Summary
11 Modeling Transactional
Applications
11.1 Transactional Applications
11.2 Transactional Infrastructure in SAP
S/4HANA
11.3 Transactional Object Models
11.3.1 Object Models
11.3.2 Access Controls
11.4 Behavior Definitions
11.4.1 Create Behavior Definition
11.4.2 Behavior Pool and Handler Implementation
11.4.3 Consumption via EML
11.4.4 Static Field Control
11.4.5 Numbering
11.4.6 Exclusive Locks
11.4.7 Authorization Checks
11.4.8 Authorization Contexts and Privileged Access
11.4.9 Associations
11.4.10 Actions
11.4.11 Functions
11.4.12 Data Determinations and Validations
11.4.13 Dynamic Feature Control
11.4.14 Mappings
11.4.15 Calculated Fields
11.4.16 Prechecks
11.4.17 HTTP ETags
11.4.18
11.4.19
11.4.20
11.4.21
Draft
Side Effects
Change Documents
Events
11.5 Transactional Projection Object Models
11.5.1
11.5.2
11.5.3
11.5.4
11.5.5
Projection Object Models
Access Control
Denormalized Fields
Localized Elements
Calculated Fields/Virtual Elements
11.6 Define Interface Behavior Definition
11.6.1
11.6.2
11.6.3
11.6.4
11.6.5
11.6.6
11.6.7
Create Interface Behavior Definition
Static Feature Control
Operations
Draft, ETag, and Side Effects
Events
Consumption via EML
Release for Consumption
11.7 Define Projection Behavior Definition
11.7.1
11.7.2
11.7.3
11.7.4
11.7.5
11.7.6
11.7.7
Create Projection Behavior Definition
Actions and Functions
Prechecks
Augmentation
Side Effects
Events
Consumption via EML
11.8 Runtime Orchestration
11.8.1 Interaction Phase Operation Flow
11.8.2 Save Phase Operation Flow
11.8.3 Runtime Component Overview
11.8.4 Consumption via OData
11.9 SAP Fiori and OData Consumption
11.9.1 OData Service for Web API Consumption
11.9.2 OData Service for UI Consumption
11.10 SAP Event Mesh and Local Event
Handlers
11.10.1 Local Event Handler
11.10.2 SAP Event Mesh
11.11 Summary
12 Hierarchies in CDS
12.1 Hierarchy Categories and Basics
12.2 Annotation-Based Parent-Child
Hierarchies
12.2.1 Example of a Parent-Child Hierarchy
12.2.2 Determination of a Hierarchy
12.2.3 Test a Hierarchy
12.3 CDS Hierarchies
12.3.1
12.3.2
12.3.3
12.3.4
12.3.5
Data for an Example of a Reporting Line Hierarchy
Define the CDS Hierarchy
Hierarchy Attributes
Visualization of a Hierarchy
Hierarchy with an Orphaned Node
12.3.6 Hierarchy with Multiple Parent Nodes
12.3.7 Hierarchy with Cycles
12.3.8 Further Options for Defining Hierarchies
12.3.9 CDS Hierarchies in ABAP SQL
12.3.10 OData Service for CDS Hierarchies
12.4 Summary
13 CDS-Based Search
Functionality
13.1 Modeling Value Helps
13.1.1
13.1.2
13.1.3
13.1.4
13.1.5
Modeling Elementary Value Helps
Integrating Value Help CDS Views
Collective Value Helps
Exposing Value Helps in OData Services
Using Value Helps
13.2 Free-Text Search Functionality in OData
Services
13.3 Enterprise Search Functionality
13.3.1 Define Enterprise Search Models
13.3.2 Adapt Enterprise Search Models from SAP
13.4 Summary
14 Lifecycle and Stability
14.1 Stability Contracts
14.2 Lifecycle of Development Objects
14.3 Deprecation of Development Objects
14.4 Use of CDS Models and Supported
Capabilities
14.5 Summary
15 Extensions of CDS Views and
Other Entities
15.1 Solution Variants and ABAP Language
Versions
15.2 Stable CDS Extensions
15.2.1
15.2.2
15.2.3
15.2.4
Stable Extensions of CDS Views
Example: Stable Extension Points
Example: Extension with Custom Fields
CDS Extensions in Product Variants
15.3 Extensions of Transactional Models
15.3.1 Add Fields to an Entity
15.3.2 Add Application Logic
15.3.3 Extend Action and Function Parameters and
Results
15.3.4 Extend Behavior
15.3.5 Add Composition Child Entity
15.4 Summary
16 Automated Testing
16.1 Test Logic of Data Selections
16.1.1
16.1.2
16.1.3
16.1.4
Views
16.1.5
Fundamentals of the Test Double Framework
Test Sample Overview
Test Implementations of CDS Views
Test ABAP Logic with SQL Accesses to CDS
Test Code Generation Functions
16.2 Test Logic of Transactional Applications
16.2.1
16.2.2
16.2.3
16.2.4
Test Behavior Handler
Test Events and Event Payloads
Test Event Handler
Tests via EML Interface
16.3 Summary
17 Troubleshooting
17.1 Performance Aspects
17.1.1
17.1.2
17.1.3
17.1.4
17.1.5
Static View Complexity
Calculated Fields
CDS Models in ABAP Code
Performance Tests
Analysis Tools
17.2 Pitfalls
17.2.1 Null Values
17.2.2 Data Types
17.2.3 Decimal Shift Logic for Amounts
17.3 Troubleshoot Implementations of CDS
Models
17.3.1 Syntax Checks
17.3.2 Consistency Checks of Frameworks
17.4 Troubleshoot Activation Issues
17.4.1 Online Activation
17.4.2 Mass Checks and Repairs
17.5 Examine ABAP RESTful Application
Programming Model Applications
17.6 Summary
A CDS Annotation Reference
B Migration to the ABAP
RESTful Application Programming
Model
C The Authors
Index
Service Pages
Legal Notes
Preface
ABAP's core data services (CDS) technology forms the basis of the
ABAP platform’s modern programming model and is therefore an
important part of SAP’s current technology strategy. Among other
things, as an extension of SQL, this technology allows users to
leverage the features of the SAP HANA database. ABAP CDS and
its annotations can be used to enhance the model with information
that can be optimally integrated into analytical applications and web
interfaces for cloud applications or user interfaces (UIs) via OData as
a model-based protocol. CDS also serves as a foundation for the
new ABAP RESTful application programming model. Therefore,
CDS and the programming model based on it form the basis of SAP
Fiori applications, which embody the new standards for the modern
user experience (UX) of SAP applications.
SAP S/4HANA, the next generation business suite, is also based on
CDS in the form of the virtual data model (VDM). In this book, we
describe the use of CDS from the perspective of SAP S/4HANA and
its overall architecture. Therefore, we not only describe the technical
aspects of CDS but also the motivation behind the applied
approaches. We use examples from application development and
supplement them with recommendations for the successful
implementation of your own projects, which go far beyond discussing
purely functional aspects.
We cover the most important aspects and functions of ABAP CDS,
which are used in concrete use cases in the SAP S/4HANA
environment (up to date for release SAP S/4HANA 2023). For a
technical description of all CDS functions, you can refer to the SAP
standard documentation. This book also provides the relevant basis
(in the respective version of the ABAP platform) for the applicable
syntax and scope of functions supported.
The CDS-based programming model is relatively new and is
therefore still subject to changes and significant further
enhancements and features. Especially in the field of transactional
applications and the ABAP RESTful application programming model,
the development of the programming model is still evolving.
Particularly in this area, we expect the implementation to become
more efficient and the range of features and functions to be
extended significantly in the future. You should always consider this
evolution when implementing your own projects.
With this book, we aim to give you an overview of the CDS-based
programming model and possible use cases. The knowledge we
provide will enable you to define CDS models based on the basic
rules and recommendations that the SAP standard development
follows, to use them in a reasonable way in your applications, and to
enhance the SAP S/4HANA standard applications. By the time
you’ve finished the book, you should have developed comprehensive
knowledge of the underlying concepts, learned how SAP uses these
concepts in SAP S/4HANA, and gained a practical understanding of
how these concepts can be applied when building your own
applications.
The book is primarily aimed at ABAP developers who want to
develop or enhance ABAP-based applications for use in OData
application programming interfaces (APIs), in analytics or in SAP
Fiori UIs. Basic knowledge of ABAP development and SQL is
required. However, if you’re a newcomer to the ABAP and SQL
world, the book will help you create your first applications and
services with the help of CDS models. You can very quickly develop
appealing analytical applications and interfaces, as well as purely
reading applications, without writing ABAP code yourself.
Still, to enter the transactional world with the ABAP RESTful
application programming model, using ABAP as a programming
language for business logic is a prerequisite; in addition, a lot of
model-driven infrastructure support is available. We’ll only deal with
these topics on a very basic level in the context of this book. We also
won’t go into detailed discussions regarding the SAP Fiori UI and will
only use simple examples to show how you can easily create an
OData service and an SAP Fiori UI based on your CDS models.
However, you can also deepen your knowledge by studying the
relevant literature or SAP documentation in parallel.
In the third edition of this book, the newest developments and
features of the ABAP platform are reflected and included. With this,
the structuring of the book was also reworked. The chapter on
transactional applications (Chapter 11) has been enhanced
significantly to cover the newest features of the ABAP RESTful
application programming model (e.g., business events) and much
more. We have provided a completely new chapter for the software
lifecycle and stability of CDS and ABAP RESTful application
programming model artifacts; based on this, we’ve reworked the
complete chapter on extensions of applications. The other chapters
have all been updated accordingly, especially the chapter for test
automation and troubleshooting, which has been enhanced to also
cover transactional applications. Further, all CDS-based examples
are now based on CDS view entities (CDS V2), the new generation
of CDS views discussed and explained in the previous editions.
The descriptions and examples refer to the current state of the art
(up to date for release SAP S/4HANA 2023, but also the related SAP
S/4HANA Cloud, public edition). Most of the concepts and basics
also work in general for ABAP cloud-based applications independent
of SAP S/4HANA. We’ve created them to the best of our knowledge
but can’t completely exclude errors. If in doubt, consult the official
SAP documentation.
We use ABAP in Eclipse (ABAP Development Tools [ADT]) as our
development environment. English is the default development
language for development objects or comments in source code. All
texts relevant for the end user can be translated, of course, and thus
any application can also be localized.
Structure of the Book
This section provides a rough overview of the chapter structure and
the content of the individual chapters. The first four chapters should
be read sequentially as a foundation because all other concepts and
chapters are based on them. You can then jump back and forth
between the individual chapters according to your needs and
interests.
Chapter 1 shows you how to define your first simple CDS views
quickly. In addition to the essential parts of a CDS view, we’ll also
briefly introduce the most important development tools.
Chapter 2 provides the fundamentals and technical basics of CDS
data models. For this purpose, we present the relevant CDS artifacts
from the developer’s point of view, including their components, using
modeling examples.
In Chapter 3, we explain the basics, definition, and usage of
associations in CDS models via ABAP implementation and special
association types, such as compositions.
In Chapter 4, we explain the basics of CDS annotations and their
propagation logic using examples. CDS annotations enrich the data
models with semantic information. This information is interpreted by
CDS consumers and controls, and then influence, for example, the
presentation on the UI or the aggregation behavior of an analytical
application.
In Chapter 5, we deal with the authorizations for accessing the data
exposed by the CDS models. Access controls using the CDS data
control language (DCL) enable you to restrict the selection results of
CDS views according to the user’s authorizations via classic ABAP
authorization objects. We explain the basics of CDS access controls
and demonstrate their concrete use.
In Chapter 6, you get an insight into the modeling and definition of
business services based on a special view type, projection views,
service definitions, and service bindings focusing on OData
protocol–based bindings. Business services allow you to expose
your CDS models to external consumers.
In Chapter 7, we examine CDS table functions, which enable you to
integrate native SAP HANA functions into the CDS view stack using
SQLScript. We illustrate the modeling of CDS table functions and
highlight their special features.
Chapter 8 provides an overview of the application architecture in
SAP S/4HANA and the positioning of CDS within this architecture.
Using examples, we show how important aspects of application data
are modeled with CDS, such as field labels, field semantics, foreign
key relationships, language-dependent texts, entity compositions,
and time-dependent data.
In Chapter 9, we explain the virtual data model (VDM) of SAP
S/4HANA, which is formed by selected CDS views that meet
centrally defined quality and modeling aspects. The modeling rules
used are presented schematically via examples. The explanations
are intended to help you understand the CDS models delivered by
SAP and enable you to use them purposefully in your own
developments.
In Chapter 10, we focus on analytical applications. We briefly
discuss the SAP S/4HANA architecture for embedded analytics and
explain how CDS views can be used for this. The analytical model
used is based on a consistent network of interlinked analytical CDS
views and exposed via analytical queries. We use examples to show
you the relationships between these CDS views and their special
features and analytical calculations.
In Chapter 11, we then deal with transactional applications. In
addition to modeling and executing read accesses, CDS also is the
basic data model for enabling transactional processing. The
integration of a transactional runtime based on the ABAP RESTful
application programming model and the special aspects of
transactional applications, such as locking, authorizations, feature
control, business logic (e.g., actions, determinations, and
validations), and business events are explained using appropriate
examples. In addition, the draft concept is introduced and explained
in detail.
In Chapter 12, we introduce two options for the flexible modeling in
CDS of hierarchical structured data with concrete examples. The
hierarchical models are interpreted directly by the underlying
infrastructure (e.g., in analytical applications) or can be used as
powerful functions for navigation or aggregation of these hierarchies
in your ABAP implementations.
Chapter 13 shows you that CDS can also be used as a basis for
modeling value helps and free-text searches. You’ll learn how the
value helps and search functions can be integrated and used in
certain use cases, for example, for OData exposure.
Chapter 14 addresses the question of lifecycle and stability of CDS
and ABAP RESTful application programming model artifacts as well
as if and how these can be changed within an upgrade or hot fix.
Stability contracts provide security for developers and consumers of
these artifacts. A defined lifecycle controls the release of these
artifacts as well as their deprecation and replacing successors.
Chapter 15 provides an overview of the possibilities for modificationfree enhancements by customers and partners. In SAP S/4HANA
Cloud, public edition, special tailored apps support an experienced
user with special authorizations—the key user—in performing
necessary enhancements. These enhancements must be kept stable
from a lifecycle perspective to ensure smooth software. This requires
special preparations during application development and adherence
to stability contracts. Further developer extensibility is explained by
using CDS view extensions.
Chapter 16 introduces the creation of automated tests for your CDS
models as well as the transactional runtime and its implementations.
The test double framework presented here allows you to exchange
the data sources of CDS views as well as certain parts of the
behavior implementation and thus to decouple the test execution
from the actual persistent data or productive application logic. We
explain test automation with the test double frameworks using
examples.
In Chapter 17, we recommend practical ways for troubleshooting to
find and correct errors and problems in the context of CDS and the
ABAP RESTful application programming model. We describe the
tools relevant for troubleshooting and explain how to use them. In
addition, we illustrate some pitfalls that you may come across when
developing your CDS models.
An overview of the CDS annotations presented in this book is
included in Appendix A as a quick reference. For an overview of all
available CDS annotations, refer to the SAP documentation.
In Appendix B, you can find an overview of how to migrate an
application based on the ABAP programming model for SAP Fiori
(that was the basic for transactional applications in the first edition of
this book) to the ABAP RESTful application programming model.
In addition to screenshots and code examples, this book also
contains several text boxes to supplement the topics:
[+] Tip
Boxes marked with this icon provide recommendations or offer tips
from professional experience.
[»] Note
This icon indicates that additional information is provided.
[ ! ] Warning
With this icon, we have marked warnings and common pitfalls.
Acknowledgments
We would like to thank our colleagues Christoph Glania, Irena
Kofman, Roland Lucius, Horst Schnörer, Stefan Unnebrink, and Felix
Wente for taking the time to discuss and review the contents of the
book. Your comments and suggestions have contributed significantly
to the success of the book. In particular, we would like to thank our
families and friends, whose support has given us the necessary
freedom to create the book.
Now, we wish you much fun reading and much success
implementing your development projects with ABAP CDS and the
ABAP RESTful application programming model.
Renzo Colle, Ralf Dentzer, and Jan Hrastnik
1
Modeling Your First CDS Views
This chapter provides you with a quick introduction to data modeling
with core data services (CDS). In particular, you get an overview of
the processes and tools that are relevant to a CDS developer.
Using CDS models, you can capture the data retrieval logic of your
application in such a way that it can be executed directly in the database
system. This chapter explains how to develop and analyze such CDS
models.
Currently, CDS views represent the most important design-time artifacts
within the range of supported CDS model types. They allow you to flexibly
transform the fixed design of database tables into suitable data models, on
top of which you can then efficiently build your applications.
You define your CDS views in syntax similar to the structured query
language (SQL) that you use for selecting data from database tables via the
ABAP SQL interface (ABAP SQL). From this CDS view model, a
corresponding database view gets generated on the SAP HANA database
by the ABAP infrastructure. This generated database view is executed
when selecting data from the CDS view in ABAP.
When implementing CDS models, you may leverage a plurality of
specialized development tools that are integrated into the ABAP in Eclipse
environment—the ABAP Development Tools (ADT). In this chapter, you’ll
learn about the most important development and analysis tools via a
sample reference application.
Note that this chapter doesn’t focus on the details of the CDS
implementation. You’ll learn more about the detailed aspects of the CDS
models in subsequent chapters. Instead, we focus on presenting the
processes and procedures as well as the available tools, which you’ll apply
during your development activities.
In Section 1.1, we’re looking at the design of the data model of your
application. Based on the model of the database tables, we then run
through the individual, practically executable steps for creating and
changing CDS views in Section 1.2. In this context, you’ll also learn how to
use CDS views as basic building blocks for defining other CDS views as
well as learn how to use CDS views as data sources in your ABAP logic.
That is, you’ll learn how to select data from the modeled CDS views by
leveraging the ABAP SQL syntax.
As part of the maintenance processes presented, we provide additional
information about functions for examining CDS models to help you analyze
them in your future projects.
1.1
Define the Data Model of the Application
The most important step in the development of CDS models is defining the
fundamental data model of the application. In this design phase, the main
aim is to identify and name the different data sources, that is, the
corresponding fundamental entities that your application will be based on.
Furthermore, the identified entities should also be correlated to one another
based on their semantic relationships.
[+] Elaborate on the Fundamental Design
Before you start implementing your CDS models, you should take time to
work out the fundamental data model of your application carefully and to
clarify all open questions. Your evaluations should consider all levels,
beginning with the persistency layer up to the end users’ application user
interface (UI). With a little exercise, the subsequent transfer of the
finalized data model to a CDS-based implementation will become an
easy task.
If, however, you immediately start with the implementation of the actual
functions, changes to the already implemented CDS models will very
likely be required in the course of your development activities. Such
adjustments of the already developed CDS models may become very
time-consuming due to limited refactoring support.
For demonstration purposes, we use a simplified model of a sales order as
our sample reference data model, which is referred to in many discussions
in subsequent chapters. Figure 1.1 gives you an overview of its most
important aspects. You’ll create parts of the reference data model in the
course of this chapter. You can find the complete implementation of the
database tables and CDS views in the code snippets, which you can
download from the book’s website at www.sap-press.com/5642.
The main object—sales order—represents a hierarchically structured
document that is composed of the following entities: sales order header,
sales order item, and sales order schedule line. The header entity of the
sales order is also related to the sales organization and the customer. The
entity sales order item is related to the object product, which consists of the
corresponding header entity (product header) and its related entity product
text.
Figure 1.1
Reference Data Model of the Sales Order
The cardinalities, which are maintained at the illustrated relationships,
describe the number of related data records respective to the data records
of the source entities. On this instance level, a single sales order document
consists of one header to which multiple items can be assigned (maximum
cardinality: *). Each individual data record of an item can be related to
several records of the schedule line (maximum cardinality: *).
For each sales order header data record, a maximum of one sales
organization and one customer data record can be assigned (maximum
cardinality: 1). A sales order item data record can have a maximum of one
product assigned, that is, one product header (maximum cardinality: 1). For
each product header data record, a large number of multilingual texts can
be stored as data records of the entity product text (maximum cardinality:
*).
The minimum cardinalities, which are specified with the value 0 in
Figure 1.1, reflect the technical consistency constraints of the reference
application.
[»] Objects and Header Entities
In this chapter, we’ll no longer distinguish between the overall object
sales order and the entity sales order header; instead, we’ll simply refer
to the sales order in both cases. In the majority of cases, sales order
refers to the entity sales order header. The same applies for the entity
product header, which will represent the object product. In this chapter,
it’s also only referred to as product.
Table 1.1 shows a summary of the objects and entities of the sample
reference application and provides an overview of how the entities of the
reference data model are mapped onto the underlying database tables.
Furthermore, the table lists the basic CDS views that represent the data
model of the application in CDS. Note that these are sample tables and
CDS views that you must create locally in your system if you want to use
them.
Object
Entity
Database Table
Basic CDS View
Sales order
Sales order
(header)
ZSALESORDER
ZI_SalesOrder
Sales order
item
ZSALESORDERITEM
ZI_SalesOrderItem
Sales order
schedule
line
ZSALESORDERSLINE ZI_SalesOrderScheduleLine
ZSALESORG
Sales
Sales
organization organization
ZI_SalesOrganization
Customer
Customer
ZCUSTOMER
ZI_Customer
Product
Product
ZPRODUCT
ZI_Product
Product text ZPRODUCTTEXT
Table 1.1
ZI_ProductText
Reference Data Model with Associated Database Tables and CDS Views
In general, we use names for all CDS models and their components with
spellings and prefixes based on the specifications of the virtual data model
(VDM), which most of the CDS models delivered by SAP adhere to. SAP’s
VDM will be explained in more detail in Chapter 9.
The CDS models of the reference data model are created in the local
namespace. For the sake of simplicity, they are prefixed with the letter Z.
[ ! ] Use Your Customer Namespace
When you create CDS models, you must always define them in your own
namespace to avoid naming conflicts with the models delivered by SAP.
Merely adding the prefix Z for local developments might not be sufficient
for avoiding name clashes.
1.2 Implement the Data Model of the
Application
We now want to implement an extract from the reference data model.
First, we create the database tables for sales order item
ZSALESORDERITEM and for product ZPRODUCT in Section 1.2.1. Based on
database table ZPRODUCT, in Section 1.2.2, we then define CDS view
ZI_Product, which will represent the product. In Section 1.2.3, we’ll
show you how you can maintain this CDS view and how the
maintenance steps get reflected in its activation states. We conclude
this chapter by defining a hierarchy of CDS view models in
Section 1.2.4 in which we first create CDS view ZI_SalesOrderItem as
a representative of the sales order item similar to CDS view
ZI_Product. We capture the semantic relationship of these two CDS
views by means of CDS association _Product.
[»] CDS Associations
Associations are an important and integral part of the CDS syntax
(see Chapter 3). Using CDS associations, you document the
relationship between the CDS models, essentially creating an entity
relationship model (ERM). An ERM describes a semantic data
model based on entities, their relationships, and their properties. It’s
the basis of many development projects.
Apart from enriching the CDS models with semantic information,
CDS associations can also be used for implementing CDS models
and parts thereof, as well as for data accesses at runtime. We’ll
show you this later in Section 1.2.4 and in Chapter 3.
Then, we create another CDS view, ZC_SalesOrderItemWithProduct,
which represents an example of a use case-specific consumption view
based on the fundamental data model of the application. It selects data
from the basic CDS view of the sales order item and enriches this data
with the data fetched from the basic CDS view of the product by using
a join. Figure 1.2 illustrates these correlations.
Figure 1.2
Excerpt from the Implementation of the Reference Application Data Model
Finally, we show you how you can analyze CDS models and present
the corresponding tools provided by the CDS infrastructure.
We’ll take a closer look at each step in the following sections.
1.2.1
Create Database Tables
Often, the database tables of an application already exist. For
example, the productive CDS views of the sales order, which are
delivered with SAP S/4HANA, use database table VBAK, which was
already defined in the past. Therefore, we’ll only deal with the
definition of database tables in this section.
You can create and access database tables in the ABAP Workbench
via Transaction SE11. Alternatively, you can use the corresponding
functionality of the ADT environment, which provides a source code
editor for database tables within the ABAP perspective. In this section,
we’ll use ADT to create two database tables.
To create the database table, choose File • New • Other… from the
main menu. Alternatively, you can call the same function from the
context menu of the project tree of the ABAP development system in
the Project Explorer view by choosing New • Other…. In the
selection screen that appears, choose ABAP • Dictionary • Database
Table. You can restrict the object selection by entering the filter
“Database Table”. The result of this filtering is shown in Figure 1.3. You
confirm your selection by choosing Next >.
Figure 1.3
Selecting the Creation of a Database Table
The selected function opens a dialog box for creating database tables.
The dialog box guides you through two steps:
1. A modal window appears, in which you enter the ABAP project of
the development system, the package, the name of the database
table, and its description. The example shown in Figure 1.4 uses
local package TEST_CDS_REFERENCE_APPLICATION for creating
database table ZPRODUCT with description Product. Note that if you
want to follow this example, you can also use local package $TMP
instead of the aforementioned package. Choose Next > to confirm
your entries.
Figure 1.4
First Dialog Step When Creating a Database Table
2. Select a transport request that records your changes. Because
you’re only defining local objects for the reference model, you can
close the dialog box by clicking Finish.
This opens the source code-based editor for database tables. Enter
the same data as depicted in Figure 1.5; that is, in addition to key field
client, you define another key field product and three further fields—
product_type, authorization_group, and creation_date_time—and
then specify their types with data elements matnr, mtart, begru, and
timestampl, respectively.
Figure 1.5
Table Definition ZPRODUCT before Saving
You can now activate your table definition ZPRODUCT by choosing Edit •
Activate from the main menu, by using the key combination (Ctrl)+
(F3), or by clicking the corresponding icon
below the main menu
bar. After the activation is completed, you’ve successfully created a
transparent database table for the application data of the product with
maintenance options and without enhancement options.
Figure 1.6 shows the corresponding presentation of table ZPRODUCT in
the ABAP Workbench.
Figure 1.6
Table ZPRODUCT in the ABAP Workbench
Now, create the table of entity sales order item ZSALESORDERITEM in the
same way as you’ve just created the table of entity product ZPRODUCT.
Transfer the data from Listing 1.1 for use in defining this database
table.
@EndUserText.label : 'Sales Order Item'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #ALLOWED
define table zsalesorderitem {
key client
: abap.clnt not null;
key salesorder
: vbeln not null;
key salesorderitem : posnr not null;
product
: matnr;
@Semantics.quantity.unitOfMeasure :
'zsalesorderitem.orderquantityunit'
orderquantity
: kwmeng;
orderquantityunit
: vrkme;
@Semantics.amount.currencyCode :
'zsalesorderitem.transactioncurrency'
netamount
: netwr_ap;
transactioncurrency : waerk;
creationdate
: erdat;
@AbapCatalog.anonymizedWhenDelivered : false
createdbyuser
: ernam;
creationdatetime
: creation_date_time;
@AbapCatalog.anonymizedWhenDelivered : false
lastchangedbyuser
: aename;
lastchangedatetime
: last_changed_date_time;
}
Listing 1.1
Definition of Database Table ZSALESORDERITEM
[+] Simplify the Design of Tables
When you define the design of your application tables, ensure that
the database tables to be created correspond to the entities of your
data model. In particular, don’t distribute data of a single entity
across multiple tables. Otherwise, the consumers of your data model
need to apply additional joins when selecting data from the entity.
In addition, align the names of the database table fields with the
names used externally (e.g., in OData services) to avoid
unnecessary as well as potentially incorrect mappings.
1.2.2
Create a CDS View
CDS models are developed in the ADT environment.
[»] ABAP Workbench Restrictions
The ABAP Workbench provides only a rudimentary visualization
option for CDS models without the essential features required for
developing CDS models. Therefore, you should always work with
the ADT environment when dealing with CDS models.
When creating a CDS model, you can proceed in the same way as
when creating database tables. Choose File • New • Other… from the
main menu. In the dialog box that appears (see Figure 1.7), choose
ABAP • Core Data Services • Data Definition. You can restrict the
object selection by entering the filter “Data Definition”. Confirm your
selection by choosing Next >.
Figure 1.7
Selecting the Creation of a CDS Data Model
The chosen function launches a specific dialog box for creating CDS
data models, which guides you through several steps. First, a modal
window appears in which you can enter the ABAP project of the
development system (Project), the package (Package), the name of
the CDS model (Name), its description (Description), and the optional
name of an object, which is referenced by the CDS model
(Referenced Object). Enter the package
“TEST_CDS_REFERENCE_APPLICATION”, the name “ZI_Product”,
the description “Product”, and the referenced object “ZProduct”, as
shown in Figure 1.8.
Figure 1.8
First Dialog Step When Creating a CDS Data Model
The entered name will become the name of your data definition
language source (DDLS) transport object, as well as the proposed
name for your CDS model.
[ ! ] DDLS Transport Object and CDS Model
The DDLS transport object and the CDS model are two different
design-time artifacts. The transport object (the DDLS) contains the
definition of a CDS model. A DDLS transport object could
theoretically contain several CDS model definitions. However, this
isn’t supported by the ABAP infrastructure.
For some types of CDS models, the names of these two design-time
artifacts—the DDLS transport object and the CDS model—can differ
from each other. However, we recommend that you always define
these two names in such a way that the DDLS name corresponds to
the name of the CDS model in uppercase letters. Otherwise, you
must map both names for later analyses.
Choose Next > to confirm your entries.
In the next step, you can select the transport request, which records
your changes. In our example, you can skip this step by choosing Next
>.
In the last step, you can select a predefined template that will support
you when creating a new CDS model (see Figure 1.9).
Figure 1.9
Selecting a Template in the Creation Dialog
[»] Use of Templates for Creating CDS Models
The standard templates for creating CDS models cover prominent
use cases and help you capture the required information efficiently.
The use of these templates is optional. If you don’t want to apply a
template, deselect the Use the selected template checkbox.
Because you want to create a CDS view entity, choose the Define
View Entity template. Confirm your selection by clicking Finish.
This opens the source text-based CDS editor shown in Figure 1.10.
The CDS editor is the main development tool for CDS models.
Figure 1.10
CDS Editor after Closing the Creation Dialog Box
Because you’ve previously selected the Define View Entity template,
the basic structure of a CDS view entity model is already defined. By
default, the name you entered for the transport object—ZI_Product—is
reused as the name of the CDS model. You can find it behind key
words define view entity. It’s written in accordance with the spelling
that you used in the first step of the creation wizard. Therefore, we
entered the DDLS name in camel case notation, even though the
name of the transport object itself, in contrast to the name of the CDS
model, only supports uppercase letters.
[»] CDS View Types
In general, the CDS syntax allows you to define different types of
CDS views. You can get an overview of the supported CDS view
types in Chapter 2, Section 2.1. In most cases, we use the CDS
view entities as you see in this example.
The descriptive text Product of the transport object is also transferred
to the following annotation of the CDS view:
@EndUserText.label:'Product'. You’ll learn more about annotations in
Chapter 4.
Database table ZProduct, which was entered as the referenced object
in the creation wizard, acts as the data source for CDS view
ZI_Product. It was added automatically after syntax element select
from.
The field list of your CDS view is defined within curly brackets {}. All
fields of database table ZProduct except for the client field were
transferred there. This is due to the specific client handling in CDS
view entities, which we’ll explain in Chapter 2, Section 2.9. In addition,
the product field was prefixed with syntax element key, which makes it
a key field of the CDS view. We’ll discuss key fields further in
Chapter 2, Section 2.3.
Note that the fields projected from the database table were renamed
by applying the alias function as. In this context, the field names of the
tables were transferred in lowercase letters into the CDS view. In
contrast to the classic ABAP Data Dictionary, CDS models support
defining names in camel case notation. This is supported by the CDS
editor, which converted the names of the table fields by capitalizing the
first letters of their constituting terms (the boundaries of terms are
identified by underscores) and concatenating them, omitting
separating underscores. For example, table field product_type was
renamed to CDS field ProductType, as shown earlier in Figure 1.10.
[»] CDS Names
The ABAP logic isn’t case-sensitive. Consequently, CDS names that
differ only due to a different usage of uppercase and lowercase
notation for the same letters are considered identical in ABAP.
However, if, for example, the CDS models are exposed in OData
services, which are implemented by the Service Adaptation
Definition Language (SADL), the names of derived artifacts keep
their original spelling. We therefore recommend that you use camel
case notation, even if it doesn’t have any effect within the ABAP
logic.
You can now activate the CDS model either by choosing Edit •
Activate from the main menu or by using the key combination (Ctrl)+
(F3).
Congratulations! You’ve successfully created and activated your first
CDS model. In principle, you can now start using it.
With the successful activation, you created and activated two ABAP
Data Dictionary artifacts. Table 1.2 provides an overview of these
artifacts.
ABAP
Technical Name
Data
Object
Dictionary Type
Artifact
Description
DDLS
object
DDLS
ZI_PRODUCT
Transportable object
CDS view
STOB
ZI_Product
Carrier of the modeled
information and data source for
selections in ABAP and SAP
HANA
Table 1.2
ABAP Data Dictionary Artifacts of the CDS Model ZI_Product
DDLS object ZI_PRODUCT is used as a transport container for the CDS
model defined in its source code. CDS view ZI_Product contains the
modeled SQL selection logic and further metadata. You’ll find out how
this metadata is evaluated and used by the runtime environment and
the various frameworks in the subsequent chapters of this book.
The two ABAP Data Dictionary artifacts are assigned to the same
package. In the Project Explorer view of the ADT environment, you
can find these in their embedding package (in the example under
TEST_CDS_REFERENCE_APPLICATION).
As shown in Figure 1.11, you can access the DDLS object or CDS
view from the embedding package by opening the Core Data
Services • Data Definitions folders.
Figure 1.11
CDS Model in the Project Explorer View
[+] Use the Synchronization Option between the CDS Editor
and the Project Explorer
You can facilitate the navigation between the Eclipse editor views of
the CDS models and the Project Explorer view by enabling the
Link with Editor function via the
icon, as shown in Figure 1.11.
If you use the newly created CDS view in a data selection, the data
access within the ABAP SQL runtime is delegated from ABAP to
generated database view ZI_PRODUCT. This means that the database
view determines the actual execution of the selection request in the
database.
You can use the Show SQL CREATE Statement function in the
context menu of the CDS editor to display the statement that results in
the creation of the database view in the database (see Listing 1.2).
You can request the context menu from any position within the CDS
view editor.
CREATE OR REPLACE VIEW "ZI_PRODUCT" AS SELECT
"ZPRODUCT"."CLIENT" AS "MANDT",
"ZPRODUCT"."PRODUCT" AS "PRODUCT",
"ZPRODUCT"."PRODUCT_TYPE" AS "PRODUCTTYPE",
"ZPRODUCT"."AUTHORIZATION_GROUP" AS "AUTHORIZATIONGROUP",
"ZPRODUCT"."CREATION_DATE_TIME" AS "CREATIONDATETIME"
FROM "ZPRODUCT" "ZPRODUCT"
WHERE "ZPRODUCT"."CLIENT" = SESSION_CONTEXT(
'CDS_CLIENT'
)
Listing 1.2
1.2.3
Create Statement for Database View ZI_PRODUCT
Edit a CDS View
In the next step, we want to change CDS view ZI_Product that we
previously created and activated. Along the way, you’ll learn about
further functions of the ADT environment.
First, close the ADT view for DDLS object ZI_PRODUCT by selecting
Close in the upper area of the respective editor. Alternatively, you
can choose Close All in the context menu for closing all the opened
Eclipse views at once.
Now, open the CDS editor for DDLS object ZI_PRODUCT by choosing
Navigate • Open ABAP Development Object from the main menu (or
press (Ctrl)+ (Shift)+(A) or click the
icon in the function bar).
In the dialog box that opens, you first select the ABAP project of the
ADT environment (see Figure 1.12) in which you want to find the DDLS
object. You can also specify appropriate filter criteria for restricting the
results list. To specifically search for DDLS object ZI_PRODUCT, enter
“type:ddls ZI_PRODUCT<” in the input field of the filter.
Figure 1.12
Selecting the DDLS Object ZI_PRODUCT
The first part of your input, “type:ddls”, restricts the relevant search
scope to DDLS objects. The subsequent string “ZI_PRODUCT” reduces
the hit list to all DDLS objects whose names begin with this prefix. The
suffix “<” ensures that the search is performed specifically for the
name entered. Consequently, the selection result contains the single
entry ZI_PRODUCT, as shown in Figure 1.12. You can load the
selected entry into the CDS editor by choosing OK.
[+] Open CDS Models from Project Explorer
Instead of transferring a CDS model to the editor via the load dialog,
you can also use the Project Explorer view and double-click on the
data model to be displayed.
In the CDS editor, remove the annotations starting with @ObjectModel…
from CDS view ZI_Product. Figure 1.13 shows the resulting CDS view
model. Note that next to the DDLS name at the top-left side of the
screen, a small lock symbol is displayed
along with an asterisk (*).
The lock symbol indicates that the DDLS object is locked due to your
changes, preventing any concurrent changes thereof; the asterisk
signals that your changes haven’t yet been saved. Note that you can’t
perform data selections from such unsaved and inactive versions of a
CDS view.
Figure 1.13 CDS View ZI_Product Immediately after Removing Annotations
@ObjectModel…
Save your changes by choosing File • Save (or pressing (Ctrl)+(S)).
After the changes are saved successfully, the asterisk disappears.
Instead, a small gray diamond appears below the lock symbol , as
shown in Figure 1.14.
Figure 1.14
CDS View ZI_Product after Saving the Model Changes
The current status of an object is also displayed in the Properties view
(see Figure 1.15). This ADT view displays various pieces of
administrative information about a CDS model. Until now, the
annotation changes haven’t yet been transferred to the active CDS
view model. As a result, the Version of the modified CDS view is
displayed as Inactive.
Figure 1.15
View Properties after Saving the Model Changes
You can display the active version of the CDS model by calling the
context menu of the CDS editor. Choose the Toggle Active and
Inactive Version function, and the content of the editor window of DDLS
object ZI_PRODUCT changes according to Figure 1.16. At the same time,
the information displayed in the Version field of the Properties view
changes too. The corresponding text, which is also displayed in
Figure 1.16, shows that there is an inactive version in addition to the
currently visible active version of the CDS model.
You can now return to the inactive version by clicking on Toggle
Active and Inactive Version in the context menu of the CDS editor. In
the inactive version, activate your changes by pressing (Ctrl)+(F3).
Figure 1.16
View Properties after Switching to the Active Version of the CDS Model
[+] Switching between Versions
Switching between the active and inactive versions of a CDS model
is particularly useful if you analyze activation problems and want to
trace the changes between the CDS model that has already been
activated and the version that is currently inactive (see Chapter 17,
Section 17.4, for more information on troubleshooting activation
issues).
The CDS editor supports you in the maintenance process of CDS
models by providing an auto-completion functionality. To leverage this
function, first remove the AuthorizationGroup field from CDS view
ZI_Product. Afterward, place your cursor on the corresponding line,
and press (Ctrl)+(Space). The popup that appears allows you to
either transfer individual selected elements or all elements of the data
source to the projection list of your CDS view (see Figure 1.17).
Figure 1.17
Auto-Completion Function of the CDS Editor
In this case, we want to restore the former view definition by selecting
authorization_group and renaming the transferred database table
field to AuthorizationGroup. You can then activate the CDS view.
1.2.4
Create a Hierarchy of CDS Views
In this section, we’ll create the two missing CDS views,
ZI_SalesOrderItem and ZC_SalesOrderItemWithProduct, from
Figure 1.2. We’ll proceed step by step, taking into account the
interdependencies between the CDS view models.
Create CDS View ZI_SalesOrderItem
First, create CDS view ZI_SalesOrderItem. Follow the procedure
described in Section 1.2.2. Copy the CDS view definition from
Listing 1.3 to your DDLS object ZI_SALESORDERITEM.
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Item'
@Metadata.ignorePropagatedAnnotations: true
define view entity ZI_SalesOrderItem
as select from zsalesorderitem
association [0..1] to ZI_Product as _Product
on $projection.Product = _Product.Product
{
key salesorder
as SalesOrder,
key salesorderitem
as SalesOrderItem,
product
as Product,
@Semantics.quantity.unitOfMeasure: 'OrderQuantityUnit'
Orderquantity
as OrderQuantity,
Orderquantityunit
as OrderQuantityUnit,
@Semantics.amount.currencyCode: 'TransactionCurrency'
netamount
as NetAmount,
transactioncurrency as TransactionCurrency,
creationdate
as CreationDate,
_SalesOrder,
_Product
}
Listing 1.3
CDS View ZI_SalesOrderItem
This CDS view definition contains the definition of an association
named _Product. This CDS association documents the relationship
between its source CDS view ZI_SalesOrderItem and its target CDS
view ZI_Product. In accordance with its specified on condition, data
records of the source and the target are linked to each other if their
values for field Product are the same. Depending on whether there is
no or a single matching data record of the product, the target
cardinality of the association varies between 0 and 1, respectively.
If you want to make an association definition usable for the consumers
of your CDS view as well, you must expose this association by
including it in the same way as the fields in the projection list of the
CDS view. For more information on the use of associations, refer to
subsequent chapters. For example, Chapter 3 provides you with
fundamental information about associations, and Chapter 8,
Section 8.4, explains the use of associations when defining foreign key
relationships.
Test CDS View ZI_SalesOrderItem
Before proceeding, you should test activated CDS view
ZI_SalesOrderItem. To do this, you must first store suitable test data in
database tables ZPRODUCT and ZSALESORDERITEM. To follow the
discussed example in practice, create two data records per database
table in accordance with Table 1.3.
Table
Field
SALESORDER SALESORDERITEM PRODUCT
ZSALESORDERITEM
ZPRODUCT
Table 1.3
S1
000010
P1
S1
000020
P2
–
–
P1
–
–
P2
Minimal Test Data
In the CDS editor context menu, choose Open With • Data Preview or
press (F8). Figure 1.18 shows the Data Preview with the result of the
automatically executed selection from CDS view ZI_SalesOrderItem.
You can sort this results list by clicking on the corresponding column
headings. You can also restrict this results list by defining additional
filter conditions (Add filter).
Figure 1.18
Displaying the Selection Result in the Data Preview
Select a data record to which a product is assigned. In the example
shown, this data record is determined by two key fields: SalesOrder
with the value S1 and SalesOrderItem with the value 000020. As
shown in Figure 1.18 and defined in Table 1.3, this data record
contains a reference to the product P2.
Above the results list, the name of the CDS model appears in capital
letters between two arrow symbols
in the gray area. This area
provides you with a breadcrumb-navigation option. If you click on the
right arrow symbol, a selection list appears where you’ll find all the
associations that are exposed by the CDS view (see Figure 1.19). You
can choose one of these associations for navigating to its associated
target records. In our example, click on the _Product association.
Figure 1.19
Selecting Associations for Navigation in the Data Preview
The results list now displays the data record for product P2 (see
Figure 1.20). Above the results list, you see the tracked path from CDS
view ZI_SALESORDERITEM following the association _Product to its
target CDS view ZI_Product. By clicking on the first segment
ZI_SALESORDERITEM of this path, you can return to the original
results list.
Figure 1.20
Displaying the Navigation Target in the Data Preview
However, to see which selection request resulted in the displayed
records of the association target from Figure 1.20, click on the SQL
Console button above the results list. Another ADT view opens (see
Figure 1.21) in which the applied select statement for the result shown
in Figure 1.20 is illustrated.
Figure 1.21
SQL Console with Select Statement
The displayed select statement uses ABAP’s SQL syntax. Starting
from the primary data source of the selection—CDS view
ZI_SalesOrderItem—four fields Product, ProductType,
AuthorizationGroup, and CreationDateTime, are selected by using path
expressions based on association _Product. In these path expressions,
the association is prefixed with a slash (\) and separated by a hyphen
(-) from the field name, for example, \_Product-Product. According to
the on condition of association _Product, only the values of those data
records from target CDS view ZI_Product are returned whose field
Product has the same value as the corresponding field of the selected
data records of source view ZI_SalesOrderItem.
The selection result is also restricted to those records for which field
SalesOrder of data source ZI_SalesOrderItem has the value S1 and for
which field SalesOrderItem of data source ZI_SalesOrderItem has the
value 000020. The additional where condition \Product-Product is not
null ensures that there is at least one corresponding data record of
target view ZI_Product in the selection result for each selected source
data record.
You can change the select statement by commenting out the
restriction to the specific sales order item in line 10. After executing
this adapted data selection in the SQL Console view by clicking on
Run (or by pressing (F8)), the results list displays two entries, as
illustrated in Figure 1.22. In addition to the previously selected product
P2, this list also includes product P1. In the given example, this
second data record originates from the additionally selected record of
the sales order item, which is identified by the key SalesOrder field with
the S1 value and SalesOrderItem field with the 000010 value and whose
field Product has the P1 value (refer to Figure 1.18).
Figure 1.22
SQL Console with Adapted Select Statement
Selections from CDS Models in ABAP
The discussed select statements shown in the SQL Console view
illustrate how you can access data from the CDS data models in your
ABAP code. Let’s look at Listing 1.4, which shows a data selection in
ABAP that corresponds to the logic from Figure 1.22.
DATA: lt_zi_product
TYPE STANDARD TABLE OF zi_product
WITH DEFAULT KEY.
SELECT \_product-product
AS product,
\_product-producttype
AS producttype,
\_product-authorizationgroup AS authorizationgroup,
\_product-creationdatetime
AS creationdatetime
FROM zi_salesorderitem
WHERE zi_salesorderitem~salesorder = 'S1'
AND \_product-product IS NOT NULL
INTO TABLE @lt_zi_product.
Listing 1.4
Data Selection in ABAP
As illustrated in Listing 1.4, you can use CDS models for specifying
types of variables in ABAP. For example, variable lt_zi_product is
defined as an internal table based on a structure type, which is derived
from CDS view ZI_Product. Unlike the select statement in the SQL
Console view, the result of the data selection must be explicitly
returned to such an internal table. To include this internal table in the
select statement in ABAP, it’s prefixed with @, making it a host
variable. Note that when using the new SQL syntax of ABAP, you must
place this prefix in front of all the variables used within your select
statement.
The example also demonstrates that you can use CDS models as
regular data sources of select statements, similar to database tables.
You can define path expressions to leverage the exposed CDS
associations and use these expressions in various places within the
select statement.
Create CDS View ZC_SalesOrderItemWithProduct
Now, continue with the creation of CDS view
ZC_SalesOrderItemWithProduct. This CDS view joins previously created
CDS views ZI_SalesOrderItem and ZI_Product and incorporates major
parts of their field lists, as shown in Listing 1.5.
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Item with Product'
@Metadata.ignorePropagatedAnnotations: true
define view entity ZC_SalesOrderItemWithProduct
as select from ZI_SalesOrderItem as ITEM
left outer to one join ZI_Product as PROD
on PROD.Product = ITEM.Product
{
key ITEM.SalesOrder,
key ITEM.SalesOrderItem,
ITEM.Product,
@Semantics.quantity.unitOfMeasure: 'OrderQuantityUnit'
ITEM.OrderQuantity,
ITEM.OrderQuantityUnit,
PROD.ProductType,
PROD.CreationDateTime
}
Listing 1.5
CDS View ZC_SalesOrderItemWithProduct
With the activation of this CDS view, you’ve built your first CDS view
stack.
Analysis Options
You can explore the structure of this view hierarchy by choosing Open
With • Dependency Analyzer from the context menu of the CDS
editor.
The SQL Dependency Tree tab opens for CDS view
ZC_SalesOrderItemWithProduct (see Figure 1.23). On this tab, the SQL
dependencies of the CDS view to all its underlying data sources,
including the database tables, are shown in a hierarchical
representation.
Figure 1.23
SQL Dependency Tree
On the first level, CDS view ZC_SalesOrderItemWithProduct depends
on two joined data sources (CDS views ZI_SalesOrderItem and
ZI_Product). On the next level, you’ll find the dependencies to
database tables ZSALESORDERITEM and ZPRODUCT.
Besides displaying these dependencies in a table, you can display
them graphically too. Select the SQL Dependency Graph tab below
the tabular display. The system displays a tree-like representation of
the relationships (see Figure 1.24).
Figure 1.24
SQL Dependency Graph
The Complexity Metrics tab shows key figures that describe the static
complexity of the CDS view stack. The metric is calculated from the
entire view stack, which is resolved down to the level of the included
database tables. The occurrences of joins, unions, function calls, and
so on are summed up there. Figure 1.25 presents the analysis result
for CDS view ZC_SalesOrderItemWithProduct. The statistics show that
in the CDS view stack, there are two tables and two view usages. In
addition, it shows that the view hierarchy contains a join.
Figure 1.25
Complexity Metrics
[»] Interpret the Results of the Complexity Metrics Correctly
Note that in the Complexity Metrics tab, the figures for the used
data sources refer to their occurrences in the CDS view stack. For
example, if the same CDS view is joined twice within the view stack,
the number of used views, which is provided by the statistics,
increases by two and not by one.
You can get an overview of the different usages of a given CDS model
in an ABAP system by using the where-used list functionality. Return
to the display of view ZI_Product. Select Get Where-Used List from
the context menu. The result of the where-used list is presented in the
Search view that opens automatically (see Figure 1.26). It includes,
among other things, the usages of CDS view ZI_Product as an
association target of CDS view ZI_SalesOrderItem and as a data
source of CDS view ZC_SalesOrderItemWithProduct. Furthermore, it
contains its usage as a data source for a data selection implemented
in ABAP program ZTEST_CDS_SQL_SELECT.
Figure 1.26
Results of the Where-Used Analysis of CDS View ZI_Product
By double-clicking an entry in the results list, you can navigate to the
corresponding usage location of CDS view ZI_Product.
Let’s now focus again on CDS view ZC_SalesOrderItemWithProduct.
Open it in the CDS editor. In addition, open the Outline view, which
provides a compact view of the CDS model parts without the model’s
annotations. In particular, if you create more comprehensive CDS
models, you can benefit from the grouping and alphabetical sorting
option for gaining a quick overview of the CDS model. In addition, the
Outline view also allows you to navigate to the CDS view details (and
vice versa). To use this navigation feature, simply select the relevant
tree element, for example, the Product field. Once selected, the
corresponding element will be highlighted in the CDS editor too, as
shown in Figure 1.27.
Figure 1.27
Using the Outline View for Navigation Support
Now, open the CDS Navigator view to get an overview of the artifacts
and functions that are closely related to the CDS model. The displayed
artifacts either directly affect the functionality of the CDS model or are
generated from the CDS model. For example, you can get an overview
of all the CDS access controls (see Chapter 5), which protect the CDS
model. Additionally, you can find all the metadata extensions (see
Chapter 4, Section 4.4) and extended views (see Chapter 15,
.Section 15.2.3), which enhance the CDS model. Clicking on a
displayed artifact opens its editor.
In the given example in Figure 1.28, you can see that CDS view
ZC_SalesOrderItemWithProduct doesn’t have any of these artifacts.
Figure 1.28
Using the CDS Navigator View for Identifying and Accessing Related Artifacts
You can also navigate to referenced objects from within the CDS
editor. To do this, place your cursor on the corresponding object name
in the CDS editor. For example, you can place your cursor on the data
source ZI_SalesOrderItem of CDS view ZC_SalesOrderItemWithProduct.
After pressing (F3) or choosing function Navigate To from the context
menu, CDS view ZI_SalesOrderItem is displayed in the CDS editor.
In the context of modeling business objects with their behavior
definitions (see Chapter 11), the Relation Explorer view also might
come in handy for getting an overview of all development objects
engaged in implementing the functionality of a transactional
processing-enabled application. You can launch the Relation Explorer
for an object, for example, by calling function Show In • Relation
Explorer from its context menu. For more information about the
Relation Explorer view, go to http://s-prs.co/v529400.
Let’s now look closer at the implementation details of CDS view
ZC_SalesOrderItemWithProduct.
If you’re not sure which effects a syntax element has, you can launch
its ABAP documentation from the CDS editor by placing your cursor on
the corresponding syntax element and pressing (F1). The ABAP Help
displayed in Figure 1.29 is shown for syntax element key.
Figure 1.29
Integrated ABAP Help
If you’re interested in the technical details of a field, place the cursor
on the field in the projection list of the CDS view, and trigger the (F2)
help. A brief characterization of the field pops up with information
about the underlying data element and its properties, such as the
technical type and the assigned field labels. Figure 1.30 illustrates
such a characterization for key field SalesOrder of CDS view
ZC_SalesOrderItemWithProduct, which references data element VBELN.
Note that you also can request the (F2) information for the entire CDS
view by positioning your cursor on the CDS view name.
Even though field SalesOrder of CDS view
ZC_SalesOrderItemWithProduct isn’t annotated explicitly, it’s
nevertheless equipped with some annotations from the perspective of
its consumers. To view these active annotations, choose Open With •
Active Annotations from the CDS editor context menu. This opens
the Active Annotations view, which is shown in Figure 1.31.
Figure 1.30 F2 Information about Field SalesOrder of CDS View
ZC_SalesOrderItemWithProduct
Figure 1.31
Active Annotations View
In the Active Annotations view, you find all effective annotations for
the CDS entity itself as well as for its elements and parameters. In the
given example, the SalesOrder field is annotated with @EndUserText....
The corresponding texts are defined by its typing data element VBELN
(Origin Data Element). For propagated element annotations or
annotations defined in metadata extensions, the definition source can
be found in the Origin Data Source column.
For analyzing these correlations in detail, select Open With •
Annotation Propagation in the CDS editor context menu. In the
launched overview of the applied annotation propagation logic, enter
the CDS field name “SalesOrder” in the Annotations For input field.
Figure 1.32 shows the result.
The information displayed is restricted by the entered filter to
annotations of field SalesOrder. You can adjust this overview according
to your needs by changing the applied filters. The result confirms the
previous analysis of active annotations.
Figure 1.32
Analysis of the Applied Annotation Propagation Logic
1.3
Summary
In this chapter, you learned about the main tools and processes for
creating and maintaining CDS models. The explanations are based
on a sample application that many examples in the subsequent
chapters will refer to. The fundamental concepts, which were only
quickly outlined in this chapter, will be explained more thoroughly in
the following detailed chapters of this book. The functions, which
you’ve become familiar with in this chapter, will cover a wide range of
tasks in developing and analyzing CDS models. This knowledge is a
prerequisite for creating the subsequently presented CDS sample
models yourself.
Now, let’s take a closer look at the details of the CDS syntax in the
next chapter.
2 Fundamentals of CDS Data
Modeling
This chapter explains the basic syntax and concepts of core
data services (CDS) for defining data models. It focuses on the
most important design-time artifact—the CDS view—and
describes its components.
CDS allows you to define and implement data models. The
implementation of these data models is based on the CDS data
definition language (DDL), which is closely related to the structured
query language (SQL). It supports defining several specialized CDS
entity types and related metadata models.
In this chapter, Section 2.1 provides an overview of these models
and their purposes. The remainder of the chapter deals with CDS
view models, which, from a developer’s perspective, are considered
the most important CDS entities. Specifically, you’ll learn how to
implement your data selections using CDS syntax. You’ll find an
overview of the fundamental syntax elements for CDS view
definitions in Section 2.2. Afterward, we’ll introduce you to the
following aspects in more detail:
Key fields (Section 2.3)
Cast operations (Section 2.4)
Typed literals (Section 2.5)
Simple types (Section 2.6)
Case statements (Section 2.7)
Session variables (Section 2.8)
Client handling (Section 2.9)
Select distinct statements (Section 2.10)
Union views (Section 2.11)
Intersect and except statements (Section 2.12)
Joins (Section 2.13)
SQL aggregation functions (Section 2.14)
Projection fields (Section 2.15)
Parameters (Section 2.16)
Reference fields (Section 2.17)
Conversion functions for currencies and quantity units
(Section 2.18)
Provider contracts (Section 2.19)
Entity buffer definitions (Section 2.20)
Apart from modeling data selections, you’ll also get acquainted with
CDS associations and annotations. Associations allow you to
establish directed relationships between your data models. These
associations enrich the data models with semantic information.
Furthermore, they can be leveraged when defining select
statements. Associations are described in detail in Chapter 3.
Annotations allow you to equip your CDS models with additional
metadata that is primarily expected to be interpreted by the
consumers of the CDS models. For example, CDS annotations are
evaluated by the Service Adaptation Definition Language (SADL)
infrastructure when defining an OData service. You’ll learn how
annotations are defined and applied in Chapter 4.
2.1
Overview of CDS Models
CDS models comprise the definitions of various CDS entity types
such as CDS views and CDS abstract entities. These entity types
will cover different use cases. Typically, the CDS entity type can’t be
changed after the corresponding CDS model is activated. From a
technical perspective, all CDS entity types are assigned to the object
type DDLS, which we introduced in Chapter 1, Section 1.2.2.
However, the range of CDS models also comprises further objects
such as CDS metadata extensions and simple types, which have
deviating object types of DDLX and DRTY, respectively.
Table 2.1 shows the fundamental definitions of the available CDS
models along with their envisioned usages.
CDS
Model
Definition
Application
View
define view ... as
select from ...
Data selection
View
entity
define view entity
... as select from
...
Data selection; successor of view
CDS model
Projection
view
define view entity
... as projection
on ...
Data selection
Transient
view
define transient
view entity ... as
projection on ...
Analytical query
CDS
Model
Definition
Application
Extend
view
extend view
... with ...
Extension of a CDS view
Extend
view
entity
extend view entity
... with ...
Extension of a CDS view entity
Table
function
define table
function ...
Data selection with SAP HANA–
native functions
Custom
entity
define custom
entity ...
ABAP-based data selection in
SADL-based OData services
Extend
custom
entity
extend custom
entity …with…
Extension of a CDS custom
entity
Abstract
entity
define abstract
entity ...
Modeling of structures
Extend
abstract
entity
extend abstract
entity …with…
Extension of an abstract entity
Hierarchy
define hierarchy
entity ...
Modeling of hierarchies
Metadata
extension
annotate view
... with ...
Annotation decorator
Simple
type
define type …
Typing of fields and parameters;
successor of ABAP Data
Dictionary data element in CDS
Entity
buffer
definition
define view entity
buffer on ...
Buffering records of CDS view
entities
Table 2.1
CDS Models
As shown, there are different types of CDS view models:
Views (aka V1 views)
Represent the original CDS view models.
View entities (aka V2 views)
Represent the successor of V1 views. Compared to V1 views, V2
views don’t generate an additional ABAP Data Dictionary view
upon their activation. This reduces the risk of technical
inconsistencies and improves the overall activation performance.
Furthermore, V2 views enforce more homogenous modeling and
apply stricter syntax checks. For example, V2 views foster uniform
client handling, which avoids some of the issues that might occur
when defining and using V1 views. Because primarily view entities
are expected to benefit from further improvements of the CDS
infrastructure, we recommend always defining V2 views (probably
as projection or transient views) instead of V1 views.
[»] Migration of V1 Views to V2 Views
You can migrate your existing V1 views to V2 views. This
migration can be achieved by manually changing the statement
define view … to define view entity …. Alternatively, you can run
report RUTDDLSV2MIGRATION or use the corresponding
functionality Migrate to CDS View Entity of the context menu in
the Project Explorer view of the ADT environment.
However, before migrating a V1 view, all the usages of its
generated SQL view need to be removed. Additionally, the
definitions of the V1 views and their extensions need to be aligned
with the stricter syntax checks and behavior of V2 views prior to
their migration. The migration tools just mentioned perform some
of the necessary adjustments automatically and provide you with
information about potential changes. Therefore, we suggest using
the tools for migrating V1 views.
Projection views
Represent a specialization of the view entities. Their main purpose
is the definition of interfaces on their underlying CDS models with
a modeled mapping of the corresponding functionality. As a result,
projection views restrict the overall functionality of view entities to
mere projection features. Projection views will be explained later
in Chapter 6, Section 6.1.
Transient views
Transient views (sometimes also referred to as V3 views) define
CDS view entities without a direct representation on the SAP
HANA database system. They act as mere declarative view
models whose runtime behavior is governed and implemented by
infrastructure components such as the analytical engine. This
implies that you can neither use transient views as data sources
for other CDS views nor can you select from transient views in
your ABAP code. You’ll learn about the use of transient views in
the context of implementing analytical queries in Chapter 10,
Section 10.3.
For the sake of brevity, we’ll refer to CDS view entities when talking
about CDS views or simply views unless explicitly mentioned
otherwise.
Extend views and extend view entities allow you to enhance a CDS
view or a CDS view entity, respectively, with additional fields and
associations; that is, they allow you to define extensions of CDS data
models. You’ll learn more about such extensions in Chapter 15.
Whereas the selection logic of CDS views is implemented in the
definitions of the CDS models themselves by applying a declarative
CDS syntax, the implementation of table functions leverages native
SAP HANA SQLScript syntax. The usage of SAP HANA SQLScript
enables a higher degree of freedom in the way the selection logic is
implemented. In addition, it provides you with the option to use
functions that aren’t yet supported by the CDS syntax. However,
there are also several drawbacks to using table functions. You’ll find
further information about CDS table functions in Chapter 7.
Similar to table functions, custom entities only capture the signature
of a data model within the CDS model definition itself. The actual
implementation of custom entities occurs in ABAP code. As such,
the logic of custom entities can’t be executed on the database level.
Instead, custom entities can be used to define OData entity sets that
are processed by the SADL infrastructure. For more information,
refer to http://s-prs.co/v529401.
[»] ABAP Logic Can’t Be Used in Logic Accessed from SAP
HANA
You can’t incorporate ABAP logic into the logic being executed on
SAP HANA. ABAP logic can only be processed before or after
processing the logic on SAP HANA.
Abstract entities are used to define signatures without
implementation. In the context of the ABAP RESTful application
programming model, you can use them for defining data structures
for parameters and results of actions and functions, as well as for
event payloads. Furthermore, abstract entities may be used for
defining proxies of external service models entities. For more
information, refer to the SAP documentation at http://sprs.co/v529421.
Hierarchy entities allow you to gain access to the functionality of
SAP HANA hierarchies; that is, they allow you to leverage SAP
HANA hierarchy features in your ABAP code. You’ll find an
explanation of the hierarchy modeling and its application in
Chapter 12, Section 12.3.
Whereas all the aforementioned CDS models represent dedicated
CDS entity types, metadata extensions represent an extension
option of the CDS entity type instead. From a technical perspective,
they are assigned the object type DDLX. The purpose of metadata
extensions is to enrich and redefine annotations of CDS entities. The
concept of metadata extensions will be explained in more detail in
Chapter 4, Section 4.4.
CDS simple types define reusable scalar type definitions. From the
CDS perspective, simple types can be interpreted as successors of
the ABAP Data Dictionary data elements. We’ll demonstrate possible
definitions and usages of simple types in Section 2.6.
For CDS view models with simple logic, a buffer can be defined on
the application server. In this context, entity buffer definitions capture
the required administrative data similar to table buffers that are
managed by the ABAP Dictionary. Direct data selections from
buffered CDS views via the ABAP SQL interface are automatically
transferred to the buffer by the ABAP runtime environment, if the
request can be processed on the buffer. This can help reduce the
load of the SAP HANA database and speed up the provisioning of
the requested data. You’ll find further information about entity buffer
definitions in Section 2.20.
2.2
Overview of CDS View Syntax
CDS views define select statements, which are augmented with
additional metadata information. Listing 2.1 provides an example of
such a definition.
/*comment*/
//other comment
//annotations
@AccessControl.authorizationCheck: #MANDATORY
@EndUserText.label: 'View Definition'
//view definition
define view entity Z_ViewDefinition
//parameter definition
with parameters
P_SalesOrderType : auart
//data source of selection with alias name
as select from ZI_SalesOrderItem as ITEM
//join
left outer to exact one join ZI_SalesOrder as SO
on SO.SalesOrder = ITEM.SalesOrder
//association definition
association [0..1] to ZI_Product as _Product on
$projection.RenamedProduct = _Product.Product
{
//projected field as key
key ITEM.SalesOrder,
//projected field used in association definition
key ITEM.Product
as RenamedProduct,
//constant
abap.char'A'
as Constant,
//calculated field
concat( ITEM.SalesOrder, ITEM.Product ) as CalculatedField,
//aggregate
count(*)
as NumberOfAggregatedItems,
//projected association
ITEM._SalesOrder,
//association exposure
_Product
}
//filter conditions based on join partner and parameter
where
SO.SalesOrderType = $parameters.P_SalesOrderType
//aggregation level
group by
ITEM.SalesOrder,
ITEM.Product
Listing 2.1
CDS View Definition
Let’s now take a closer look at this definition:
define statement
The define statement specifies the technical type of the CDS
model. In the given example, a CDS view entity model named
Z_ViewDefinition is defined (define view entity).
Data sources
CDS view Z_ViewDefinition uses the CDS view for sales order
items (ZI_SalesOrderItem) as its primary data source (select
from). It combines this primary data source via a left outer join
statement with a secondary data source, which provides the
header data of sales orders ZI_SalesOrder. Both data sources are
locally renamed by alias names ITEM and SO, respectively. You’ll
learn more about join types in Section 2.13.
Projected elements
CDS view Z_ViewDefinition projects various elements from its
primary data source, for instance, field SalesOrder, which identifies
the sales order, and field Product, which establishes a relationship
between the sales order item and its assigned product. The latter
field is renamed by aliasing it in RenamedProduct.
Aggregation
Due to the applied aggregation logic of the data records (group by)
by fields SalesOrder and Product, both of these fields determine
the key of CDS view Z_ViewDefinition.
Calculated fields
Besides the projected fields, CDS view Z_ViewDefinition also
defines the field Constant locally, which has the value A for all
records. Technically, this field is defined as a typed literal
(Section 2.5), which is based on the elementary ABAP Data
Dictionary type char of length 1. Furthermore, it contains field
CalculatedField, which concatenates the values of fields
SalesOrder and Product by applying function concat.
Field NumberOfAggregatedItems represents an aggregate. Its value
is determined by counting (count(*)) the records of sales order
items, which are grouped in a single record of the selection result.
where condition
The data selection is filtered by a where clause, which compares
the sales order type (SalesOrderType) with the value of CDS
parameter P_SalesOrderType.
Association
The CDS view model also defines association _Product, which
establishes a named relationship to the corresponding record of
the CDS view of product header ZI_Product. The association is
exposed by incorporating it in the projection list of the view. As a
result, the association can be used by consumers of the CDS view
too.
Comments
Within the CDS view model, comments refer to single-line
comments prefixed with double slashes (//) or incorporated into
the pattern /*…*/, which allows you to define multiline comments.
Annotations
Annotation @AccessControl.authorizationCheck : #MANDATORY
indicates that the selection result will be subject to an access
control. You’ll learn more about how to enable such authorization
checks in Chapter 5. Annotation @EndUserText.label specifies a
language-dependent text for the view that will correspond to its
description.
As you can see from the presented example, the CDS syntax
consists of various different elements. To master the definition of
CDS models yourself, we’ll provide you with more detailed
information about these syntax elements in the subsequent sections
and chapters of this book.
The definition of a CDS view model is captured as source code in
the transportable object of type DDLS. The CDS view isn’t
transported; instead, it’s generated locally when activating the DDLS
object. In the ABAP repository, it has object type STOB (CDS view).
For CDS view entities, the name of the CDS view (converted to
uppercase letters) has to match the name of its defining DDLS object.
[»] Consider the Namespace of CDS Models
The CDS view name shares the same namespace with other
ABAP Data Dictionary artifacts (e.g., tables and views). This
restriction has to be considered when choosing names for CDS
views and DDLS objects.
In addition, to avoid name clashes with SAP-delivered artifacts,
you should always use your dedicated customer or partner
namespace when defining your own CDS models.
2.3
Key Fields
CDS view fields are defined as key fields by adding the preceding
syntax element key to the fields. In general, the key of a CDS model
can be composed of multiple key fields. These key fields must be
placed before the non-key fields in the projection list of the CDS
view. The key must be defined in such a way that it uniquely
identifies a single record in the results list of a data selection. It’s
recommended to keep the key as short as possible; that is, fields
should only be included in the key definition if they are required for
achieving an unambiguous identification of a single record in the
results list of a data selection.
[»] Client Field
CDS view entities apply an automated filtering based on the client
(Section 2.9). Therefore, the client field isn’t contained in the list of
key fields, even if the underlying database records depend on the
client.
The sample CDS view for sales order item ZI_SalesOrderItem in
Listing 2.2 has a key that consists of the two fields SalesOrder and
SalesOrderItem. Both fields are required key components because
the identifier of the sales order item is only uniquely defined in the
context of its embedding sales order document. From a technical
perspective, it’s also possible to mark field Product as another key
field. However, this field isn’t required for making the key of the CDS
view unique, so it isn’t included in the illustrated key definition.
define view entity ZI_SalesOrderItem
as select from zsalesorderitem
{
key salesorder
as SalesOrder,
key salesorderitem as SalesOrderItem,
product
as Product
}
Listing 2.2
CDS View with Key Definition
The key definition serves as documentation of a CDS view model. In
addition, it can be used for consistency checks. For example, you
can check the specified cardinality of an association based on the
bound key fields of the target CDS model. Key definitions may also
have an impact on the CDS access controls that evaluate the key
definitions when injecting authorization restrictions into select
statements. Furthermore, it’s evaluated by various implementation
frameworks, which provide their services on top of the corresponding
CDS view. For instance, the key definition of a CDS model can be
used for automatically deriving the key definition of an OData entity
set, which is mapped onto the CDS model. Chapter 6 provides you
with more information about the OData exposure of CDS models.
[+] Increase Transparency of CDS Models
You should always define a key for your CDS models if feasible
from a technical perspective. A key definition should only be
omitted if the CDS model doesn’t contain suitable fields or
combinations for defining a unique key.
2.4
Cast Operations
Cast operations represent some of the most important fundamental
SQL functions. You can use cast operations for determining the type
of a calculated field and for converting the type of existing fields on
the database level. The CDS syntax supports casts onto elementary
ABAP types as well as onto data elements and simple types. By
using data elements or simple types, you not only can change the
type of the cast fields but also assign suitable annotations and
properties such as label texts and conversion exits to them.
If a cast doesn’t change the technical type of the field but, for
example, is performed to exchange its label texts, you can enrich the
cast statement with addition preserving type. This addition informs
the compiler that no effective type change is required, and, as a
result, no cast is required on the database level.
[»] Supported Type Conversions
Not all type conversions are supported by the CDS syntax. You’ll
find an overview of the supported conversions in your ABAP
documentation, for example, at http://s-prs.co/v529402.
Some type conversions aren’t supported by a cast operation and
can only be achieved by applying dedicated CDS conversion
functions. For example, you can only convert a floating point value
to a decimal value by using function fltp_to_dec.
In a CDS table function implementation, the extended options of
native SAP HANA conversion functions are available. Thus,
engaging a CDS table function may allow you to perform
conversions that aren’t supported by the CDS syntax itself.
Listing 2.3 shows a CDS view with fields that have different type
conversions.
define view entity Z_ViewWithCasts as select distinct from t000
{
t000.logsys
as ProjectedField,
'20170809'
as CharacterField,
cast ( '20170809' as abap.dats )
as DateField,
cast ( cast ( 'E' as abap.lang ) as sylangu preserving type )
as LanguageField,
1.2 as FloatingPointField,
fltp_to_dec( 1.2 as abap.dec(4,2) ) as DecimalField
}
Listing 2.3
CDS View with Cast Operations
By default, a field keeps the type of its origin if it’s simply projected
into a CDS view. For example, field ProjectedField retains the type
from its underlying base field t000.logsys.
If fields are locally defined, an implicit type assignment is applied. As
a result, field CharacterField is defined as a char field of length 8
based on the literal value 20170809. By explicitly casting the same
literal value onto type dats, field DateField becomes a date field.
You can also nest cast operations. This option is illustrated by
expression cast ( cast ( 'E' as abap.lang ) as sylangu preserving
type ). The value E is implicitly regarded as a character value of
length 1. In a first step, the type of corresponding field LanguageField
is explicitly set to lang. In a second step, data element sylangu finally
determines the type of the field. Because there is no change of the
technical type, this conversion is accompanied by addition
preserving type.
By assigning value 1.2 to field FloatingPoint, this field is implicitly
typed by fltp. In addition, field DecimalField is constructed from
value 1.2. Due to this value assignment, it’s implicitly defined as a
field of type fltp too. To convert this field into a field of type dec,
function fltp_to_dec is applied.
[+] Use Explicit Type Assignment
You should always explicitly cast calculated fields onto the desired
data type if feasible from a technical perspective. Otherwise, you
may experience unexpected side effects from implicit type
assignments.
If you’re not sure about the type of a CDS field, you can position your
cursor on the CDS view or one of its fields and press (F2). This
launches an information popup, as shown in Figure 2.1, where you
can find the previously discussed type information.
Figure 2.1
Technical Field Information of the CDS View from Listing 2.3
2.5
Typed Literals
Typed literals allow you to specify the technical ABAP type of a
constant value, which you introduce in your CDS model. To make a
plain literal become a typed literal, you have to enclose the actual
value in single quotation marks and add the type information as a
prefix. Listing 2.4 shows some examples.
define view entity Z_ViewWithTypedLiterals
as select distinct from t000
{
' Char10 '
as CharacterValue,
cast( ' Char10 ' as abap.char(10) ) as CastCharacterValue,
abap.char' Char10 '
as TypedCharacterValue,
cast( abap.char' Char10 '
as Char10 preserving type )
as CastTypedCharacterValue,
1234.56
as FloatingPointValue,
abap.fltp'1234.56'
as TypedFloatingPointValue,
fltp_to_dec(1234.56 as abap.dec(6,2)) as ConvertedDecimalValue,
abap.dec'1234.56'
as TypedDecimalValue,
abap.dec'001234.5600'
as TypedDecimalValue2
}
Listing 2.4
CDS View with Typed Literals
In this example, field CharacterValue is derived from literal ' Char10
'. From the CDS perspective, this field is implicitly typed as a
character field of length 8; that is, the leading two spaces are
considered, whereas the last two spaces are ignored.
With an explicit cast, you can enforce the field to have a length of 10
characters. This is shown by field CastCharacterValue. Similarly, field
TypedCharacterValue, which is derived from the typed literal
abap.char' Char10 ', becomes a character field of length 10
automatically. In other words, for typed literals, the entire value
entered in the quotation marks is considered to be significant.
If you define a field based on a typed literal and want to equip it with
additional metadata such as label texts, you should wrap the typed
literal with an explicit type-preserving cast onto a suitable data
element or simple type. This is illustrated by field
CastTypedCharacterValue.
Typed literals become especially important if the literals don’t
represent mere character strings. This will be demonstrated by the
remaining fields that are defined in Listing 2.4.
When defining a value such as 1234.56, it’s implicitly interpreted as a
floating point value. Consequently, FloatingPointValue and
TypedFloatingPointValue share the same technical typing. If you
want to define a decimal value instead, you basically have two
options: (1) perform an explicit type conversion of such a floating
point value by applying function fltp_to_dec, as illustrated for field
ConvertedDecimalValue; or (2) define a typed literal. Field
TypedDecimalValue in Listing 2.4 is typed accordingly. However, note
that abap.dec'1234.56' is interpreted as a decimal value from the
very beginning. In contrast, floating point value 1234.56 has to be
effectively converted to a decimal value. This not only implies that
there is some overhead in the processing logic of the value but also
may be the reason the field values returned by a view differ. In our
case, TypedDecimalValue exposes modeled value '1234.56',
whereas ConvertedDecimalValue returns converted value '1234.55'.
As mentioned before, the value and its notation are specified within
the typed literal matter. Typed literal abap.dec'1234.56' is associated
with a decimal type of length 6 with 2 decimals. Typed literal
abap.dec'001234. 5600' yields from typing field TypedDecimalValue2
as a decimal field of length 10 with 4 decimals.
[+] Use Typed Literals
We recommend always using typed literals instead of plain literals.
They help you avoid making errors and introducing unnecessary
conversions.
As already explained the concrete value defined in a typed literal is
decisive. For example, typed literal abap.dec'1234.56' results in field
TypedDecimalValue having type dec of length 6 with 2 decimals. In
contrast, typed literal abap.dec'001234.5600' results in field
TypedDecimalValue2 being defined as a decimal field with length 10
and 4 decimals.
2.6
Simple Types
CDS simple types can be specified based on different type
definitions: Listing 2.5 illustrates simple type ZBT_LanguageA, which is
based on elementary dictionary type lang.
define type ZBT_LanguageA : abap.lang;
Listing 2.5
Simple Type Based on an Elementary ABAP Data Dictionary Type
Listing 2.6 shows simple type ZBT_LanguageB, which is based on data
element spras.
define type ZBT_LanguageB : spras;
Listing 2.6
Simple Type Based on a Data Element
Listing 2.7 demonstrates the definition of simple type ZBT_LanguageC,
which is based on the simple type ZBT_LanguageB from Listing 2.6.
@EndUserText.label: 'Language Type C'
@Semantics.language: true
define type ZBT_LanguageC : ZBT_LanguageB;
Listing 2.7
Simple Type Based on Another Simple Type
In addition, Listing 2.7 shows how you can enrich the mere technical
definition of a simple type with annotations. In the given case, simple
type ZBT_LanguageC is classified as a language code by its annotation
@Semantics.language: true. In addition, it receives the label text
'Language Type C' from its annotation @EndUserText.label.
You can define multilevel hierarchies of simple types, as shown in
Listing 2.8. Therein, simple type ZBT_LanguageD is typed by simple
type ZBT_LanguageC from Listing 2.7.
@EndUserText.label: 'Language Type D'
@ObjectModel.sapObjectNodeTypeReference: 'Language'
define type ZBT_LanguageD : ZBT_LanguageC;
Listing 2.8
Simple Type Based on Another Simple Type: Multilevel
In this context, the annotations of simple type ZBT_LanguageC are
propagated to simple type ZBT_Language such as the technical
properties of the underlying data element spras. These annotations
are overlaid by the local annotations of simple type ZBT_LanguageD.
You’ll learn more about the resulting active annotations in Chapter 4,
Section 4.3.1.
You can leverage simple types for typing fields and parameters
(Section 2.16) of your CDS models. In Listing 2.9, CDS view field
Language1 is typed by simple type ZBT_LanguageA, and field Language2
is typed by simple type ZBT_LanguageD. These fields take over the
technical properties, including the annotations of their typing simple
types.
define view entity Z_ViewWithSimpleTypes
as select distinct from t000
{
cast ( abap.lang'E' as ZBT_LanguageA preserving type ) as Language1,
cast ( abap.lang'E' as ZBT_LanguageD preserving type ) as Language2
}
Listing 2.9
CDS View with Fields Typed by Simple Types
[+] Incorporate Annotations by Simple Types into CDS
Models
You can use simple types for incorporating field-specific or typespecific CDS annotations into your CDS models. In this context,
simple types act as reusable metadata containers, which support
you in efficiently defining consistent CDS models.
2.7
Case Statements
You can use case statements for defining conditional calculations in
your CDS view logic. Within a single case statement, you can define
multiple execution paths via when-then switches. Based on the when
condition, you can apply dedicated then instructions, which finally
determine the calculated value of a field. At the end of a case
statement, you can add an else instruction without conditions. This
branch allows you to handle cases that aren’t covered by the when
conditions. Without this fallback valuation, the calculated field may
have the value null, if none of the explicit when conditions match.
[ ! ] Avoid Non-Null Preserving Expressions
Unconditional else branches with assignments of constant values
can result in non-null preserving expressions. Such expressions
may prevent the SAP HANA optimizer from reordering the
execution plan for the most efficient processing of the select
statement, which in turn can result in performance issues.
Listing 2.10 provides an example for a CDS view with three case
statements.
define view entity Z_ViewWithCaseStatements
as select from ZI_SalesOrder
{
key SalesOrder,
case (SalesOrderType)
when 'TAF' then 'X'
when 'OAF' then 'X'
else ''
end
as IsStandardOrder,
cast( case (SalesOrderType)
when 'TAF' then 'X'
when 'OAF' then 'X'
else ''
end as abap.char(3) ) as IsStandardOrderAsChar3,
case when SalesOrderType = 'TAF' then 'X'
when SalesOrderType = 'OAF' then 'X'
else ''
end as IsStandardOrder2
}
Listing 2.10
CDS View with Case Statements
The first case statement checks the value of field SalesOrderType
and sets the value of field IsStandardOrder to the constant X if the
sales order type has a value of TAF or OAF. In all other cases, the
value is set to the initial value. In the given case, the calculation
implicitly results in a field of type char with length 1.
You can influence the resulting type explicitly using a wrapping cast
operation. This is illustrated for the second case statement, which
uses the same logic as the first cast statement. Due to the enclosing
cast operation, field IsStandardOrderAsChar3 receives type char with
length 3.
The third case statement contains the same logic as the first case
statement. From a principle perspective, the when conditions of the
branches of this third case statement may also implement complex
rules, for example, by applying pattern comparisons with like as well
as by combining multiple conditions with and and or operators. In
contrast, the when conditions in the first case statement only support
simple comparisons of operands.
2.8
Session Variables
CDS session variables allow you to access information regarding the
current runtime session within the CDS view logic. Similar to literal
values, you can use session variables at various places within the
implementation of your data selections.
Some standard usages are depicted in Listing 2.11, in which a filter
is defined by and fields are derived from session variables.
define view entity Z_ViewWithSessionVariables
as select from t000
{
$session.client
as ClientField,
$session.system_date
as SystemDateField,
$session.system_language as SystemLanguageField,
$session.user
as UserField,
$session.user_date
as UserDateField,
$session.user_timezone
as UserTimezoneField
}
where
mandt = $session.client
Listing 2.11
CDS View with Session Variables
Table 2.2 provides you with an overview of the session variables that
are supported by the CDS syntax.
Session Variable
Description
Correspondence
in ABAP Code
$session.client
Current client
sy-mandt
$session.system_date
System date of the
application server
sy-datum
$session.system_language
Logon language
sy-langu
$session.user
Current user
sy-uname
Session Variable
Description
Correspondence
in ABAP Code
$session.user_date
Current user date
sy-datlo
$session.user_timezone
User time zone
sy-zonlo
Table 2.2
Session Variables
[»] Session Variables versus Parameters
If you use session variables, the data selection of your CDS view
is influenced by factors that a consumer of your CDS view may not
be able to control explicitly. This can result in unwanted restrictions
and make the selection results more difficult to interpret.
In many cases, you can replace session variables with parameters
(Section 2.16), which eases the error analysis and maintenance of
CDS views. However, introducing parameters may require the
consumers of a CDS view to adapt. In contrast, the introduction of
session variables is a local implementation detail of a view.
Typically, from a technical perspective, it’s compatible for ABAP
consumers. Only when introducing a client dependency by using
session variable $session.client may adaptions be required.
2.9
Client Handling
When performing standard selections on database tables via the
ABAP SQL interface, client-specific data is automatically filtered by
the current client of the ABAP session. To perform cross-client data
accesses, either addition using client or addition client specified
must be added to the select statement. Listing 2.12 shows an
example of such a selection, where the data is read from client 001.
SELECT *
FROM zsalesorder
CLIENT SPECIFIED
INTO TABLE @DATA(lt_salesorder)
WHERE client = '001'.
Listing 2.12
Cross-Client Data Selection in ABAP
In CDS view entities, the standard client handling is enforced by the
ABAP Data Dictionary infrastructure. This is achieved by
automatically augmenting the technical view definition on SAP HANA
with an additional where condition, if this is required.
[ ! ] Consider Restrictions Imposed by Access Controls
Cross-client data accesses via CDS views aren’t allowed if the
CDS views are protected by access controls (see Chapter 5).
Listing 2.13 and Listing 2.14 illustrate an example. The CDS view
entity ZI_Product in Listing 2.13 selects from a client-dependent
table ZPRODUCT.
define root view entity ZI_Product
as select from zproduct
{
key product
as Product,
product_type
as ProductType,
creation_date_time as CreationDateTime
}
Listing 2.13
CDS View Selecting from a Client-Specific Data Source
Even though it doesn’t explicitly handle the client dependency, its
generated database view maps the client field CLIENT onto context
variable CDS_CLIENT, as depicted in Listing 2.14. This context variable
is associated with the session variable $session.client, thus the
selection result is restricted accordingly.
CREATE OR REPLACE VIEW "ZI_PRODUCT" AS SELECT
"ZPRODUCT"."CLIENT" AS "MANDT",
"ZPRODUCT"."PRODUCT" AS "PRODUCT",
"ZPRODUCT"."PRODUCT_TYPE" AS "PRODUCTTYPE",
"ZPRODUCT"."CREATION_DATE_TIME" AS "CREATIONDATETIME"
FROM "ZPRODUCT" "ZPRODUCT"
WHERE "ZPRODUCT"."CLIENT" = SESSION_CONTEXT(
'CDS_CLIENT'
)
Listing 2.14
Create Statement of the Database View Generated from Listing 2.13
Because the client is fixed, the client field shouldn’t be included in
the projection lists of the CDS views. However, CDS views, whose
records only differ in their client fields, are an exception.
The client field should also be contained in the signatures of CDS
table functions (see Chapter 7) that access client-specific data
records. For more information about this topic, see the ABAP
documentation at http://s-prs.co/v529403.
2.10
Select Distinct Statements
By applying the select distinct statement (select distinct), you can
remove duplicate records from the selection result list and thus
condense it. The comparison considers the values of all requested
fields. Let’s take a look at the sample CDS views in Listing 2.15 and
Listing 2.16.
CDS view Z_ViewWithoutSelectDistinct in Listing 2.15 uses table
T000, which contains one record per client. Its implemented logic
results in multiple selected records (one per client), which share the
value A for field Field1. Correspondingly, no key can be specified for
this CDS view.
define view entity Z_ViewWithoutSelectDistinct
as select from t000
{
abap.char'A' as Field1
}
Listing 2.15
CDS View without Select Distinct Statement
In contrast, CDS view Z_ViewWithSelectDistinct in Listing 2.16
combines the previous selection logic with a distinct statement.
Independent from the number of setup clients, that is, the number of
records provided by table T000, CDS view Z_ViewWithSelectDistinct
returns a single record (there is at least one record in table T000).
Therefore, Field1 can be marked as a key field.
define view entity Z_ViewWithSelectDistinct
as select distinct from t000
{
key abap.char'A' as Field1
}
Listing 2.16
CDS View with Select Distinct Statement
2.11
Union Views
Union views combine and unify data records of different data
sources. The outcome of such a union is a results list that comprises
all data records of the unified data sources and that is harmonized
from the perspective of its consumers, providing them with uniform
fields and associations. In Section 2.11.1, you get an overview about
the fundamental modeling of union CDS views. In Section 2.11.2, we
explain the differences between the plain union and the union all
operators.
2.11.1
Union Definitions
You define union views by combining multiple select statements with
the union statement. Each individual selection branch must define
the same fields and associations in the same order. In addition, the
corresponding items of the selection lists must have the same
definitions. This implies that the names of the elements within the
individual branches must be the same. Furthermore, the underlying
association definitions must specify the same on conditions,
cardinalities, and target entities. The key definitions must be the
same across all branches too. However, the types of the individual
fields of the union view may deviate for each branch, if they are
convertible. In such a case, the effective type of the field of the union
view is derived from the corresponding field of the first select
statement.
Similarly, element annotations are determined by the first selection
branch. Therefore, only elements of the first selection branch may be
annotated. Element annotations must not be propagated to a union
view. This requires annotating the union view with
@Metadata.ignorePropagatedAnnotations: true. Chapter 4,
Section 4.3 explains the propagation logic in more detail.
Let’s look at the following example, which comprises four CDS view
definitions. CDS view Z_ViewAsDataSourceA from Listing 2.17 acts as
a data source for union CDS view Z_UnionView from Listing 2.20,
later in this chapter.
define view entity Z_ViewAsDataSourceA
as select distinct from t000
association [0..1] to Z_ViewAsDataSourceC as _ViewC
on $projection.FieldA3 = _ViewC.FieldC1
{
key cast( 'A' as abap.char(1) ) as FieldA1,
cast( 'B' as abap.char(1) ) as FieldA2,
cast( 'C' as abap.char(2) ) as FieldA3,
_ViewC
}
Listing 2.17
CDS View Z_ViewAsDataSourceA
Likewise, CDS view Z_ViewAsDataSourceB from Listing 2.18 acts as a
data source for union CDS view Z_UnionView from Listing 2.20.
define view entity Z_ViewAsDataSourceB
as select distinct from t000
{
key cast( 'B_X' as abap.char(3) ) as FieldB1,
cast( 'A' as abap.char(1) )
as FieldB2
}
Listing 2.18
CDS View Z_ViewAsDataSourceB
CDS view Z_ViewAsDataSourceC from Listing 2.19 is used as the
association target of view Z_ViewAsDataSourceA from Listing 2.17 and
union CDS view Z_UnionView from Listing 2.20.
define view entity Z_ViewAsDataSourceC
as select distinct from t000
{
key cast( 'C' as abap.char(2) ) as FieldC1,
cast( 'C2' as abap.char(2) ) as FieldC2
}
Listing 2.19
CDS View Z_ViewAsDataSourceC
Each of the mentioned CDS views defines a single data record. For
example, CDS view Z_ViewAsDataSourceA selects a single data
record from table T000, which is always populated, by applying the
distinct statement. It returns the constant values A, B, and C for its
FieldA1, FieldA2, and FieldA3, respectively. Similarly, the other two
CDS views, Z_ViewAsDataSourceB and Z_ViewAsDataSourceC, return
single records with constant field values. Table 2.3 shows an
overview of the data provided by these CDS views.
CDS View
FieldA1/B1/C1 FieldA2/B2/C2 FieldA3
Z_ViewAsDataSourceA A
B
C
Z_ViewAsDataSourceB B_X
A
–
Z_ViewAsDataSourceC C
C2
–
Table 2.3
Data Records of the CDS Views from Listing 2.17 to Listing 2.19
Union CDS view Z_UnionView in Listing 2.20 combines CDS views
Z_ViewAsDataSourceA and Z_ViewAsDataSourceB as its data sources. In
addition, it exposes association _ViewC to CDS view
Z_ViewAsDataSourceC.
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_UnionView
as select from Z_ViewAsDataSourceA
association [0..1] to Z_ViewAsDataSourceC as _ViewC on $projection.UnionField1 =
_ViewC.FieldC1
{
@EndUserText.label: 'Label of UnionField1'
key FieldA1 as UnionField1,
key FieldA2 as UnionField2,
key FieldA3 as UnionField3,
_ViewC
}
union select from Z_ViewAsDataSourceB
association [0..1] to Z_ViewAsDataSourceC as _ViewC on $projection.UnionField1 =
_ViewC.FieldC1
{
key FieldB2 as UnionField1,
key FieldB1 as UnionField2,
key ''
_ViewC
as UnionField3,
}
Listing 2.20
Union CDS View Z_UnionView
To harmonize the element names, the fields contained in the
individual selection branches of union CDS view Z_UnionView are
mapped to field names UnionField1, UnionField2, and UnionField3
using alias function as. All fields contained in the field list of the union
CDS view have type char with length 1 or 2. This definition is derived
from the definition of underlying base fields FieldA1, FieldA2, and
FieldA3 of the first selection statement of the union CDS view.
Table 2.4 shows the resulting records of the union CDS view.
UnionField1 UnionField2 UnionField3
A
B
C
A
B
Initial value
Table 2.4
Records of the Union CDS View Z_UnionView
Note that FieldB1 of CDS view Z_ViewAsDataSourceB, which has a
maximum length of 3, is implicitly shortened when it’s mapped onto
UnionField1. Therefore, the corresponding data record of the union
CDS view contains value B instead of original value B_X (compare
Table 2.3 with Table 2.4). If the types of the mapped fields aren’t
automatically mutually convertible, you must explicitly define a type
conversion. This can typically be achieved by applying a cast
operation (Section 2.4) for the fields in question.
In general, each selection branch of a union view must define the
same number of fields, so UnionField3 must also be inserted into the
second selection branch. Because there is no related information
available in underlying base CDS view Z_ViewAsDataSourceB, this
field is filled with the initial value, which corresponds to its type.
In the first selection branch, UnionField1 is annotated with
@EndUserText.label.... Such annotations are valid for all other
branches too; that is, the annotations of the first branch determine
the corresponding annotations of the union CDS view as a whole.
In union CDS view Z_UnionView from Listing 2.20, association _ViewC
to view Z_ViewAsDataSourceC defined in its base view
Z_ViewAsDataSourceA can’t simply be projected into the first selection
branch because the definition of association _ViewC in the second
selection branch of union view Z_UnionView has a different ON
condition than in CDS view Z_ViewAsDataSourceA. Due to the
requirement that all selection branches must share the same
association definitions in union views, this association must be
redefined in the first selection branch too.
To avoid redundancies in association definitions, you could split the
definition of your union CDS views into two CDS views that are built
one on top of the other. Listing 2.21 and Listing 2.22 show an
example of such a two-layer construction.
CDS view Z_UnionViewWithoutAssociations from Listing 2.21
implements union logic.
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_UnionViewWithoutAssociations
as select from Z_ViewAsDataSourceA
{
@EndUserText.label: 'Label of UnionField1'
key FieldA1 as UnionField1,
key FieldA2 as UnionField2,
key FieldA3 as UnionField3
}
union select from Z_ViewAsDataSourceB
{
key FieldB1 as UnionField1,
key FieldB2 as UnionField2,
key ''
as UnionField3
}
Listing 2.21
Union CDS View without Associations
CDS view Z_UnionViewWithAssociations from Listing 2.22, which is
based on this intermediate view, enriches union logic with
association _ViewC without introducing redundancies in the definition.
define view entity Z_UnionViewWithAssociations
as select from Z_UnionViewWithoutAssociations
association [0..1] to Z_ViewAsDataSourceC as _ViewC
on $projection.UnionField1 = _ViewC.FieldC1
{
key UnionField1,
key UnionField2,
key UnionField3,
_ViewC
}
Listing 2.22
CDS View with Association Based on Union CDS View
[+] Association Definitions in Union CDS Views
If you need to define a larger number of associations within your
union CDS view, it may be beneficial to relocate the association
definitions to a separate superordinated CDS view. This CDS view
can transfer the field list of the union CDS view via the regular
projection mechanism and define the required associations once
locally.
2.11.2
Union and Union All Logic
When applying union logic, duplicate records, which originate from
the different merged data sources, are automatically removed from
the results list. For example, by applying union logic on top of the
same data source Z_ViewAsDataSourceA from Listing 2.17, the
implementation of CDS view Z_UnionViewWithoutDuplicates from
Listing 2.23 leads to a single resulting data record.
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_UnionViewWithoutDuplicates
as select from Z_ViewAsDataSourceA
{
key FieldA1
}
union select from Z_ViewAsDataSourceA
{
key FieldA1
}
Listing 2.23
Union CDS View without Duplicate Records (Union Logic)
If this isn’t desired, you should use union all logic, which retains the
data records of the data sources involved. In contrast to the selection
in Listing 2.23, by applying union all logic, the selection result of
CDS view Z_UnionViewWithDuplicates from Listing 2.24 contains two
identical data records.
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_UnionViewWithDuplicates
as select from Z_ViewAsDataSourceA
{
FieldA1
}
union all select from Z_ViewAsDataSourceA
{
FieldA1
}
Listing 2.24
Union CDS View with Duplicate Records (Union All Logic)
[»] Performance Implications
Due to the missing comparison and filtering of data records, union
all logic typically outperforms union logic. If applicable, you should
therefore choose the union all logic.
Merging selection results from various data sources requires a
critical check of the key definitions of the union CDS views. If CDS
views that have their own key definitions are merged, the key of the
union CDS view isn’t necessarily the same as the superset of all key
fields of the merged CDS views. In union CDS view Z_UnionView
from Listing 2.20, not only are UnionField1 and UnionField2 part of
the key but also UnionField3. In the given example, UnionField3 is
required for a unique differentiation between the two data records of
the selection result (refer to Table 2.4). If the second selection
branch of the union view would set this field to value C instead of
setting it to the initial value, both selection branches would return the
same data record. In this case, you wouldn’t be able to designate a
unique key for union CDS view Z_UnionView as a whole.
In general, a unique key can’t be specified for a CDS view with
duplicate records. Therefore, the key definition should always be
omitted in such a CDS view (refer to Listing 2.24).
2.12
Intersect and Except Statements
Besides merging records of data sources, the CDS syntax also supports you in defining intersections of
and exceptions from data sources, which will be illustrated in this section.
Listing 2.25 shows CDS view Z_UnionViewAsDataSourceA, which will act as a data source. It returns two
records as given in Table 2.5.
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_UnionViewAsDataSourceA
as select distinct from t000
{
key 'A' as Field1
}
union all select distinct from t100 {
key 'B' as Field1
}
Listing 2.25
CDS View Z_UnionViewAsDataSourceA
Z_UnionViewAsDataSourceA Z_UnionViewAsDataSourceB Z_ViewWithIntersect Z_ViewWithEx
A
A
A
B
B
C
-
-
Table 2.5
Values of Field1 of the CDS Views Records from Listing 2.25 to Listing 2.28
Similarly, Listing 2.26 shows CDS view Z_UnionViewAsDataSourceB, which will act as another data source.
It also returns two records, as given in Table 2.5.
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_UnionViewAsDataSourceB
as select distinct from t000
{
key 'A' as Field1
}
union all select distinct from t100 {
key 'C' as Field1
}
Listing 2.26
CDS View Z_UnionViewAsDataSourceB
If you want to determine those records that two data sources have in common, you can use the CDS
syntax element intersect. CDS view Z_ViewWithIntersect from Listing 2.27 defines such an intersection
of CDS view Z_UnionViewAsDataSourceA from Listing 2.26 and CDS view Z_UnionViewAsDataSourceB from
Listing 2.25. Both these data sources contain a record with the value A for Field1, which represents the
result of the intersection (refer to Table 2.5).
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_ViewWithIntersect
as select from Z_UnionViewAsDataSourceA
{
key Field1
}
intersect select from Z_UnionViewAsDataSourceB
{
key Field1
}
Listing 2.27
CDS View with Intersect
If you want to determine those records that are only contained in one of two data sources, you can use
the CDS syntax element except. CDS view Z_ViewWithExcept from Listing 2.28 excepts records of CDS
view Z_UnionViewAsDataSourceB from Listing 2.25 from CDS view Z_UnionViewAsDataSourceA from
Listing 2.24. Because both these data sources contain a record with the value A for Field1, the result is
defined by the record of CDS view Z_UnionViewAsDataSourceA with the value B for Field1 (refer to
Table 2.5).
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_ViewWithExcept
as select from Z_UnionViewAsDataSourceA
{
key Field1
}
except select from Z_UnionViewAsDataSourceB
{
key Field1
}
Listing 2.28
CDS View with Except
2.13
Joins
Joins allow you to model conditional links between two data sources.
The join conditions describe criteria for linking a data record of the
primary data source with a data record of the secondary data source.
You can use elements of joined data sources for defining elements in
the projection list of your CDS views. In addition, you can use the
elements of joined data sources for enriching the where conditions of
your CDS views.
[»] Performance Aspects
Joins make optimizing the execution plan of the selection request
at the database level more difficult. Therefore, you should only
integrate those joins into the logic of your CDS view definition that
are essentially required for the view’s functionality, and avoid
unnecessary joins of data sources resulting in denormalized CDS
view models. This particularly applies to CDS views that you
define for reuse purposes. Instead, you should consider providing
appropriate associations between the CDS models. These
associations can be used by the consumers of your model to
enrich the data records in a convenient way, where this is actually
necessary. We’ll discuss associations further in Chapter 3.
CDS views support four flavors of joins:
Left outer joins
These joins relate records of a primary data source with records of
a secondary data source so that the result contains all the data
records of the primary data source.
Right outer joins
These joins relate records of the secondary data source with the
records of the primary data source so that the result contains all
the data records of the secondary data source.
Inner joins
These joins relate records of a primary data source with records of
a secondary data source so that the result contains only those
records of the primary data source for which at least one join
partner in the secondary data source exists.
Cross joins
These joins combine all records of a primary data source with all
records of a secondary data source. The number of records in the
result set is equal to the number of records of the primary data
source multiplied by the number of records of the secondary data
source.
When dealing with joins, you’ll often be confronted with the term
cardinality. The cardinality of the join partner specifies the number of
data records that result from the join relationship. For example, if
there is more than one corresponding data record in the secondary
data source for a single record of the primary data source, the
number of resulting data records is multiplied by the cardinality of the
join partner when applying the left outer join logic.
In the following discussion, we’ll focus on the left outer joins and
inner joins, which are the most common join types. Sample CDS
views Z_ViewWithLeftOuterJoins from Listing 2.31 and
Z_ViewWithInnerJoins from Listing 2.33 (later in this section) will
illustrate the various aspects of the join logic.
Both these CDS views use CDS view Z_ViewAsDataSourceD from
Listing 2.29 as their primary data source.
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_ViewAsDataSourceD
as select distinct from t000
{
key cast( 'A' as abap.char(1) ) as FieldD1,
cast( 'D' as abap.char(1) ) as FieldD2
}
union select distinct from t000
{
key cast( 'C' as abap.char(1) ) as FieldD1,
cast( 'E' as abap.char(1) ) as FieldD2
}
Listing 2.29
CDS View Z_ViewAsDataSourceD
CDS view Z_ViewAsDataSourceE from Listing 2.30 acts as their
secondary data source.
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_ViewAsDataSourceE
as select distinct from t000
{
key cast( 'D' as abap.char(1) ) as FieldE1,
key cast( 'H' as abap.char(1) ) as FieldE2
}
union select distinct from t000
{
key cast( 'D' as abap.char(1) ) as FieldE1,
key cast( 'I' as abap.char(1) ) as FieldE2
}
union select distinct from t000
{
key cast( 'F' as abap.char(1) ) as FieldE1,
key cast( 'I' as abap.char(1) ) as FieldE2
}
Listing 2.30
CDS View Z_ViewAsDataSourceE
CDS views Z_ViewAsDataSourceD and Z_ViewAsDataSourceE return two
or three data records, as shown in Table 2.6.
CDS View
FieldD1 FieldD2 FieldE1 FieldE2
Z_ViewAsDataSourceD
A
D
–
–
C
E
–
–
CDS View
FieldD1 FieldD2 FieldE1 FieldE2
Z_ViewAsDataSourceE
–
–
D
H
–
–
D
I
–
–
F
I
Z_ViewWithLeftOuterJoins A
D
–
H
A
D
–
I
C
E
–
null
A
D
–
H
A
D
–
I
Z_ViewWithInnerJoins
Table 2.6
Records of the Data Sources and the Join CDS Views Built on Top
If these two CDS views are linked as data sources by a left outer join
according to CDS view Z_ViewWithLeftOuterJoins from Listing 2.31,
the results list comprises three data records (see Table 2.6).
define view entity Z_ViewWithLeftOuterJoins
as select from
Z_ViewAsDataSourceD
left outer one to many join Z_ViewAsDataSourceE
on Z_ViewAsDataSourceD.FieldD2 = Z_ViewAsDataSourceE.FieldE1
{
key Z_ViewAsDataSourceD.FieldD1,
key Z_ViewAsDataSourceD.FieldD2,
key Z_ViewAsDataSourceE.FieldE2
}
Listing 2.31
CDS View with a Left Outer Join
The first entry of CDS view Z_ViewAsDataSourceD, which has value A
for key field FieldD1, is linked to two data records of CDS view
Z_ViewAsDataSourceE. Consequently, the results list of CDS view
Z_ViewWithLeftOuterJoins has two data records for this entry. This
potential increase of the cardinality is expressed by addition to many
in the left outer join statement. If you take a closer look at the values
of FieldE1 of the join partner CDS view Z_ViewAsDataSourceE, you’ll
recognize that there is no or a single (i.e., at maximum one) record
of CDS view Z_ViewAsDataSourceD with a matching value of FieldD1.
Accordingly, source cardinality of the left outer join in CDS view
Z_ViewWithLeftOuterJoins is defined as one. Overall, this results in a
one-to-many join.
[+] Specify the Cardinality of Joins
You should always specify the maximum target cardinality (to one
or to exact one or to many) of a join partner. Furthermore, it may be
useful to define the source cardinality (one or exact one or many)
too. This specification is used both to document the composition of
the CDS view and optimize the processing of a selection request
in the database. However, if you maintain the cardinality
information, you should make sure that it’s defined correctly;
otherwise, the selection result may become incorrect!
The second record of view Z_ViewAsDataSourceD, which has value C
for key field FieldD1, doesn’t have a join partner. According to the
semantics of the left outer join, this data record remains in the results
list. However, in this case, associated FieldE2 of CDS view
Z_ViewWithLeftOuterJoins receives value null on the database level.
If you transfer the selection result to an internal ABAP table
according to Listing 2.32, the null value is implicitly converted to the
initial value of the corresponding ABAP field. By default, the ABAP
runtime environment doesn’t distinguish between initial values and
null values in contrast to the CDS language and the SQL logic.
SELECT *
FROM z_viewwithleftouterjoins
INTO TABLE @DATA(lt_viewwithleftouterjoins).
Listing 2.32
Data Selection from the CDS View from Listing 2.31 in ABAP
[ ! ] Required Handling of Null Values
On the SQL level, the difference between initial values and null
values is significant in that null values represent nonvaluated
data. In the CDS views, null values usually result from left outer
joins without a matching join partner.
When modeling your CDS view logic, you must consider this
distinction and handle it if necessary. For example, when
comparing field values in where conditions, you have to consider
potential null values. However, null values won’t simply be
replaced by initial values in your CDS logic. Otherwise, you may
experience significant performance issues.
In contrast to a left outer join, an inner join removes source records
without a join partner from the selection result.
Accordingly, the selection result of CDS view Z_ViewWithInnerJoins
from Listing 2.33 contains only two data records (refer to Table 2.6).
define view entity Z_ViewWithInnerJoins
as select from Z_ViewAsDataSourceD
inner join
Z_ViewAsDataSourceE
on Z_ViewAsDataSourceD.FieldD2 = Z_ViewAsDataSourceE.FieldE1
{
key Z_ViewAsDataSourceD.FieldD1,
key Z_ViewAsDataSourceD.FieldD2,
key Z_ViewAsDataSourceE.FieldE2
}
Listing 2.33
CDS View with an Inner Join
You can achieve a corresponding selection result using a left outer
join relationship too. In this case, the corresponding left outer join
statement must be accompanied by additional where conditions,
which ensure that a data record of the respective join partner exists.
Within these where conditions, you can validate that an adequate
field (usually a key field) of the join partner has a value, which is
different from the null value. Listing 2.34 illustrates this approach by
checking FieldE2 of left outer join partner CDS view
Z_ViewAsDataSourceE.
define view entity Z_ViewWithLeftOuterJoinsFiltrd
as select from
Z_ViewAsDataSourceD
left outer one to many join Z_ViewAsDataSourceE
on Z_ViewAsDataSourceD.FieldD2 = Z_ViewAsDataSourceE.FieldE1
{
key Z_ViewAsDataSourceD.FieldD1,
key Z_ViewAsDataSourceD.FieldD2,
key Z_ViewAsDataSourceE.FieldE2
}
where
Z_ViewAsDataSourceE.FieldE2 is not null
Listing 2.34
Join Partner
CDS View with a Left Outer Join and an Additional Existence Check for the
If you combine multiple data sources using joins, you must ensure
that references to the elements of these data sources remain unique.
To achieve this, you have to prefix the element name with the name
of its data source, separated by a period. In the previous examples,
this was realized by using the name of the data source as a qualifier.
However, if the same data source is included multiple times in the
same CDS view, you have to specify suitable alias names for each of
its occurrences, as shown in Listing 2.35. In this case, the CDS view
Z_ViewAsDataSourceD is used twice as a data source. To distinguish
between the two embedments of this CDS view, they are assigned
distinct alias names D1 and D2.
define view entity Z_ViewWithJoinsAndDataSrcAlias
as select from
Z_ViewAsDataSourceD as D1
left outer exact one to exact one join Z_ViewAsDataSourceD as D2
on D1.FieldD1 = D2.FieldD1
{
key D1.FieldD1,
D2.FieldD2
}
Listing 2.35
Times
CDS View with Alias Names for the Same Data Source Joined Multiple
2.14
SQL Aggregation Functions
SQL aggregation functions allow you to perform calculations of
predefined aggregates efficiently on the database level. You can use
these SQL functions within the implementation of your CDS views.
To do this, first define the aggregation level to which you want to
aggregate the result. Enter the aggregation level using syntax
element group by, which should be placed after the projection list. It
has to include all fields of the data sources that are included in the
projection list of the CDS view. Fields whose aggregate is calculated
are excluded from this. Within the projection list of the CDS view, you
then apply the envisioned aggregation function to the fields that are
relevant for aggregation.
An example is given by CDS view Z_ViewAsDataSourceF from
Listing 2.36 and CDS view Z_ViewWithAggregations from Listing 2.37.
CDS view Z_ViewAsDataSourceF acts as the data source for the
aggregation.
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_ViewAsDataSourceF
as select distinct from t000
{
key abap.char'A' as Field1,
key abap.char'A' as Field2,
abap.int1'1' as Field3
}
union all select distinct from t000
{
key abap.char'A' as Field1,
key abap.char'B' as Field2,
abap.int1'2' as Field3
}
union all select distinct from t000
{
key abap.char'A' as Field1,
key abap.char'C' as Field2,
abap.int1'3' as Field3
}
Listing 2.36
CDS View Z_ViewAsDataSourceF
It defines three data records, as depicted in Table 2.7.
Field1 Field2 Field3
A
A
1
A
B
2
A
C
3
Table 2.7
Records of CDS View Z_ViewAsDataSourceF
CDS view Z_ViewWithAggregations from Listing 2.37 contains the
aggregation logic. Its group by statement defines that the data source
records are to be aggregated up to the level of the first key field:
Field1.
define view entity Z_ViewWithAggregations
as select from Z_ViewAsDataSourceF
{
key Field1,
min(Field3)
as FieldWithMin,
max(Field3)
as FieldWithMax,
avg( Field3 as abap.decfloat34 ) as FieldWithAvg,
cast( sum(Field3) as abap.int4 ) as FieldWithSum,
count( distinct Field1 )
as FieldWithCountDistinct,
count(*)
as FieldWithCountAll
}
group by Field1
Listing 2.37
CDS View with Aggregation Functions
The result of the selection from CDS view Z_ViewWithAggregations
comprises a single data record. The minimum (min) value
(FieldWithMin = 1), maximum (max) value (FieldWithMax = 3), average
(avg) value (FieldWithAvg = 2), and summed-up (sum) value
(FieldWithSum = 6) of Field3 are calculated. In addition, for each
entry in the results list of aggregating view Z_ViewWithAggregations,
the number (= 1) of its underlying aggregated original data records
that differs in the value of grouping field Field1 (count distinct) is
calculated and returned by FieldWithCountDistinct. Finally, the total
number (= 3) of underlying aggregated data records (count(*)) is
calculated and returned by FieldWithCountAll.
Note that the fields for minimum value FieldWithMin and maximum
value FieldWithMax retain type int1 of underlying base field Field3.
By default, this would also hold true for field FieldWithSum, which
contains the result of the summation. However, in the example, a
cast operation is applied, which explicitly extends the type to int4.
When applying the average function, an explicit type assignment is
required. FieldWithAvg is typed as decfloat34. The fields that expose
the result of the count operation—FieldWithCountDistinct and
FieldWithCountAll—are implicitly assigned ABAP type int4.
[ ! ] Avoid Memory Overflows
To avoid a memory overflow at the execution time of the data
selection when using aggregation functions, you should check the
expected value ranges and existing type assignments of the fields
and enhance these types by explicit type conversions if necessary.
If you use aggregation functions, the grouping fields defined in the
group by statement ideally should not contain any calculated or
joined fields. Furthermore, it may be beneficial to perform the
required aggregations as deep as possible within the CDS view
stack to reduce the amount of processed data to its minimum as
soon as possible.
If the values to be aggregated represent amounts or quantities, you
must normalize them before the aggregation is performed. This
means that you may first have to convert them to the same unit or
keep the currency and unit information in the aggregation result to
avoid unwanted calculation errors. We’ll explain conversion functions
in Section 2.18.
[»] Performance Aspects
If you use conversion functions on nonaggregated single data
records, this can have a negative effect on performance. In such
cases, you should consider first aggregating the data records
while keeping their units and applying the conversion functions to
the preaggregated data records.
2.15
Projection Fields
Projection fields are defined in the select list of a CDS view. Within
the CDS view definition, you can access these fields by prefixing
their names with operator $projection followed by a dot. You can
use projection fields to refer to calculation results defined within the
same view. This allows you to build up nested calculations with the
exposed fields serving as reusable intermediate results. In addition,
you can use projection fields (with some restrictions) for defining
associations (see Chapter 3).
Listing 2.38 illustrates the implementation of CDS view
Z_ViewWithProjectionFields, which leverages projection fields for
further calculations.
define view entity Z_ViewWithProjectionFields
as select distinct from t000
{
abap.char'A' as FieldA,
abap.char'B' as FieldB,
concat( $projection.FieldA, $projection.FieldB ) as FieldC,
concat( abap.char'A', abap.char'B' ) as FieldC2,
concat( $projection.FieldC, abap.char'D' ) as FieldD,
concat( concat( abap.char'A', abap.char'B' ), abap.char'D' )
as FieldD2
}
Listing 2.38
CDS View with Projection Fields
In this example, two fields, FieldA and FieldB, are defined with
constant values A and B, respectively. These locally defined fields are
concatenated to FieldC. The resulting value is the same as when
concatenating the original constants, A and B; that is, FieldC2 holds
the same value as FieldC. Similarly, FieldD and FieldD2 hold the
same values, with FieldD reusing the determination logic of FieldC
and with FieldD2 calculating the result from scratch.
From a technical perspective, the modeled reuse on the CDS level is
being expanded when creating the corresponding database view on
SAP HANA. This is shown by the create statement in Listing 2.39, in
which the definitions of fields FieldC and FieldC2—which are FieldD
and FieldD2, respectively—are identical.
CREATE OR REPLACE VIEW "Z_VIEWWITHPROJECTIONFIELDS" AS SELECT
DISTINCT N'A' AS "FIELDA",
N'B' AS "FIELDB",
CONCAT(
N'A',
N'B'
) AS "FIELDC",
CONCAT(
N'A',
N'B'
) AS "FIELDC2",
CONCAT(
RTRIM(
CONCAT(
N'A',
N'B'
)
),
N'D'
) AS "FIELDD",
CONCAT(
RTRIM(
CONCAT(
N'A',
N'B'
)
),
N'D'
) AS "FIELDD2"
FROM "T000" "T000"
Listing 2.39
Create Statement of the Database View Generated from Listing 2.38
2.16
Parameters
Parameters are constituents of the CDS model signature. They
represent scalar input values that must be supplied by the caller
when performing data selections.
You can access and evaluate parameter values in the logic of your
CDS models. This allows you to equip consumers of your CDS
models with predefined control options for their data selections. You
can also use parameters for enforcing a restriction of the selection
result by applying them as predefined filter criteria.
Parameters are listed directly after the name of the CDS model. The
parameter list is introduced by CDS syntax element with parameters.
Each parameter has a name and a type separated by a colon. The
type can be based on elementary ABAP types, data elements, and
simple types.
The example from Listing 2.40 shows two alternatives for assigning
types to parameters.
define view entity Z_ViewWithParameters
with parameters
P_KeyDate : abap.dats,
P_Language : sylangu
as select from …
Listing 2.40
Definition of Parameters
The parameter names must be unique within a CDS model and must
be different from the element names.
[+] Parameter Names
You should start the name of parameters with the prefix P_,
followed by the semantic name of the transported value. Following
this rule creates a clear separation of the parameter names from
the names of the remaining elements of the CDS models.
Parameters can be used in different places within a CDS model. For
example, you can use parameters to do the following:
Define where conditions in a CDS view.
Calculate fields in the projection list of a CDS view.
Supply parameters of other data sources with the requested
values.
Within the implementation of a CDS model, parameters can be
accessed by the preceding syntax element $parameters, which is
separated by a dot from the parameter name.
Let’s now have a look at the potential usages of parameters with the
following sample CDS views and ABAP code snippets.
In all depicted cases, CDS view Z_ViewWithParametersDataSource
from Listing 2.41 serves as the fundamental data source. According
to its definition, it returns a single data record.
define view entity Z_ViewWithParametersDataSource
as select distinct from t000
{
key abap.char'A'
as KeyField,
key abap.lang'E'
as Language,
key abap.dats'99580809' as ValidityEndDate,
abap.dats'20111004' as ValidityStartDate
}
Listing 2.41
CDS View Z_ViewWithParametersDataSource
CDS view Z_ViewWithParameters in Listing 2.42 uses CDS view
Z_ViewWithParametersDataSource as its direct base data source. It
defines parameters for key date P_KeyDate and language P_Language.
define view entity Z_ViewWithParameters
with parameters
P_KeyDate : abap.dats,
P_Language : sylangu
as select from Z_ViewWithParametersDataSource
association [0..*] to Z_ViewWithParametersAscTarget as _Target
on $projection.KeyField = _Target.KeyField
association [0..1] to Z_ViewWithParametersAscTarget as _FilteredTarget
on $projection.KeyField = _FilteredTarget.KeyField
and $projection.Language = _FilteredTarget.Language
{
key KeyField,
ValidityEndDate,
ValidityStartDate,
$parameters.P_Language
as Language,
_Target(P_ValidityDate: $parameters.P_KeyDate)
[1:Language= $parameters.P_Language].KeyField as TargetKeyField,
_FilteredTarget
}
where ValidityEndDate
>= $parameters.P_KeyDate
and ValidityStartDate <= $parameters.P_KeyDate
and Language
= $parameters.P_Language
Listing 2.42
CDS View with Parameters
These parameters are used in its where condition for restricting the
selection result. Parameter P_Language is also added as field
Language to the projection list of the CDS view. This field will have the
same value as the parameter for all data records. As a result, the
parameter value can indirectly be used in the definition of
association _FilteredTarget in expression $projection.Language =
_FilteredTarget.Language. Alternatively, you could use the
parameter directly on the right-hand side of an expression such as
_FilteredTarget.Language = $parameters.P_Language.
The target of association _FilteredTarget is CDS view
Z_ViewWithParametersAscTarget with parameter P_ValidityDate, as
shown in Listing 2.43.
define view entity Z_ViewWithParametersAscTarget
with parameters
P_ValidityDate : abap.dats
as select from Z_ViewWithParametersDataSource
{
key KeyField,
key Language,
ValidityEndDate,
ValidityStartDate
}
where ValidityEndDate
>= $parameters.P_ValidityDate
and ValidityStartDate <= $parameters.P_ValidityDate
Listing 2.43
CDS View with a Parameter Serving as an Association Target
When accessing field KeyField of this target CDS view by applying
path expression _Target(P_ValidityDate: $parameters.P_KeyDate)
[1:Language= $parameters.P_Language].KeyField in CDS view
Z_ViewWithParameters, its own parameter P_ValidityDate must be
supplied. This is achieved by binding it to parameter P_KeyDate of
CDS view Z_ViewWithParameters.
Within the depicted path expression, the second local parameter
P_Language of the source view Z_ViewWithParameters is also used for
applying a filter condition on associated target field Language. This
means that the key and thus the associated data record of target
CDS view Z_ViewWithParametersAscTarget are uniquely identified. As
a result, the maximum cardinality of the path expression is defined
as 1.
[»] Missing Support for Specifying Target Parameters in
Association Definitions
Parameters of association targets can’t be supplied within the
definition of an association. Among other things, this means you
can’t explicitly bind parameters of associated CDS models.
Therefore, a coupling of the parameters in CDS models can’t be
enforced. Instead, the consumer has to supply the parameters
consistently when selecting data from the associated CDS models.
If you want to use CDS model Z_ViewWithParameters as a data
source within a select statement, its parameters must be supplied
with values. For example, Listing 2.44 shows CDS view
Z_ViewWithParametersConsumer, which supplies parameters P_KeyDate
and P_Language of its data source with session variable
$session.system_date and constant value E, respectively.
define view entity Z_ViewWithParametersConsumer
as select from Z_ViewWithParameters(
P_KeyDate: $session.system_date,
P_Language: 'E')
{
key KeyField
}
Listing 2.44
CDS View Supplying Parameters of Its Data Source
Listing 2.45 shows a corresponding selection in ABAP code. In this
example, ABAP system field sy-datum is used for supplying
parameter P_KeyDate.
SELECT keyfield
FROM z_viewwithparameters(
p_keydate = @sy-datum,
p_language = 'E' )
INTO TABLE @DATA(lt_viewwithparameters).
Listing 2.45
Data Selection in ABAP Corresponding to the CDS Modeling in Listing 2.44
Within the CDS view stack, parameters must always be supplied. In
contrast, the ABAP SQL interface supports the automatic supply of
parameters, which are annotated with @Environment.systemField….
The permitted values of this annotation, such as SYSTEM_DATE and
SYSTEM_LANGUAGE, correspond to the values of the ABAP system
fields, such as sy-datum and sy-langu. If such an annotated
parameter isn’t supplied explicitly with a value when selecting data
via the ABAP SQL interface, the ABAP runtime automatically fills the
parameter with the value of the corresponding ABAP system field.
As a result, the corresponding parameter of the CDS model
becomes optional from an ABAP perspective.
This is demonstrated by the following example, which comprises the
CDS view Z_ViewWithOptionalParameters in Listing 2.46 and the two
data selections in Listing 2.47 and Listing 2.48.
define view entity Z_ViewWithOptionalParameters
with parameters
@Environment.systemField: #SYSTEM_DATE
P_KeyDate : abap.dats
as select distinct from Z_ViewWithParametersDataSource
{
key KeyField
}
where ValidityEndDate
>= $parameters.P_KeyDate
and ValidityStartDate <= $parameters.P_KeyDate
Listing 2.46
CDS View with a Specially Annotated Parameter
Parameter P_KeyDate of CDS view Z_ViewWithOptionalParameters is
annotated with @Environment.systemField: #SYSTEM_DATE. It’s
explicitly supplied in Listing 2.47 with system field sy-datum.
SELECT *
FROM z_viewwithoptionalparameters( p_keydate = @sy-datum )
INTO TABLE @DATA(lt_viewwithoptionalparameters).
Listing 2.47
Data Selection in ABAP with Explicit Supply of the CDS View Parameter
In contrast, the data selection in Listing 2.48 doesn’t supply this
parameter explicitly.
SELECT *
FROM z_viewwithoptionalparameters
INTO TABLE @DATA(lt_viewwithoptionalparameters).
Listing 2.48
Data Selection in ABAP with Implicit Supply of the CDS View Parameter
Nevertheless, both selections yield the same result.
[+] Check Usage of Parameters
Because parameters typically force consumers of a CDS model to
supply them with values, the consumers must know the lists of
permitted values to carry out data selections successfully.
Therefore, a simple data selection may not be possible without this
knowledge. Furthermore, the addition, modification, or removal of
a parameter is usually incompatible for consumers of the affected
CDS model because consumers have to react to these changes.
Therefore, you should only define parameters in your CDS models
if they are essential for the functionality of the corresponding CDS
models.
2.17
Reference Fields
Reference fields are amount and quantity fields that define
references to currency and unit fields, respectively. Such a reference
is technically enforced for fields that have the ABAP type curr or
quan.
In CDS models, references to currencies and units are established
by means of annotations @Semantics.amount.currencyCode... and
@Semantics.quantity.unitOfMeasure..., respectively. Therein, the
assigned annotation values represent the respective currency and
unit fields that the annotated fields refer to. The referenced currency
and unit fields with fields of type curr and quan have to be defined as
fields of type cuky or unit. The currency codes themselves originate
from table TCURC. The quantity units are defined by table T006.
Listing 2.49 shows sample CDS view Z_ViewWithReferenceFieldsA
with quantity and amount fields.
define view entity Z_ViewWithReferenceFieldsA
as select distinct from t000
{
@Semantics.quantity.unitOfMeasure: 'QuantityUnit'
abap.quan'1234.56'
as Quantity,
abap.unit'PC'
as QuantityUnit,
@Semantics.amount.currencyCode: 'Currency'
abap.curr'1234.56'
as Amount,
abap.cuky'USD'
as Currency
}
Listing 2.49
CDS View with Amount and Quantity Fields
[ ! ] Use Curr Fields with Two Decimals Only
When defining curr fields, you should always define them with two
decimals. Otherwise, the currency shift logic may not work
properly.
You’ll learn more about the currency shift logic in Chapter 17,
Section 17.2.3.
Besides plain quantities referring to units of table T006, the CDS
models also support defining quantities that are calculated from
other quantities and amounts. For regular quantities, for example,
the units of such calculated quantities are referenced by
@Semantics.quantity.unitOfMeasure.... These units can represent
complex expressions involving various base units and currencies.
Accordingly, they are defined as character fields. Typically, such
fields are modeled as virtual fields in the CDS models and populated
by ABAP code. Of course, if technically feasible, you could also
construct the unit values by applying suitable string operations, as
depicted earlier in Listing 2.46.
If a curr field will be incorporated into a calculation, it first must be
converted into a plain value without its currency reference by using
function get_numeric_value. Similarly, if you want to decouple
quantities from their unit, you can use the same function. For a curr
field, this function not only removes the reference and converts the
curr value to a value of type decfloat34 but also applies an implicit
decimal shift.
If you only want to convert the type of an amount field from curr to
decfloat while keeping its reference field nature, you can use
function curr_to_decfloat_amount. Because the resulting field is to
be considered an amount, it has to specify a reference to its
currency via @Semantics.amount. currencyCode....
CDS view Z_ViewWithReferenceFieldsB from Listing 2.50, which is
based on CDS view Z_ViewWithReferenceFieldsA from Listing 2.49,
illustrates the usage of the aforementioned conversion functions and
the definition of a calculated quantity, including its unit.
define view entity Z_ViewWithReferenceFieldsB
as select from Z_ViewWithReferenceFieldsA
{
@Semantics.quantity.unitOfMeasure: 'QuantityUnit'
Quantity,
QuantityUnit,
@Semantics.amount.currencyCode: 'Currency'
Amount,
Currency,
get_numeric_value( Amount )
as AmountWithoutReference,
get_numeric_value( Quantity )
as QuantityWithoutReference,
@Semantics.amount.currencyCode: 'Currency'
curr_to_decfloat_amount( Amount ) as DecfloatAmount,
@Semantics.quantity.unitOfMeasure: 'CalculatedUnit'
get_numeric_value( Amount ) / $projection.quantity
as AmountPerQuantity,
cast( concat( Currency,
concat( '/', QuantityUnit)
) as abap.char(50) )
as CalculatedUnit
}
Listing 2.50
CDS View with Converted Amount and Quantity Fields
If you want to get an overview about the reference fields of your CDS
entities, you can place the cursor on the CDS entity name and press
(F2). This will open up a popup that lists the fields along with their
types and references, as illustrated in Figure 2.2 for CDS view
Z_ViewWithReferenceFieldsB from Listing 2.50.
Figure 2.2
Technical Field Information of the CDS View from Listing 2.50
2.18 Conversion Functions for Currencies
and Quantity Units
Conversion functions for the conversion of currencies and units of
measure are based on several sets of persistent data records. These
data records are included in the evaluation logic during processing of
the conversion logic. The corresponding persistency captures, for
example, the time-dependent conversion factors between currencies
of different countries. This information must be integrated into the
conversion logic based on the relevant key date.
The CDS syntax allows you to embed conversion functions directly
into the implementation of your CDS views.
Listing 2.51 depicts such an embodiment for a unit conversion.
define view entity Z_ViewWithUnitConversions
with parameters
P_DisplayUnit : msehi
as select from ZI_SalesOrderItem
{
key SalesOrder,
key SalesOrderItem,
@Semantics.quantity.unitOfMeasure: 'OrderQuantityUnit'
OrderQuantity,
OrderQuantityUnit,
@Semantics.quantity.unitOfMeasure: 'OrderQuantityDisplayUnit'
unit_conversion( quantity => OrderQuantity,
source_unit => OrderQuantityUnit,
target_unit => $parameters.P_DisplayUnit,
error_handling => 'FAIL_ON_ERROR' )
as OrderQuantityInDisplayUnit,
$parameters.P_DisplayUnit as OrderQuantityDisplayUnit
}
Listing 2.51
CDS View with Unit Conversion
Listing 2.52 shows a corresponding example of a currency
conversion.
define view entity Z_ViewWithCurrencyConversions
with parameters
P_DisplayCurrency : waers_curc,
P_ExchangeRateDate : sydatum
as select from ZI_SalesOrderItem
{
key SalesOrder,
key SalesOrderItem,
@Semantics.amount.currencyCode: 'TransactionCurrency'
NetAmount,
TransactionCurrency,
@Semantics.amount.currencyCode: 'DisplayCurrency'
currency_conversion(
amount => NetAmount,
source_currency => TransactionCurrency,
target_currency => $parameters.P_DisplayCurrency,
exchange_rate_date => $parameters.P_ExchangeRateDate,
exchange_rate_type => 'M',
round => 'X',
decimal_shift => 'X',
decimal_shift_back => 'X',
error_handling => 'SET_TO_NULL' )
as NetAmountInDisplayCurrency,
$parameters.P_DisplayCurrency as DisplayCurrency
}
Listing 2.52
CDS View with Currency Conversion
The conversion functions have mandatory input parameters as well
as optional parameters. The latter only need to be supplied if you
want to override the default values that are assigned to them
automatically. Depending on the concrete input parameter of the
chosen conversion function, you can supply it with a literal value,
with an actual value from the respective data record, or with a
parameter value.
The unit conversion uses function unit_conversion. Earlier in
Listing 2.47, the quantity (quantity) and its unit (source_unit) are
provided as input parameters to the conversion function. These input
parameters are bound to fields OrderQuantity and OrderQuantityUnit
of CDS view Z_ViewWithUnitConversions. The semantic correlation
between these two fields is expressed by annotation
@Semantics.quantity.unitOfMeasure:'OrderQuantityUnit' of field
OrderQuantity.
The target unit of conversion target_unit is defined by CDS
parameter P_DisplayUnit. The result of the conversion is returned via
field OrderQuantityInDisplayUnit. This field is related to unit field
OrderQuantityDisplayUnit, which is derived from CDS parameter
P_DisplayUnit via its annotation
@Semantics.quantity.unitOfMeasure: 'OrderQuantityDisplayUnit'.
The currency conversion (currency_conversion) from Listing 2.48
essentially corresponds to the previously discussed unit conversion.
However, more input parameters are included in the calculation.
Besides the amount (amount), which is filled with the actual value of
field NetAmount, the currency (source_currency), which is filled with
the actual value of field TransactionCurrency, and target currency
(target_currency), which is specified by CDS parameter
P_DisplayCurrency, in particular, the key date of conversion
exchange_rate_date must be specified by the function’s caller. In our
case, this is specified by CDS parameter P_ExchangeRateDate.
Furthermore, the exchange rate type (exchange_rate_type) is set to
fixed value M.
The currency conversion function allows you to activate or deactivate
the business rounding (round) and the usage of shifts of decimal
places before (decimal_shift) and after the calculation
(decimal_shift_back). In the example described here, all these
parameters are filled with constant literal value X. This valuation
corresponds to the default values of the supplied parameters.
The relationships between the amount fields and their currency fields
are expressed by annotations @Semantics.amount.currencyCode:....
Particular attention must be paid to the handling of possible errors
when dealing with conversion functions. The two examples just
described apply error handling FAIL_ON_ERROR. This corresponds to
the default value of the corresponding input parameter and causes a
runtime error in the database processing if the conversion can’t be
carried out successfully.
There are many possible root causes for such errors. For example,
the unit conversion can’t be executed successfully if the value of the
target unit, which is supplied by the related input parameter, doesn’t
exist. Error handling FAIL_ON_ERROR therefore requires a high degree
of data consistency and data completeness, as well as its own
prevalidations of the supplied input parameters, for keeping possible
error situations to a minimum.
Besides error-handling value FAIL_ON_ERROR, the conversion
functions also support error-handling values KEEP_UNCONVERTED and
SET_TO_NULL. Value KEEP_UNCONVERTED can be used for preserving the
original value as the target value if problems occur in the context of
the conversion. Value SET_TO_NULL can be used for setting the target
value to null in case of issues. If you require any of these errorhandling strategies, you should define the corresponding input
parameter of the conversion function accordingly. However, note that
you might not be able to detect issues easily if you choose an errorhandling strategy that is different from FAIL_ON_ERROR.
[»] Conversion Functions in Analytical Queries
In analytical query CDS views, the conversion functions for
currencies and quantities are performed by the analytic engine
after the records are aggregated. This can significantly improve
the performance compared to applying the same conversion
functions already in the underlying data sources.
In addition, the analytic engine implements its own handling for
conversion errors: when applying the conversion in an analytical
query view, the original value with its unit is preserved if the
requested conversion fails.
You’ll find more information about analytical queries and the
analytical engine in Chapter 10, Section 10.3.
2.19
Provider Contracts
CDS provider contracts formally define rules that the definition of a
CDS model, which is classified accordingly, must follow. The rules
consider predefined usage scenarios of the CDS models. Contentwise, the provider contracts correspond to the modeling patterns
captured by annotation @ObjectModel.modelingPattern. Within a
composition hierarchy of CDS models, only the root CDS model may
be assigned a provider contract. Compositional children inherit the
provider contract from their root.
The underlying rules of the provider contracts will ensure that CDS
entities can actually be used as specified. They may result in both
restricting the admissible modeling options and enabling frameworkspecific functions. Major parts of the rules are enforced by the CDS
syntax check and as such may impact the activation of the CDS
models, which means if you assign a provider contract to a CDS
model but contradict its rules, it may not be possible to activate the
CDS model. In contrast to the provider contract being defined by the
corresponding CDS syntax elements, the aforementioned annotation
@ObjectModel.modelingPattern doesn’t influence the activation but
serves documentation purposes only.
Table 2.8 presents on overview of the provider contracts and their
intended usages.
Provider Contract
Usage
Provider Contract
Usage
transactional_interface
This CDS projection view serves as a
programmatic and modeled interface of
the functionality of business objects in
the ABAP RESTful application
programming model context. Only
simple projections of the underlying
entities are supported.
transactional_query
This CDS projection view defines a
service interface on top of a CDS view
model for transactional processing.
CDS projection views with this provider
contract may be defined on top of
regular CDS views without a provider
contract or on top of CDS projection
views with provider contract
transactional_interface.
analytical_query
This transient query CDS projection
view is executed by the analytical
infrastructure.
Table 2.8
Provider Contracts
Listing 2.53 to Listing 2.55 demonstrate an example for the usage of
the provider contracts in a CDS view stack. Fundamental CDS view
Z_ViewWithProviderContractA from Listing 2.53 acts as the root CDS
view of a compositional hierarchy.
define root view entity Z_ViewWithProviderContractA
as select distinct from t000
{
key abap.char'A' as KeyField
}
Listing 2.53
CDS View Z_ViewWithProviderContractA
CDS view Z_ViewWithProviderContractB from Listing 2.54 leverages
CDS view Z_ViewWithProviderContractA as its data source. It’s
defined as a root CDS projection view with provider contract
transactional_interface via the corresponding syntax element. In
addition, this CDS view is annotated with
@ObjectModel.modelingPattern : #TRANSACTIONAL_INTERFACE.
@ObjectModel.modelingPattern: #TRANSACTIONAL_INTERFACE
define root view entity Z_ViewWithProviderContractB
provider contract transactional_interface
as projection on Z_ViewWithProviderContractA
{
key KeyField
}
Listing 2.54
CDS Projection View Z_ViewWithProviderContractB
CDS view Z_ViewWithProviderContractB itself is used as a data
source of the transactional query CDS view
Z_ViewWithProviderContractC from Listing 2.55, which is assigned
CDS provider contract transactional_query and is annotated
accordingly too.
@ObjectModel.modelingPattern: #TRANSACTIONAL_QUERY
define root view entity Z_ViewWithProviderContractC
provider contract transactional_query
as projection on Z_ViewWithProviderContractB
{
key KeyField
}
Listing 2.55
CDS Projection View Z_ViewWithProviderContractC
2.20
Entity Buffer Definitions
Entity buffer definitions allow you to speed up selections from CDS
views in ABAP. This is achieved by processing selection requests
directly on the ABAP application server instead of delegating them to
the SAP HANA database.
To be able to buffer a CDS view, the implemented logic of the CDS
view is subject to several restrictions. For example, CDS views with
parameters and CDS views that use functions with varying results
such as utcl_current can’t be buffered. Those responsible for the
CDS views need to document by means of annotation
@AbapCatalog.entityBuffer.definitionAllowed :true that they are
aware of these technical limitations and that they will consider these
restrictions during the further lifecycle of the annotated CDS views.
Listing 2.56 shows CDS view Z_ViewWithBufferA, which is annotated
accordingly. It selects directly from table T000.
@AbapCatalog.entityBuffer.definitionAllowed: true
define view entity Z_ViewWithBufferA as select from t000
{
key mandt as KeyField,
mtext as Field
}
Listing 2.56
CDS View Z_ViewWithBufferA with Buffer Option
If you want to enable a buffer for a CDS view, which uses another
CDS view as its data source, not only the CDS view itself but also all
the underlying CDS views must support buffers in principle.
CDS view Z_ViewWithBufferB from Listing 2.57 also supports
buffering. This is technically feasible because its own data source—
CDS view Z_ViewWithBufferA—also offers a buffer option.
@AbapCatalog.entityBuffer.definitionAllowed: true
define view entity Z_ViewWithBufferB as select from Z_ViewWithBufferA
{
key KeyField,
Field
}
Listing 2.57
CDS View Z_ViewWithBufferB with Buffer Option
We now want to create an entity buffer definition. Entity buffer
definitions can be created from within the ADT Project Explorer
view. Therein, select the CDS view to be buffered and call function
New Entity Buffer from its context menu. Enter the requested
information in the dialog that is launched and confirm the creation.
In our case, we want to create a buffer entity definition for CDS view
Z_ViewWithBufferA from Listing 2.56 that is named equally. This
entity buffer definition will be assigned to the lowest definition layer
core and specify a buffer strategy on a single record level (type
single). Listing 2.58 demonstrates the corresponding definition.
define view entity buffer on Z_ViewWithBufferA
layer core
type single
Listing 2.58
Entity Buffer Definition for CDS View Z_ViewWithBufferA in the Layer Core
There are multiple logical layers to which buffer definitions can be
assigned. This allows specifying multiple entity buffer definitions for a
single CDS view. However, in a single layer, there may at maximum
be a single entity buffer definition per CDS view. The entity buffer
definition of the highest layer determines the effective buffer
handling.
Imagine that CDS view Z_ViewWithBufferA from Listing 2.56 and its
buffer definition from Listing 2.58 could not be redefined (e.g.,
because they were shipped by SAP), but you would like to disable
the buffer. You can achieve this by creating your own entity buffer
definition for CDS view Z_ViewWithBufferA in a higher layer and
deactivate the standard buffering from there. Listing 2.59 illustrates
the corresponding entity buffer definition ZZ1_ViewWithBufferA.
define view entity buffer on Z_ViewWithBufferA
layer customer
type none
Listing 2.59
Entity Buffer Definition for CDS View Z_ViewWithBufferA in Layer Customer
It’s assigned the highest layer customer and specifies via the
declaration type none that no buffering will be applied. This setting
overrules the entity buffer definition from Listing 2.58.
[»] Buffer Handling
The definition and application of buffers will always be aligned with
the actual data selections: on one side, buffers may restrict the
further evolution of the buffered CDS views, and on the other side,
you have to be aware that even if a buffer exists, not all selections
can be executed on the ABAP application server. Furthermore, the
administration of the buffers can imply a significant overhead and
negatively impact resource consumption and performance. This is
especially true if a huge amount of data is buffered, if the control
settings of the buffers aren’t chosen in an optimal way, or if the
buffered data is invalidated frequently. You should also consider
that entity buffers may be built up in parallel to existing table
buffers. In such cases, you should evaluate whether both buffers
are actually required or whether you could switch your selections
for leveraging a single buffer only.
2.21
Summary
In this chapter, you learned how data provisioning can be
implemented by leveraging SQL concepts such as joins, unions, and
aggregation functions, as well as specialized CDS functions such as
entity buffers. You also were introduced to CDS parameters. CDS
parameters can be applied for enforcing user input as well as for
providing context information that can be used for implementing
selection logic.
In the next chapter, you’ll learn more about associations, which were
only briefly introduced in this chapter.
3
Associations
In this chapter, you’ll learn how to define associations in your
core data services (CDS) models. You’ll also learn how to use
associations in view building as well as in your ABAP code.
Associations establish direct relationships between CDS models.
You can leverage these associations for implementing the internal
logic of your CDS views. Furthermore, you can expose associations
to enable consumers of your CDS views to reuse the logic that is
captured in the association definitions. This consumption covers both
the usage within dependent views in a CDS view stack as well as the
usage within select statements in ABAP code.
Apart from data provisioning, exposed associations are also used for
documenting CDS models. They act as carriers of metadata for
frameworks too, which interpret and derive many functions from this
information. For example, associated dimension views can be used
as a source of display attributes in analytical queries (see
Chapter 10, Section 10.2.4).
[»] Technical Aspects of Associations
From a technical perspective, associations are mapped onto joins.
However, these joins only become effective and, therefore, are
only executed, if the associations are actually used. This is the
case if an association is engaged for defining a path expression to
a field within a select statement. In contrast, the mere definition
and propagation of an association within a CDS view stack isn’t
manifested in the database views, which are generated from the
CDS views.
Conceptually, fields and associations are regarded as equal from the
CDS language. Both are called CDS elements. However, there are
some substantial differences between fields and associations from
an implementation perspective, which will be elaborated on in
subsequent chapters.
In this chapter, we’ll first focus on the definition (Section 3.1) and
exposure of associations (Section 3.2). Then, we’ll discuss special
flavors of associations that model compositional (Section 3.3) and
m:n relations (Section 3.4). Associations can be projected similar to
fields, which will be shown in Section 3.5. Afterward, you’ll learn how
you can leverage associations for implementing the logic of your
CDS views in Section 3.6. Last, you’ll learn how to use associations
in ABAP code in Section 3.7.
3.1
Define Associations
Association definitions describe the relationships of the respective
definition sources with their associated targets on the record level.
For example, in Listing 3.1, association _Item relates records of
source CDS view ZI_SalesOrder with records of target CDS view
ZI_SalesOrderItem.
define view entity ZI_SalesOrder
as select from …
association [0..*] to ZI_SalesOrderItem as _Item
on $projection.SalesOrder = _Item.SalesOrder
…
Listing 3.1
Association Definition
The name of an association can be specified by using the alias
function as. In the example from Listing 3.1, the association is called
_Item.
[»] Consider the Namespace of Associations
The names of CDS elements (fields and associations) and the
names of CDS parameters share the same namespace; that is,
they all must be uniquely defined within a CDS model. Therefore,
you should clearly separate names of associations from field
names and parameter names. We recommend that you prefix the
semantic name of an association with an underscore as in the
aforementioned example.
In addition to the specification of association target
ZI_SalesOrderItem, the association definition also comprises
information about the correlation between the data records of the
source and target entity by means of an on condition. In the given
example, field SalesOrder of the projection list of the source CDS
view ($projection.SalesOrder) is mapped onto the equally named
field of the target CDS view (addressed by the path expression
_Item.SalesOrder). If this association is used for accessing data
records of the target CDS view, the runtime filters the data records of
the target CDS view based on the source data records accordingly.
In principle, a single data record of a source CDS view can be
related to any number of data records of its association target,
depending on the specified on condition of the association. Vice
versa, there can be different numbers of source data records for a
given target record. The possible number of corresponding data
records specifies the cardinality of an association. There are two
alternatives for specifying the cardinality of an association:
A cardinality specification in square brackets […] (see Listing 3.1)
describes by the captured lower and upper limits the minimum and
maximum number of data records of the association target that
are related to a single source record.
A cardinality specification using the syntax elements [OF [EXACT]
ONE/MANY] TO [EXACT] ONE/MANY allows you not only to capture the
target cardinality but also the source cardinality.
[+] Specify Cardinality of Associations
You should always define the cardinality of an association
explicitly and as precisely as possible. Specify both the minimum
and maximum values to increase the readability of your CDS
models and enable the SAP HANA optimizer to derive the most
efficient execution plan for your selection request.
Table 3.1 shows examples of frequently used cardinality information.
Cardinality
Records of
Association Source
Records of
Association Target
Minimum Maximum Minimum Maximum
[1]
0
Unlimited
0
1
[0..1]
0
Unlimited
0
1
[1..1]
0
Unlimited
1
1
[0..*]
0
Unlimited
0
Unlimited
[1..*]
0
Unlimited
1
Unlimited
TO ONE
0
Unlimited
0
1
TO EXACT ONE
0
Unlimited
1
1
Cardinality
Records of
Association Source
Records of
Association Target
Minimum Maximum Minimum Maximum
TO MANY
0
Unlimited
0
Unlimited
OF ONE TO ONE
0
1
0
1
OF EXACT ONE TO
EXACT ONE
1
1
1
1
OF MANY TO MANY
0
Unlimited
0
Unlimited
OF MANY TO ONE
0
Unlimited
0
1
OF MANY TO EXACT
ONE
0
Unlimited
1
1
Not specified
(default logic)
0
Unlimited
0
1
Table 3.1
Cardinality Information in Association Definitions
[ ! ] Check Cardinality Information
You should check the cardinality information of your association
definitions. Incorrect cardinality information can lead to
subsequent modeling errors. At runtime, it can also result in
duplicate as well as missing data records and erroneous data in
the selection results. Incorrect cardinality information may also
negatively affect the performance of a data selection.
Specifically, when using the exact semantic, you have to carefully
evaluate whether it’s also applicable outside the CDS view
defining the corresponding association.
In general, associations can be defined by several CDS models. For
example, CDS views as well as CDS extend views can define
associations. Among others, the following objects may be used as
association targets:
CDS views
CDS table functions
CDS abstract entities
Database tables
[+] Choose Appropriate Association Targets
You should primarily define associations between CDS entities of
the same type to avoid functional restrictions. For example, in
analytical applications, database tables can’t be used as targets of
foreign key associations of CDS views.
3.2
Expose Associations
By defining an association, the association becomes available for the
internal implementation of a CDS view. To make the association
accessible for consumers of the CDS view, it must be included in the
projection list of the CDS view in the same way as fields. Without this
exposure, the association definition is only an internal
implementation detail. You can also realize its functionality by using
corresponding joins.
In Listing 3.2, previously defined association _Item is incorporated
into the projection list of its defining CDS view where it becomes part
of the external signature of the CDS view model and can be
accessed by CDS view consumers.
define view entity ZI_SalesOrder
as select from ...
association [0..*] to ZI_SalesOrderItem as _Item
on $projection.SalesOrder = _Item.SalesOrder
{
key SalesOrder,
_Item,
...
}
Listing 3.2
CDS View with an Exposed Association
[+] Remove Unused Associations
Defined associations that are neither used for implementing the
view logic nor exposed through the signature of a CDS view don’t
provide any functionality. However, as a part of the definition of the
CDS view, they are checked during its activation. You should
delete such associations to avoid unnecessary activation
problems.
Note that associations to extension include views are defined but
intentionally aren’t exposed (see Chapter 15). They are used by
the extensibility infrastructure for defining path expressions in the
infrastructure’s generated artifacts.
3.3
Model Compositional Relations
Compositional associations represent a specialization of
associations. They model an existence-based relation between a
compositional child and its parent. For example, they may be used to
define that a sales order item (compositional child) always belongs to
a sales order header (compositional parent).
There are specific CDS syntax elements that you can use for
defining compositional relationships. We’ll introduce you to these
syntax elements via the sample views from Listing 3.3 to Listing 3.5.
Listing 3.3 shows the root CDS view of a composition.
define root view entity Z_CompositionRootView
as select distinct from t000
composition [0..*] of Z_CompositionChildView as _ChildView
{
key abap.char'R' as RootKeyField,
_ChildView
}
Listing 3.3
Composition Root CDS View
A CDS entity becomes a root entity by adding the key word root to
its define statement. Compositional relations are defined by key
words composition ... of. Starting from the root CDS entity, the
relations span the entire compositional hierarchy.
In our example, CDS view Z_CompositionChildView from Listing 3.4 is
included by association _ChildView as a child CDS view into the
hierarchy of CDS view Z_CompositionRootView from Listing 3.3.
define view entity Z_CompositionChildView
as select distinct from t000
association to parent Z_CompositionRootView as _RootView
on $projection.RootKeyField = _RootView.RootKeyField
composition [0..*] of Z_CompositionGrandchildView
as _GrandchildView
{
key abap.char'R' as RootKeyField,
key abap.char'C' as ChildKeyField,
_RootView,
_GrandchildView
}
Listing 3.4
Composition Child CDS View
A composition child must specify a to parent association to its
composition parent and expose this association in its projection list.
Because there is a strict dependency on the existence of a single
parent record, the minimum and maximum target cardinality of this
association is always 1. The source cardinality is determined by the
corresponding composition association. Therefore, the cardinality
specification is omitted in the definition of such associations.
The on condition of the to parent association has to map all the key
fields of the associated parent entity. This on condition both specifies
the relationship between the child records and their parent records
and defines the relationship between the parent records and their
child records. This means that the on condition of the association
_RootView in Listing 3.4 also determines the on condition of the
reverse association _ChildView in Listing 3.3. The latter is therefore
not explicitly defined.
[»] Compositional Relationships in Abstract Entities
In abstract entities, you may omit the definition of on conditions for
compositional associations completely.
In our example, child CDS view Z_CompositionChildView itself
defines a compositional relationship to grandchild CDS view
Z_CompositionGrandchildView of the hierarchy from Listing 3.5, thus
extending it by an additional level.
define view entity Z_CompositionGrandchildView
as select distinct from t000
association [1..1] to Z_CompositionRootView as _RootView
on $projection.RootKeyField = _RootView.RootKeyField
association to parent Z_CompositionChildView as _ChildView
on $projection.RootKeyField = _ChildView.RootKeyField
and $projection.ChildKeyField = _ChildView.ChildKeyField
{
key abap.char'R' as RootKeyField,
key abap.char'C' as ChildKeyField,
key abap.char'G' as GrandchildKeyField,
_RootView,
_ChildView
}
Listing 3.5
Composition Grandchild CDS View
In our case, grandchild CDS view Z_CompositionGrandchildView
specifies an additional association _RootView, which establishes a
direct link to root CDS entity Z_CompositionRootView of the hierarchy.
This is technically not enforced. However, you’ll often find such an
association because it facilitates the definitions of access controls
(see Chapter 5) and the like.
[ ! ] Consider Technical Restrictions of Compositions
When defining compositions, you have to be aware of some
technical restrictions with regard to their application. In comparison
to regular associations, you can’t use compositions for defining
path expressions in the defining CDS views themselves.
There are also additional consistency constraints. For example,
parents must define compositional associations to their children
and vice versa, and root CDS views must be defined as such with
the corresponding syntax element root. These consistency
constraints must be fulfilled to successfully activate a
corresponding CDS model.
3.4
Model M:N Relations
If a data record of an entity has relationships to many data records of
another entity and vice versa, the two entities have an m:n
relationship to each other. If you want to model such m:n
relationships in your CDS views, you must usually model standalone
CDS views that capture the corresponding m:n relationship itself.
These separated CDS views avoid the need to enrich the source (or
the target) CDS views of the relations with the m:n relationship
information by applying joins. Otherwise, unwanted cardinality
changes could result.
The example shown in Figure 3.1 illustrates such a model with three
CDS views: Z_MToNRelationViewA, Z_MToNRelationViewB, and
Z_MToNRelationViewC. You can see the association network between
these CDS views.
Figure 3.1
Associations of CDS Views Related via an M:N Relationship
CDS view Z_MToNRelationViewA from Listing 3.6 (hereafter referred to
as view A) has a single key field, KeyFieldA. Based on this key field,
it’s related to CDS view Z_MToNRelationViewC from Listing 3.8 via its
association _MToN. It delivers two data records in accordance with
Table 3.2.
define view entity Z_MToNRelationViewA
as select distinct from t000
association [0..*] to Z_MToNRelationViewC as _MToN
on $projection.KeyFieldA = _MToN.KeyFieldA
{
key abap.int4'1' as KeyFieldA,
_MToN
}
union select distinct from t000
association [0..*] to Z_MToNRelationViewC as _MToN
on $projection.KeyFieldA = _MToN.KeyFieldA
{
key abap.int4'2' as KeyFieldA,
_MToN
}
Listing 3.6
CDS View Z_MToNRelationViewA
Similarly, CDS view Z_MToNRelationViewB from Listing 3.7 (hereafter
referred to as view B) establishes a relationship based on its key
field KeyFieldB with view Z_MToNRelationViewC from Listing 3.8 via its
association _MToN. This CDS view also returns two data records.
define view entity Z_MToNRelationViewB
as select distinct from t000
association [0..*] to Z_MToNRelationViewC as _MToN
on $projection.KeyFieldB = _MToN.KeyFieldB
{
key abap.int4'3' as KeyFieldB,
_MToN
}
union select distinct from t000
association [0..*] to Z_MToNRelationViewC as _MToN
on $projection.KeyFieldB = _MToN.KeyFieldB
{
key abap.int4'4' as KeyFieldB,
_MToN
}
Listing 3.7
CDS View Z_MToNRelationViewB
Associated CDS view Z_MToNRelationViewC from Listing 3.8 has a
combined key comprising key fields KeyFieldA and KeyFieldB. It
represents an m:n relationship between the CDS views A and B from
Listing 3.6 and Listing 3.7. It defines associations _ViewA and _ViewB
to these two CDS views, allowing its consumers to relate records
from CDS view A with the records of CDS view B and vice versa.
define view entity Z_MToNRelationViewC
as select distinct from t000
association [0..1] to Z_MToNRelationViewA as _ViewA
on $projection.KeyFieldA = _ViewA.KeyFieldA
association [0..1] to Z_MToNRelationViewB as _ViewB
on $projection.KeyFieldB = _ViewB.KeyFieldB
{
key abap.int4'1' as KeyFieldA,
key abap.int4'3' as KeyFieldB,
_ViewA,
_ViewB
}
union select distinct from t000
association [0..1] to Z_MToNRelationViewA as _ViewA
on $projection.KeyFieldA = _ViewA.KeyFieldA
association [0..1] to Z_MToNRelationViewB as _ViewB
on $projection.KeyFieldB = _ViewB.KeyFieldB
{
key abap.int4'1' as KeyFieldA,
key abap.int4'4' as KeyFieldB,
_ViewA,
_ViewB
}
union select distinct from t000
association [0..1] to Z_MToNRelationViewA as _ViewA
on $projection.KeyFieldA = _ViewA.KeyFieldA
association [0..1] to Z_MToNRelationViewB as _ViewB
on $projection.KeyFieldB = _ViewB.KeyFieldB
{
key abap.int4'2' as KeyFieldA,
key abap.int4'4' as KeyFieldB,
_ViewA,
_ViewB
}
Listing 3.8
CDS View Z_MToNRelationViewC
CDS view Z_MToNRelationViewC returns three data records in total
(see Table 3.2). These correlate the corresponding data records of
CDS views A and B with each other. Note that if you wanted to
integrate this information into one of these two views (A or B), their
existing data records would be multiplied accordingly.
Depending on source view A or B, a consumer can now use path
expressions in the form _MToN._ViewB or _MToN._ViewA to evaluate the
related data sets of the target views B or A.
Table 3.2 gives you an overview of the data records provided by the
different CDS views.
CDS View
KeyFieldA KeyFieldB
Z_MToNRelationViewA 1
–
2
–
Z_MToNRelationViewB –
3
–
4
Z_MToNRelationViewC 1
3
1
4
2
4
Table 3.2
Data Records of the Views from Listing 3.6 to Listing 3.7
3.5
Project Associations
You can include the exposed associations of an underlying CDS
entity in the projection list of your own CDS view and thus expose
them there as well. If necessary, you can assign alias names to the
projected associations. In other words, the exposed associations of a
CDS entity can, in principle, be used in the same way as the fields of
this CDS view within the implementation of another CDS view.
Listing 3.9 illustrates an example.
define view entity ZC_SalesOrder
as select from ZI_SalesOrder
{
key ZI_SalesOrder.SalesOrder,
ZI_SalesOrder._Item as _SalesOrderItem
}
Listing 3.9
CDS View with a Projected Association
In this example, CDS view ZC_SalesOrder projects association _Item
of its underlying CDS view ZI_SalesOrder from Listing 3.2. The
association is exposed with alias name _SalesOrderItem. In addition
to association _Item, field SalesOrder, which is used to define the on
condition of the association, must also be included in the projection
list of view ZC_SalesOrder.
[»] Consider Dependencies of Projected Associations
If you expose an association through projection in your CDS view,
you must ensure that all fields of the base view that are used in
the on condition of its association definition are also included in the
projection list of your CDS view. If required, you can rename these
base fields via alias names.
3.6
Use Associations in CDS Views
The associations can be used to define path expressions, whose
segments are separated by periods. The last segment of these paths
is either a field or an association of the target entity of the preceding
association. You’ll learn more about defining path expressions in
Section 3.6.1.
[»] Consider Dependencies of Path Expressions
If you chain several associations in path expressions, you must
also include all the source fields, which are consumed in the on
conditions of the individual association definitions in your CDS
view.
From a technical perspective, the definition of path expressions
yields implicit joins in the generated database views, as described in
Section 3.6.2. These implicit joins can have an effect on the
cardinality of the selection result, as shown in Section 3.6.3. If
association definitions are based on fields that are calculated locally,
they can’t be used for defining path expressions locally. Related
restrictions will be discussed in Section 3.6.4.
3.6.1
Define Path Expressions
Let’s look at the associated CDS views from Listing 3.10 to
Listing 3.13. CDS view ZI_SalesOrderScheduleLine from Listing 3.10
documents the schedule line information of the sales order object. It
has an association _SalesOrderItem to the CDS view of sales order
item ZI_SalesOrderItem.
define view entity ZI_SalesOrderScheduleLine
as select from zsalesordersline
association [1..1] to ZI_SalesOrderItem as _SalesOrderItem
on $projection.SalesOrder
= _SalesOrderItem.SalesOrder
and $projection.SalesOrderItem = _SalesOrderItem.SalesOrderItem
{
key salesorder as SalesOrder,
key salesorderitem as SalesOrderItem,
key salesorderscheduleline as SalesOrderScheduleLine,
_SalesOrderItem
}
Listing 3.10
CDS View ZI_SalesOrderScheduleLine
CDS view ZI_SalesOrderItem from Listing 3.11 describes the sales
order item, which has an association _Product to the CDS view of
product ZI_Product.
define view entity ZI_SalesOrderItem
as select from zsalesorderitem
association [0..1] to ZI_Product as _Product
on $projection.Product = _Product.Product
{
key salesorder
as SalesOrder,
key salesorderitem as SalesOrderItem,
product
as Product,
_Product
}
Listing 3.11
CDS View ZI_SalesOrderItem
CDS view ZI_Product from Listing 3.12 has an association _Text to
CDS view ZI_ProductText that represents the product text.
define view entity ZI_Product
as select from zproduct
association [0..*] to ZI_ProductText as _Text
on $projection.Product = _Text.Product
{
key product as Product,
_Text
}
Listing 3.12
CDS View ZI_Product
Listing 3.13 contains the definition of CDS view ZI_ProductText.
define view entity ZI_ProductText
as select from zproducttext
{
key language
as Language,
key product
as Product,
product_name
as ProductName
}
Listing 3.13
CDS View ZI_ProductText
On top of these CDS views, projection CDS view
Z_ViewWithPathExpressions is defined according to Listing 3.14.
define view entity Z_ViewWithPathExpressions
as select from ZI_SalesOrderScheduleLine
{
key SalesOrder,
key SalesOrderItem,
key SalesOrderScheduleLine,
_SalesOrderItem,
_SalesOrderItem.Product as SalesOrderItemProduct,
_SalesOrderItem._Product,
_SalesOrderItem._Product.Product,
_SalesOrderItem._Product._Text
}
Listing 3.14
CDS View with Path Expressions
CDS view Z_ViewWithPathExpressions projects association
_SalesOrderItem of underlying base CDS view
ZI_SalesOrderScheduleLine from Listing 3.10 into its element list.
This projection also requires fields SalesOrder and SalesOrderItem to
be included in the projection list because these fields are used in the
on condition of the association definition.
Path expression _SalesOrderItem._Product establishes a relationship
to the CDS view of product master ZI_Product. By adding this path
expression, CDS view Z_ViewWithPathExpressions itself exposes
association _Product. As a prerequisite for exposing this association,
field Product must also be transferred from CDS view
ZI_SalesOrderItem to the projection list of CDS view
Z_ViewWithPathExpressions because this field is used in the on
condition of the underlying association definition. The integration of
this field is realized by path expression _SalesOrderItem.Product. To
avoid naming conflicts, corresponding field Product is assigned alias
name SalesOrderItemProduct.
In the same way, association _Text to the text view of product
ZI_ProductText is exposed by applying path expression
_SalesOrderItem._Product._Text. The exposed association requires
an extension of the projection list with field Product of CDS view
ZI_Product. This field is incorporated into the CDS view by using
path expression _SalesOrderItem._Product.Product.
3.6.2
Implicit Joins
If fields are added using path expressions, the on conditions defined
in the association definitions are implicitly converted into joins.
Accordingly, the two path expressions, _SalesOrderItem.Product and
_SalesOrderItem._Product.Product, as shown in Listing 3.14, result
in two effective joins.
[»] Implicit Creation of Joins
The mere definition and exposure of associations doesn’t lead to
additional joins. Joins are only created if the associations are used
in path expressions.
Listing 3.15 illustrates this by showing the create statement of the
database view generated from Listing 3.14.
CREATE OR REPLACE VIEW "Z_VIEWWITHPATHEXPRESSIONS" AS SELECT
"ZI_SALESORDERSCHEDULELINE"."MANDT" AS "MANDT",
"ZI_SALESORDERSCHEDULELINE"."SALESORDER",
"ZI_SALESORDERSCHEDULELINE"."SALESORDERITEM",
"ZI_SALESORDERSCHEDULELINE"."SALESORDERSCHEDULELINE",
"=A0"."PRODUCT" AS "SALESORDERITEMPRODUCT",
"=A1"."PRODUCT"
FROM (
"ZI_SALESORDERSCHEDULELINE" "ZI_SALESORDERSCHEDULELINE"
LEFT OUTER MANY TO ONE JOIN "ZI_SALESORDERITEM" "=A0" ON (
"ZI_SALESORDERSCHEDULELINE"."SALESORDER" = "=A0"."SALESORDER" AND
"ZI_SALESORDERSCHEDULELINE"."SALESORDERITEM" =
"=A0"."SALESORDERITEM" AND
"ZI_SALESORDERSCHEDULELINE"."MANDT" = "=A0"."MANDT"
)
) LEFT OUTER MANY TO ONE JOIN "ZI_PRODUCT" "=A1" ON (
"ZI_SALESORDERSCHEDULELINE"."MANDT" = "=A1"."MANDT" AND
"=A0"."PRODUCT" = "=A1"."PRODUCT"
)
WHERE "ZI_SALESORDERSCHEDULELINE"."MANDT" = SESSION_CONTEXT(
'CDS_CLIENT'
)
Listing 3.15
Create Statement for the Database View Generated from Listing 3.14
Associations _SalesOrderItem and _Product, which are used in the
path expressions of CDS view Z_ViewWithPathExpressions from
Listing 3.14, don’t appear in the definition of generated database
view Z_VIEWWITHPATHEXPRESSIONS. Instead, joins replace these
associations on the database level. These joins take over the
cardinality specifications (to one) as well as the on conditions of the
modeled associations. Addition to one signals to the database that
the respective join doesn’t affect the cardinality of the selection
result. Consequently, at runtime, the joins don’t necessarily have to
be executed if fields SalesOrderItemProduct and Product aren’t
requested within the data selection. However, these joins have a
negative effect on the static view complexity. Thus, they can hamper
the potential optimizations of the data selection processing on the
database level.
As an alternative to modeling CDS view Z_ViewWithPathExpressions
from Listing 3.14 with path expressions, its function can be
implemented without path expressions merely by using explicit joins,
as shown in Listing 3.16.
define view entity Z_ViewWithoutPathExpressions
as select from
ZI_SalesOrderScheduleLine as SL
left outer to one join ZI_SalesOrderItem
on ITEM.SalesOrder
= SL.SalesOrder
and ITEM.SalesOrderItem = SL.SalesOrderItem
left outer to one join ZI_Product as PROD
on PROD.Product = ITEM.Product
as ITEM
{
key SL.SalesOrder,
key SL.SalesOrderItem,
key SL.SalesOrderScheduleLine,
SL._SalesOrderItem,
ITEM.Product as SalesOrderItemProduct,
ITEM._Product,
PROD.Product,
PROD._Text
}
Listing 3.16
CDS View with Explicit Joins
[+] Avoid Denormalizations of CDS Models
You should avoid using (potentially implicit) joins of data sources
as much as possible to keep the static complexity of your CDS
view models as low as possible. This consideration is especially
important for your central, reused CDS views.
Instead of denormalizing CDS models explicitly or implicitly, you
should try to model the corresponding relations of the data
sources by using associations and making these associations
available to the consumers of your CDS views (possibly distributed
across several associated CDS views). The consumers can then
navigate by following the exposed associations, discover the entire
network of connected models, and only perform those joins that
are actually required.
By default, the associations used in a path expression to a field lead
to left outer joins. However, if an association or a path expression is
used within the from statement of a CDS view, it’s converted into an
inner join.
You can also use associations for defining the data sources of a
CDS view. In Listing 3.17, CDS view Z_ViewWithPathExprDataSources
uses association _SalesOrder from CDS view ZI_SalesOrderItem to
project elements of its target CDS view ZI_SalesOrder.
define view entity Z_ViewWithPathExprDataSources
as select from ZI_SalesOrderItem._SalesOrder
{
key SalesOrder,
_Item as _SalesOrderItem
}
Listing 3.17
CDS View Using a Path Expression as Its Data Source
In this case, the data source is constituted from an inner join of CDS
view ZI_SalesOrderItem with CDS view ZI_SalesOrder (see
Listing 3.18).
CREATE OR REPLACE VIEW "Z_VIEWWITHPATHEXPRDATASOURCES" AS SELECT
"=A0"."MANDT" AS "MANDT",
"ZI_SALESORDER"."SALESORDER"
FROM "ZI_SALESORDERITEM" "=A0" INNER JOIN "ZI_SALESORDER"
"ZI_SALESORDER" ON (
"=A0"."MANDT" = "ZI_SALESORDER"."MANDT" AND
"=A0"."SALESORDER" = "ZI_SALESORDER"."SALESORDER"
)
WHERE "=A0"."MANDT" = SESSION_CONTEXT(
'CDS_CLIENT'
)
Listing 3.18
Create Statement for the Database View Generated from Listing 3.17
3.6.3 Cardinality Changes Resulting from Path
Expressions
Path expressions can influence the number of selected data records
and thus influence the cardinality of the selection result by
introducing joins and filters. Both effects will be discussed in the
following examples.
CDS view Z_ViewWithPathExprsChngngCards, which is defined in
Listing 3.19, uses CDS view ZI_Product from Listing 3.12 as its data
source.
define view entity Z_ViewWithPathExprsChngngCards
as select from ZI_Product
{
key Product,
key _Text[*:inner].Language
}
Listing 3.19
CDS View with a Cardinality-Changing Path Expression
Path expression _Text[...].Language is used to enrich the projection
list of CDS view Z_ViewWithPathExprsChngngCards with language key
Language of target CDS view ZI_ProductText. Because this target
CDS view can potentially hold texts in multiple languages for a single
product record, the addition of the language key results in multiplying
records of CDS view ZI_Product in the results list of CDS view
Z_ViewWithPathExprsChngngCards. This is indicated by the cardinality
* in the illustrated path expression.
In the depicted case, an inner join is to be applied instead of a left
outer join. This is expressed by syntax element inner. Listing 3.20
illustrates the resulting create statement for the database view,
which is generated from CDS view Z_ViewWithPathExprsChngngCards
from Listing 3.19.
CREATE OR REPLACE VIEW "Z_VIEWWITHPATHEXPRSCHNGNGCARDS" AS SELECT
"ZI_PRODUCT"."MANDT" AS "MANDT",
"ZI_PRODUCT"."PRODUCT",
"=A0"."LANGUAGE"
FROM "ZI_PRODUCT" "ZI_PRODUCT" INNER JOIN "ZI_PRODUCTTEXT" "=A0" ON (
"ZI_PRODUCT"."MANDT" = "=A0"."MANDT" AND
"=A0"."PRODUCT" = "ZI_PRODUCT"."PRODUCT"
)
WHERE "ZI_PRODUCT"."MANDT" = SESSION_CONTEXT(
'CDS_CLIENT'
)
Listing 3.20
Create Statement of the Database View Generated from Listing 3.19
According to the semantics of the inner join, the resulting data
records are filtered, meaning that the inner join also influences the
cardinality of the selection result of CDS view
Z_ViewWithPathExprsChngngCards.
If you use associations in path expressions, you can supply filter
criteria for the associations used in these paths. Listing 3.21 shows
an example.
define view entity Z_ViewWithPathExprsWithFilters
as select from ZI_Product
{
key Product,
_Text[1:Language='E'].ProductName as ProductNameInEnglish,
_Text[1:Language='E'].Product
as ProductOfEnglishText
}
Listing 3.21
CDS View with Filter Restrictions in Path Expressions
CDS view Z_ViewWithPathExprsChngngCards uses association _Text of
CDS view ZI_Product from Listing 3.12 for incorporating fields
ProductName and Product of its target view ZI_ProductText from
Listing 3.13 into its projection list. The associated target data records
are restricted to those data records whose language key Language
corresponds to constant value E. This filter adds another restriction
to the on condition of association _Text, which already binds field
Product of the target CDS view. As a result, the key of target CDS
view ZI_ProductText, which is determined from fields Product and
Language, is fully specified. Accordingly, the maximum cardinality of
the join between the data records, which is implicitly introduced by
the corresponding path segments, decreases to 1. This cardinality
information is entered before the actual filter condition within the two
path expressions separated by a colon _Text[1:Language='E']....
Because the joins resulting from both path expressions are identical,
the database view, which is generated from CDS view
Z_ViewWithPathExprsChngngCards, should ideally contain a single join
only. In the given example, the joins are condensed accordingly.
There is only one join in the create statement, as shown in
Listing 3.22.
CREATE OR REPLACE VIEW "Z_VIEWWITHPATHEXPRSWITHFILTERS" AS SELECT
"ZI_PRODUCT"."MANDT" AS "MANDT",
"ZI_PRODUCT"."PRODUCT",
"=A0"."PRODUCTNAME" AS "PRODUCTNAMEINENGLISH",
"=A0"."PRODUCT" AS "PRODUCTOFENGLISHTEXT"
FROM "ZI_PRODUCT" "ZI_PRODUCT" LEFT OUTER MANY TO ONE JOIN
"ZI_PRODUCTTEXT" "=A0" ON (
"=A0"."PRODUCT" = "ZI_PRODUCT"."PRODUCT" AND
"=A0"."LANGUAGE" = N'E' AND
"ZI_PRODUCT"."MANDT" = "=A0"."MANDT"
)
WHERE "ZI_PRODUCT"."MANDT" = SESSION_CONTEXT(
'CDS_CLIENT'
)
Listing 3.22
Create Statement for a Database View Generated from Listing 3.21
The association definition can be equipped with a default filter that is
applied if you leverage the association in path expressions without
redefining its filter. If you explicitly define a filter, the default filter is
ignored.
Listing 3.23 shows an example.
define view entity Z_ViewWithAscsWithDfltFilters
as select from ZI_Product
association [0..*] to ZI_ProductText as _TextWithDefaultFilter
on $projection.Product = _TextWithDefaultFilter.Product
with default filter _TextWithDefaultFilter.Language = 'E'
association [0..*] to ZI_ProductText as _Text
on $projection.Product = _Text.Product
association [0..1] to ZI_ProductText as _TextInEnglish
on $projection.Product = _TextInEnglish.Product
and _TextInEnglish.Language = 'E'
{
Product,
_TextWithDefaultFilter.ProductName as ProductNameInEnglish,
_Text[1:Language='E'].ProductName as ProductNameInEnglish2,
_TextInEnglish.ProductName as ProductNameInEnglish3,
_TextWithDefaultFilter[Language='D'].ProductName
as ProductNameInGerman,
_TextWithDefaultFilter[*:left outer].ProductName as ProductName,
_TextWithDefaultFilter,
_Text,
_TextInEnglish
}
Listing 3.23
Association with Default Filter in CDS View
Association _TextWithDefaultFilter defines default filter E for field
Language of target CDS view ZI_ProductText. Consequently, when
using path expression _TextWithDefaultFilter.ProductName, the
English name is fetched. This corresponds to the result of path
expressions _Text[1:Language='E'].ProductName and
_TextInEnglish.ProductName, wherein the same filter is explicitly
specified within the path expression, respectively, by the on condition
of association _TextInEnglish.
When redefining the applied default filter for field Language as D via
path expression
_TextWithDefaultFilter[Language='D'].ProductName, the German
name is fetched; that is, the default filter is ignored.
When redefining the filter as in path expression
_TextWithDefaultFilter[*:left outer].ProductName, the names in all
languages are fetched.
Because the default filter isn’t always applied, the maximum
cardinality of association _TextWithDefaultFilter is specified as *
and not as 1 in Listing 3.23.
[ ! ] Default Filters of Associations
The default filters of associations are only applied if no other filters
are specified when using the associations. Typically, filters in path
expressions can only reduce the cardinalities of the association
targets. In contrast, overruled default filters may even increase the
cardinality of the association targets.
Specifically, if the consumers of an association aren’t aware of its
specified defaulting logic, they can’t predict the associated records
and thus the effective cardinality of such an association.
If in doubt, you should define an additional association that
explicitly incorporates the desired filter criteria in its on condition
instead of leveraging the default filter logic.
3.6.4
Restrictions for Defining Path Expressions
If associations are both exposed and used for implementing the logic
of their defining views, their on conditions can’t reference fields,
which are calculated locally in the respective CDS views.
CDS view Z_ViewWithAscsWithCalcFields in Listing 3.24 illustrates
this restriction.
define view entity Z_ViewWithAscsWithCalcFields
as select from ZI_SalesOrderItem
association [0..1] to ZI_Product as _Product
on ZI_SalesOrderItem.Product = _Product.Product
association [0..1] to ZI_Product as _ProductWithAlias
on $projection.ProductWithAlias = _ProductWithAlias.Product
association [0..1] to ZI_Product as _ProductWithCast
on $projection.ProductWithCast = _ProductWithCast.Product
association [0..1] to ZI_Product as _ProductWithCastPreservingType
on $projection.ProductWithCastPreservingType =
_ProductWithCastPreservingType.Product
{
key SalesOrder,
key SalesOrderItem,
Product,
_Product,
_Product.ProductType,
Product
as ProductWithAlias,
_ProductWithAlias,
_ProductWithAlias.ProductType
as ProductTypeWithAlias,
cast (Product as matnr)
as ProductWithCast,
_ProductWithCast,
cast (Product as matnr preserving type)
as ProductWithCastPreservingType,
_ProductWithCastPreservingType,
_ProductWithCastPreservingType.ProductType
as ProdTypeWithCastPreservingType
}
Listing 3.24
CDS View with Associations Whose Definitions Use Calculated Fields
Its association _Product uses field Product of underlying data source
ZI_SalesOrderItem in its on condition. This association can be both
exposed and used for defining path expressions, such as
_Product.ProductType, within CDS view
Z_ViewWithAscsWithCalcFields.
The same holds true for association _ProductWithAlias, which
references field ProductWithAlias in its definition. This field
represents an alias of field Product of underlying data source
ZI_SalesOrderItem.
Field ProductWithCast is calculated from base field Product by
applying a cast operation. It’s used for defining association
_ProductWithCast. This association can also be exposed. However,
within the definition of CDS view Z_ViewWithAscsWithCalcFields, it’s
not possible to use a path expression such as
_ProductWithCast.ProductType. Such a path expression is only
allowed in CDS views that select from the current CDS view.
Field ProductWithCastPreservingType retains its technical type,
although it’s also cast onto data element MATNR. This is expressed by
addition preserving type. If such a field is included in the on condition
of an association, the association can also be used for defining path
expressions in its defining CDS view, such as
_ProductWithCastPreservingType.ProductType.
3.7
Use Associations in ABAP Code
Within your ABAP application, you can leverage exposed
associations of a CDS view when selecting data from this CDS view.
As in CDS, you can specify path expressions with associations in
ABAP code. These path expressions can access the fields of the
respective target CDS views. Unlike in CDS views, however, you
can’t include any association as an element to the results list of your
select statement.
If you want to address an association in your ABAP code, you must
prefix its name with a backslash (\). The association target field is
delimited by a hyphen (-) from the association name. For association
chains, the individual associations are concatenated with a
preceding backslash (\). Filter conditions can be specified in square
brackets ([…]).
Listing 3.25 illustrates an example.
SELECT \_salesorder-salesordertype,
\_salesorderitem\_product\_text[ (1) inner :
where language = 'E' ]-productname
FROM zi_salesorderscheduleline
WHERE \_salesorderitem\_product-producttype EQ 'FERT'
INTO TABLE @DATA(lt_result).
Listing 3.25
Data Selection in ABAP Using Path Expressions
In the selection statement shown in this example, associated field
SalesOrderType of sales order CDS view ZI_SalesOrder is added to
results list lt_result from the CDS view of sales order schedule line
ZI_SalesOrderScheduleLine. In addition, field ProductName from CDS
view ZI_ProductText is included in the results list. The applied path
restriction [ (1) inner : where language = 'E' ] results in selecting
only those data records according to the inner join semantics for
which there is an English text (Language = E). The cardinality 1, which
is entered in parentheses, signals that there is no multiplication
effect of the number of data records in the results list by applying the
path expression. The applied where condition ensures that only data
records from the sales order schedule line are taken into account to
whose item a product of type (ProductType) FERT is assigned.
3.8
Summary
In this chapter, you were introduced to associations, which allow you
to model the relationship between CDS entities and their data
records. They can conveniently be used by consumers of the CDS
views for specifying data selections across multiple data sources.
This helps reduce the need for statically manifesting joins within the
CDS view definitions, which in turn may help to improve CDS model
performance.
In the next chapter, we’ll discuss how you can enrich the CDS data
models with additional metadata via CDS annotations.
4
Annotations
In this chapter, you’ll learn about the fundamentals of the core
data services (CDS) annotations and their possible usages.
We also provide you with some particularities of the CDS
annotations that you should take into account when dealing
with them.
CDS annotations enrich the plain SQL logic of CDS data models with
additional metadata that is interpreted by the ABAP runtime
environment and other consumers of the CDS models. These
consumers may be developers who want to understand the CDS
models and infrastructure components that execute the CDS
models. For example, the analytic engine evaluates CDS
annotations for deriving the applicable aggregation behavior of key
figures.
CDS annotations are an integral part of CDS model definitions. They
capture information that is immediately available to all potential
consumers of CDS data models. CDS annotations are used to
incorporate and replace proprietary metadata of different frameworks
with a central and harmonized description of the data model
properties. Adding the corresponding information on the data model
level will avoid redundancies and inconsistencies in the modeling
and implementation of the applications.
Section 4.1 provides an overview of the annotation definitions, which
include the names and technical properties of CDS annotations. In
Section 4.2, you get an explanation of the possible usages of CDS
annotations. These cover documentary aspects and, in particular,
the control of generic framework functions.
In Section 4.3, you’ll then learn about the propagation logic of
annotations, which allows you to reuse model information that is
centrally defined in dependent CDS models. At the same time, the
successful application of the propagation logic requires a consistent
modeling of the entire CDS view stack, which is illustrated by two
examples.
Section 4.4 explores CDS metadata extensions, which allow you to
branch out annotations from your CDS model definitions. This makes
the SQL logic of the CDS views easier to read. In addition, you can
use CDS metadata extensions to adjust annotations of CDS views
that are enabled for CDS metadata extensions—even for CDS views
delivered by SAP. Although you don’t need to apply technical
modifications to the delivered artifacts, you still have to check the
impact of the corresponding metadata extensions carefully.
Finally, in Section 4.5, you’ll learn how active annotations are
constructed. Active annotations represent the effective annotations
from the consumer’s point of view.
4.1
Annotation Definitions
Annotations are a basic concept of CDS modeling. Even though
annotations are an integral part of the CDS syntax, their concrete
realizations are modeled as annotation definition objects. Annotation
definitions have the technical object type DDLA. They are defined by
SAP and shipped like the CDS models themselves.
Before we introduce you to the technical details, we want to first
present you an overview about the annotation syntax in
Section 4.1.1. In this context, we show you the syntax applicable
within both the annotation definitions and the CDS models. You’ll
also learn how to access annotation definitions.
Afterward, we’ll discuss the following properties, which are specified
with the technical definition of a CDS annotation:
Annotation names (Section 4.1.2)
Annotation types and values (Section 4.1.3)
Enumeration values (Section 4.1.4)
Annotation default values (Section 4.1.5)
Annotation scopes (Section 4.1.6)
4.1.1
Syntax Overview
To get an overview of the annotation syntax, you can take a look at
annotation definitions in the ABAP Development Tools (ADT) editor.
To do this, open the corresponding development object by using the
Open ABAP Development Object dialog, which you can launch, for
example, by pressing (Ctrl)+(Shift)+(A).
In the dialog box, enter the term “Semantics”, and select the object
that is indicated by the
icon, as illustrated in Figure 4.1, if you
want to display the annotations of the corresponding domain. The
concept of domains is discussed in conjunction with the annotation
names in Section 4.1.2.
Figure 4.1
Open Annotation Definition of Domain Semantics
After confirming the selection with OK, the annotation definition is
displayed, as shown in Figure 4.2.
Figure 4.2
Annotation Definition of Domain Semantics
As illustrated by Figure 4.2, the annotation definitions comprise a set
of technical properties associated with the annotations. These
properties are described in detail in the following sections. At this
point, we want to focus on a few important aspects, which are
illustrated in Listing 4.1.
...
@Scope:[#ELEMENT,#PARAMETER]
annotation Semantics
{
...
systemDate
{
createdAt: Boolean default true;
...
};
...
@Scope:[#ELEMENT]
quantity
{
unitOfMeasure: ElementRef;
};
...
}
Listing 4.1
Extract from the Annotation Definition of Domain Semantics
This example shows an extract from the annotation definition of
domain Semantics. In the illustrated annotation definition, you find an
annotation named createdAt or the fully qualified name (i.e., a name
that provides the complete path from the domain to the current
annotation name) Semantics.systemDate.createdAt and another
structured annotation with the fully qualified name
Semantics.quantity.unitOfMeasure.
The first annotation, Semantics.systemDate.createdAt, can be used
to mark CDS elements and parameters that hold the creation date of
a data record. Its admissible usage scope @Scope :
[#ELEMENT,#PARAMETER] is inherited from superordinated domain
Semantics. According to its further specification, this annotation has
type Boolean and is equipped with default value true.
The second annotation, Semantics.quantity.unitOfMeasure,
establishes a reference between the annotated quantity field and its
unit of measure. This annotation has type ElementRef, which allows
for referencing CDS elements. Its scope is restricted to ELEMENT, as
specified by superordinated annotation Semantics.quantity.
Until now, we’ve dealt with the specification of the annotations
themselves. When applying annotations, for example, within the
CDS model definitions, they have to begin with special character @.
Annotations must be located before the name of the annotated entity
or its component. The end of an annotation isn’t marked specifically.
In Listing 4.2, for example, you can find the two annotations
described earlier as metadata enrichment of two CDS view fields.
Quantity field OrderQuantity, which is annotated with
@Semantics.quantity.unitOfMeasure: 'OrderQuantityUnit', refers to
unit field OrderQuantityUnit. The CreationDate field is classified as
the date of creation by annotation
@Semantics.systemDate.createdAt:true.
define view entity ZI_SalesOrderItem as select from ...
{
...
@Semantics.quantity.unitOfMeasure:'OrderQuantityUnit'
OrderQuantity,
OrderQuantityUnit,
@Semantics.systemDate.createdAt:true
CreationDate,
...
}
Listing 4.2
CDS View with Annotated Fields
The CDS editor supports you in entering annotations into your CDS
models. It offers a code completion function along with some built-in
documentation of the annotations. You can request this support
functionality by pressing (Shift)+(Space) after placing the cursor at
the position where the annotation or parts thereof will be inserted
(see Figure 4.3).
Figure 4.3
Code Completion for Annotation @Semantics.quantity
In the displayed popup, you can select the leaf annotation for
displaying its documentation. By double-clicking on the leaf
annotation of choice or by pressing (Shift)+(Enter), it’s inserted
into the CDS entity definition.
4.1.2
Annotation Names
Annotation names are structured hierarchically. Starting from a
domain, for example, Semantics, which groups semantically related
CDS annotations and simultaneously defines the root or main
annotation name, the fully qualified annotation name is successively
composed from several potential hierarchy levels of intermediate
components (subannotations) ending with a leaf element. The
individual components of the fully qualified annotation name are
separated by periods.
In Listing 4.1, shown previously, fully qualified annotation name
Semantics.quantity.unitOfMeasure is built stepwise, starting from
domain Semantics followed by intermediate element quantity up to
closing leaf element unitOfMeasure. The same holds true for fully
qualified annotation name Semantics.systemDate.createdAt.
Intermediate structuring elements can branch, forming an annotation
name tree. The leaves of these trees are used as the actual carriers
of annotation values, which are relevant for the consumers of the
CDS models.
Table 4.1 shows an overview of the most important annotation
domains. Additional information about the annotations that are used
in this book is provided in Appendix A. For a comprehensive
overview, refer to the ABAP documentation at http://sprs.co/v529404.
Domain
Description
ABAPCatalog
Controls the ABAP runtime environment as well
as the ABAP Data Dictionary and defines
extensibility options
AccessControl
Documents and controls the authorization
checks for CDS models
Aggregation
Defines elements that can be used as
aggregating key figures (successor of
DefaultAggregation)
Analytics
Defines analytical data models and applications
AnalyticsDetails
Defines the details of an analytical query, such
as its standard layout and the exception
aggregations to be applied
Consumption
Provides hints for CDS model consumers that
are evaluated in particular by implementation
frameworks
EndUserText
Defines translatable label texts
Environment
Controls the defaulting logic of parameters with
system variables as well as special SQL
operations
Event
Defines properties of event signatures
Domain
Description
Hierarchy
Defines hierarchical relationships
Metadata
Controls the annotation enrichments of a CDS
view by enabling CDS metadata extensions
and the propagation of element annotations
ObjectModel
Describes the basic structural properties of the
data models
OData
Defines the exposure of CDS models in OData
services
Search
Controls the search functionality
Semantics
Describes the basic semantics of types,
elements, and parameters
UI
Defines a semantic user interface (UI)
representation that is independent of the
specific UI implementation technology
VDM
Classifies CDS models in the virtual data model
(VDM)
Table 4.1
Selected Annotation Domains
If two annotation names start with the same components, these
annotations are grouped by curly brackets {...} in the annotation
definitions. This grouping option is not only applicable to the
annotation definitions but also can be used when annotating CDS
models.
Listing 4.3 and Listing 4.4 illustrate the possible grouping of
annotations in CDS views. Both listings display alternative modeling
approaches that have the same effect. The CDS view definition from
Listing 4.3 contains isolated annotations that are fully qualified.
...
@ObjectModel.dataCategory: #TEXT
@ObjectModel.representativeKey: 'Product'
define view entity ZI_ProductText
...
Listing 4.3
CDS View with Separated, Fully Qualified Annotations
The CDS view definition from Listing 4.4 uses enclosing domain
ObjectModel for grouping the same annotations.
...
@ObjectModel: {
dataCategory: #TEXT,
representativeKey: 'Product'
}
define view entity ZI_ProductText
...
Listing 4.4
CDS View with Grouped Annotations
A single annotation may have multiple values or subannotations
assigned to it. The corresponding array-like annotation elements and
values are defined with the key words array of in the annotation
definitions. This is illustrated for domain ObjectModel in Listing 4.5.
annotation ObjectModel {
...
alternativeKey : array of
{
id : String(30);
uniqueness : String(30) enum { UNIQUE;
UNIQUE_IF_NOT_INITIAL; };
element : array of ElementRef;
};
...
};
Listing 4.5
Extract from the Annotation Definition of Domain ObjectModel
In this example, the definition of alternative key
ObjectModel.alternativeKey represents an array whose structure is
composed of three elements: id, uniqueness, and element. These
elements are either scalar types (id and uniqueness) or are specified
as an array of a scalar type (element), as arrays can be nested.
When using such annotations in CDS models, the assigned values
and subannotations have to be entered in square brackets [...], as
shown in Listing 4.6.
...
@ObjectModel.alternativeKey: [
{ id : 'ObjectInternalID',
element : [ 'ObjectInternalID' ] },
{ id : 'ObjectExternalKey',
element : [ 'ObjectID',
'ObjectVersion' ] }
]
define view entity Z_ViewWithAlternativeKeys
as select distinct from t000
{
key hextobin('00000000000000000000000000000001')
as ObjectUUID,
1
as ObjectInternalID,
1
as ObjectID,
1
as ObjectVersion
}
Listing 4.6
CDS View Using Array-Like Annotations and Values
Annotation @ObjectModel.alternativeKey defines a list of alternative
keys for modeled primary key ObjectUUID of CDS view
Z_ViewWithAlternativeKeys. The first entry in this list defines an
alternative key with identifier ObjectInternalID, which consists of an
identically named element. The second entry in this list specifies a
key with identifier ObjectExternalKey, which is composed of
elements ObjectID and ObjectVersion.
[»] Multiple Assignments of Annotations
Outside of array-like contexts, the same annotation can’t be used
multiple times for annotating the same entity or entity component.
4.1.3
Annotation Types and Values
CDS annotation types can represent a scalar value, a structure, or
an array. This type information is captured in the annotation
definitions as discussed previously in Section 4.1.2. When
annotating a CDS model, consistent typing is enforced by the CDS
syntax checks.
The annotation values that are relevant for CDS model consumers
are always either based on scalar types or on arrays of scalar types.
Annotation values are only permitted if they match the specified
types of the CDS annotation.
Depending on the actual type assignment, admissible values can be,
among others, Boolean values, for example, for annotation
Semantics.systemDate.createdAt from Listing 4.1; string values, for
example, for annotation ObjectModel.alternativeKey.id from
Listing 4.5; or references to CDS elements, for example, for
annotation ObjectModel.alternativeKey.element from Listing 4.5.
4.1.4
Enumeration Values
Enumeration values are used to restrict the range of admissible
scalar values that is technically defined by the type of a CDS
annotation. The constants captured in the enumeration lists have a
semantic meaning, which can be interpreted by the ABAP runtime
environment or by frameworks that execute or use the CDS models.
Within the annotation definitions, such enumeration lists of
admissible values are specified by key word enum. Listing 4.7 shows
an example.
annotation ObjectModel {
...
dataCategory : String(30) enum { TEXT;
HIERARCHY;
VALUE_HELP; };
...
};
Listing 4.7
Annotation Definition with an Enumeration
The illustrated enumeration defines that annotation
ObjectModel.dataCategory can only have one of these values: TEXT,
HIERARCHY, or VALUE_HELP.
If an enumeration value is to be used for specifying an annotation in
a CDS model, this value can be addressed directly by the special
character #, as shown in Listing 4.8.
...
@ObjectModel.dataCategory:#TEXT
define view entity ZI_ProductText as select ...
Listing 4.8
CDS View with an Annotation Enumeration Value
In this example, the data category of CDS view ZI_ProductText is
defined as TEXT by referencing (#) the corresponding enumeration
value of annotation ObjectModel.dataCategory.
[»] Annotations with Null Value
Regardless of the type assignment and the potential defined
enumeration lists, you can always assign an element or type
annotation the value null instead of a scalar or array value (unless
the value is specified within an array). An annotation with this
valuation is interpreted as if it was completely missing in the CDS
model. In this context, it also suppresses an annotation value
propagated to the annotated element or type (Section 4.3.2).
4.1.5
Annotation Default Values
CDS annotation definitions can specify default values. Such a
default value applies if the annotation is used without explicit
valuation.
[ ! ] Avoid Modeling Errors
To avoid misunderstandings and errors, you should always specify
annotation values explicitly. Don’t rely on any implicitly set default
values.
For example, in accordance with Listing 4.1 shown earlier,
annotation Semantics.systemDate.createdAt has default value true.
As a result, the annotations in Listing 4.9 and Listing 4.10 have the
same effect. In both cases, field CreationDate is marked as the field
holding the date of creation.
...
@Semantics.systemDate.createdAt:true
CreationDate,
...
Listing 4.9
Using an Annotation with Explicit Value Assignment
If the concrete annotation value is missing, as in Listing 4.10, both
the developer and the consumer of the CDS model must know the
underlying annotation definition to apply the annotation correctly or
to interpret it correctly.
...
@Semantics.systemDate.createdAt
CreationDate,
...
Listing 4.10
Using an Annotation with an Implicit Transfer of Its Default Value
[ ! ] Usage of Default Values
The default value of an annotation, which is specified in its
definition, doesn’t necessarily correspond to the value that is
applied if the annotation is missing completely in a CDS model.
For example, the default value of annotation
Semantics.systemDate.createdAt is true, classifying the annotated
field as a creation date. However, fields that don’t carry this
annotation aren’t interpreted as creation dates. Instead, they are
interpreted as if they were annotated with value false.
4.1.6
Annotation Scopes
Within the CDS model, annotations can appear at the header level to
provide information for the annotated CDS entities as a whole or at
the individual component level (parameters or projected elements).
The scope specification defines where a given annotation is
admissible to be used within a CDS model. In Listing 4.1 earlier, for
example, the scope of annotation Semantics.quantity is defined as
ELEMENT. As a result, this annotation can be used for annotating CDS
elements, that is, fields and associations. It can’t be used for
annotating parameters or entire CDS models.
[»] Annotations for Associations
Associations can only be annotated within the projection list; that
is, only exposed annotations can be annotated. Annotations can’t
be added to the association definitions.
Annotations such as Semantics.systemDate.createdAt and
Semantics.quantity.unitOfMeasure, which aren’t explicitly specified
with their own scope definitions, inherit their scope settings from their
parent annotations Semantics.systemDate, respectively, Semantics
and Semantics.quantity.
[»] Scope ELEMENT
There is no dedicated scope for fields of CDS models. Instead,
fields are always handled along with associations by scope
ELEMENT. As a result, annotations such as Semantics.quantity… can
also be used formally for annotating associations. However, this
type of usage isn’t necessarily supported by all frameworks, and,
in many cases, this type of usage is also not intended. For
example, you can neither annotate nor refer to an association via
annotation Semantics.quantity.unitOfMeasure.
Defining annotation scopes requires gathering answers to the
following questions:
Can the annotation be used more than once or at most once in a
CDS model?
Will it be possible to use the annotation in CDS extended views?
Will, or can, the annotation be propagated throughout the CDS
view stack?
Depending on the answers to these questions, the scope of an
annotation may be restricted to the header or to the component
level. The scope of the annotation that is finally specified in the
annotation definition may also represent a compromise that valuates
different requirements against each other.
4.2
Effects of Annotations
The metadata captured in the CDS annotations may control both the
generation of artifacts at design time as well as the application of the
modeled CDS logic at runtime. Furthermore, CDS annotations may
also aim at documenting certain properties of the CDS models.
These documented properties can be used, for example, for
identifying relevant CDS models as well as for consistency checks of
the same.
Let’s look at sample CDS view Z_ViewWithODataExposure in
Listing 4.11, which is equipped with several annotations, which we’ll
discuss in this section.
@ObjectModel.usageType:{ serviceQuality : #B,
sizeCategory
: #XL,
dataClass
: #TRANSACTIONAL }
@OData.entityType.name: 'ViewWithODataExposure_Type'
define view entity Z_ViewWithODataExposure
with parameters
@Consumption.hidden: true
@Environment.systemField: #SYSTEM_DATE
P_CreationDate : sydatum
as select from ZI_SalesOrderItem
{
key SalesOrder,
key SalesOrderItem,
CreationDate
} where CreationDate = $parameters.P_CreationDate
Listing 4.11
Annotated CDS View
As mentioned, frameworks may leverage the information captured in
annotations for generating their design-time artifacts. Examples of
such artifacts are OData services, which are created by the Service
Adaptation Definition Language (SADL) infrastructure from CDS
models. If CDS view Z_ViewWithODataExposure is exposed as an
entity in an OData service, the name of the OData entity type is
derived from annotation @OData.entityType.name, as shown in
Listing 4.12.
...
<EntityType ... Name="ViewWithODataExposure_Type">
<Key>
<PropertyRef Name="SalesOrder"/>
<PropertyRef Name="SalesOrderItem"/>
</Key>
<Property Name="SalesOrder" .../>
<Property Name="SalesOrderItem" .../>
<Property Name="CreationDate" .../>
</EntityType>
...
Listing 4.12
OData Entity Type ViewWithODataExposure_Type
CDS annotations are also interpreted by the ABAP runtime
environment and can influence the execution of a data selection. For
example, parameters of CDS models can be mapped by CDS
annotations onto runtime variables. The ABAP runtime environment
automatically assigns the respective system fields to the parameters
that are annotated in this way if they aren’t explicitly supplied in the
context of a data selection via the ABAP SQL interface (see
Chapter 2, Section 2.16). In our example from Listing 4.11, we
leverage this mechanism for supplying parameter P_CreationDate
with current system date @Environment.systemField:#SYSTEM_DATE.
The parameter itself won’t be exposed to service consumers, so it’s
annotated with @Consumption.hidden:true.
Besides the CDS annotations that directly or indirectly affect the
runtime execution of a data selection, there are also CDS
annotations that merely serve documentation purposes. These
annotations support the selection of suitable CDS models as well as
analyses and checks of the same. For example, you can use
annotated performance classifications @ObjectModel.usageType… for
selecting a CDS model that meets the requirements of your
application regarding data throughput and processing efficiency.
Let’s look at the corresponding CDS view annotations from
Listing 4.11. Service quality @ObjectModel.usageType.serviceQuality
describes which performance characteristics a CDS model has.
Quality value B means that the CDS model can in principle be used
within the logic of transactional applications. Specified size category
@ObjectModel.usageType.sizeCategory answers the question of how
many data records are typically processed within a selection request.
In this context, it’s not important how many data records the
selection result contains but rather how many data records are
effectively used for its calculation. The maintained size category
provides a hint for estimating the resource requirements of a
selection request on the SAP HANA database. In the given example,
value XL means that the processing of less than 100 million data
records is expected.
Data category @ObjectModel.usageType.dataClass may be used as
an indicator for consumers to define suitable caching strategies for
the selected data. Whereas metadata usually only changes due to
corrections or upgrades of a system and might therefore be
appropriate for buffering, transactional data (TRANSACTIONAL) has to
be regarded as volatile. In other words, this data can be subject to
frequent and significant changes over time. Consequently, it has to
be expected that the corresponding data, if buffered, will become
outdated after a short time.
4.3
Propagation Logic for Annotations
CDS models can be defined one on top of another. This applies to CDS simple types,
which we’ll discuss in Section 4.3.1, as well as to CDS view models, which we’ll
discuss in Section 4.3.2. By default, annotations of underlying CDS models are
transferred to the superordinated CDS models. We’ll also explain how consistency is
ensured when applying the propagation logic in Section 4.3.3.
4.3.1
Propagation Logic within Simple Type Hierarchies
As outlined in Chapter 2, Section 2.6, simple types can be defined based on
elementary ABAP types, data elements, and other simple types, so both data elements
and simple types can act as carriers of annotations. These annotations are
automatically transferred to the simple type definition in use where you can redefine
and/or enrich the annotations.
Let’s have a look at the two dependent simple type definitions from Listing 4.13 and
Listing 4.14. Simple type ZBT_UnitOfMeasure from Listing 4.13 is based on data element
MEINS. From the CDS perspective, several annotations are derived from the definition of
this data element. These annotations comprise, among others, label texts
@EndUserText… as well as conversion exit @AbapCatalog.typeSpec.conversionExit
:'CUNIT', which is technically assigned to the equally named ABAP Data Dictionary
domain of the data element.
@EndUserText.label:'Unit of Measure'
define type ZBT_UnitOfMeasure : MEINS;
Listing 4.13
Simple Type ZBT_UnitOfMeasure
Table 4.2 shows an extract of the resulting active annotations of simple type
ZBT_UnitOfMeasure.
Annotation
Artifact
MEINS ZBT_UnitOfMeasure ZBT_UnitOfMeasureB
@EndUserText. label
Base
Unit of Measure
Unit of
Measure
Unit of Measure
@AbapCatalog.
typeSpec.conversionExit
CUNIT
CUNIT
null
@ObjectModel.
sapObjectNodeTypeReference
-
-
UnitOfMeasure
Table 4.2
Extract from the Active Annotations of the CDS Simple Types from Listing 4.13 and Listing 4.14
Simple type ZBT_UnitOfMeasureB from Listing 4.14 leverages simple type
ZBT_UnitOfMeasure from Listing 4.13 as its definition source and also takes over its
active annotations. It overwrites the inherited assignment of the previously discussed
conversion exit with a null value. In addition, it introduces annotation
@ObjectModel.sapObjectNodeTypeReference, which establishes a reference to SAP
object node type UnitOfMeasure. The resulting active annotations of simple type
ZBT_UnitOfMeasureB are shown in Table 4.2 too.
@AbapCatalog.typeSpec.conversionExit: null
@ObjectModel.sapObjectNodeTypeReference: 'UnitOfMeasure'
define type ZBT_UnitOfMeasureB : ZBT_UnitOfMeasure;
Listing 4.14
Simple Type ZBT_UnitOfMeasureB
You can get an overview of the active annotations of a simple type by executing its
context menu function Open With • Active Annotations. This launches the Active
Annotations view, which is depicted for simple type ZBT_UnitOfMeasureB in Figure 4.4.
[»] Reuse of Annotations
In principle, you can use simple types for defining annotations centrally once and
reusing them for any CDS field and parameter typed accordingly. However, you
should only include those annotations in the simple type definitions that are
universally applicable to all the fields and parameters that will be typed with the
corresponding simple types.
Figure 4.4
4.3.2
Active Annotations of Simple Type ZBT_UnitOfMeasureB
Propagation Logic of Element Annotations
CDS view models can be defined one on top of another. If CDS views on a higher level
of this view stack project fields and associations of their underlying models, their
element annotations are also transferred by default. Let’s walk through the following
examples that demonstrate the principles and consistency aspects behind propagation
logic.
[»] Propagation Logic of Element Annotations
Within a CDS view stack, the propagation logic only considers element annotations.
CDS annotations of parameters as well as CDS annotations that are defined at the
header level of the CDS models are excluded from the propagation logic.
Using the propagation logic, element annotations of a data source are automatically
transferred to the CDS view that is based on it. The annotations that are defined locally
in the CDS view overlay the propagated element annotations that are inherited from its
data sources. Thus, the resulting effective element annotations comprise the locally
defined annotations plus the element annotations of the underlying models, which
aren’t redefined locally. This mechanism corresponds to the propagation logic of
annotations within simple type hierarchies discussed beforehand.
The propagation logic can be suppressed partially or completely when using certain
modeling features in the local CDS view definitions:
If a field of the projection list is changed by a calculation such as a cast operation,
this field doesn’t inherit any element annotations from its data source. Instead, it
inherits annotations from its assigned data type only. Note that a mere renaming of
fields and associations doesn’t change their inherited annotations.
If an element annotation is valued with null, this value hides the corresponding
annotation value of the underlying data source. Value null is propagated as a
regular value to the dependent CDS views too. When applying this value, the
valuated annotation is considered undefined, that is, as if the annotation was
completely missing.
Array-like annotations are always evaluated as a coherent, integral group. Even if
only a single entry of such a group is annotated locally, this local definition overlays
all entries of the corresponding annotation of the underlying data source.
The propagation logic from underlying data sources is disabled completely when
using header annotation @Metadata.ignorePropagatedAnnotations:true. Note that
annotations originating from typing data elements or simple types remain active.
The following example, which comprises the models from Listing 4.15 to Listing 4.19,
will clarify these aspects. It consists of a database table and several CDS views that
are based on this table and on one another. Figure 4.5 gives you an overview of the
resulting CDS view stack.
Figure 4.5
Correlation between the Database Table and the CDS Views from Listing 4.15 to Listing 4.19
Listing 4.15 shows an extract of underlying database table ZSALESORDERITEM.
...
define table zsalesorderitem {
...
@Semantics.quantity.unitOfMeasure:
'zsalesorderitem.orderquantityunit'
orderquantity
: kwmeng;
orderquantityunit : vrkme;
creationdate
: erdat;
...
}
Listing 4.15
Extract from the Definition of Database Table ZSALESORDERITEM
CDS view Z_ViewWithAnnotationsA from Listing 4.16 uses database table
ZSALESORDERITEM as its data source.
define view entity Z_ViewWithAnnotationsA
as select from zsalesorderitem
{
@Semantics.quantity.unitOfMeasure: 'OrderQuantityUnit'
orderquantity
as OrderQuantity,
orderquantityunit
as OrderQuantityUnit,
@Semantics.systemDate.createdAt: true
creationdate
as CreationDate
}
Listing 4.16
CDS View Z_ViewWithAnnotationsA
This example selects fields OrderQuantity, OrderQuantityUnit, and CreationDate from
database table ZSALESORDERITEM. Data elements KWMENG, VRKME, and ERDAT, which are
assigned to these fields, respectively, provide descriptive, language-dependent label
texts. These texts are included as values in CDS annotation @EndUserText.label of the
corresponding CDS view fields.
The reference between fields OrderQuantity and OrderQuantityUnit is captured by the
CDS annotation @Semantics.quantity.unitOfMeasure:'OrderQuantityUnit'.
Within the definition of CDS view Z_ViewWithAnnotationsA, field CreationDate is also
annotated with @Semantics.systemDate.createdAt :true. Table 4.3 shows you an extract
of the resulting active annotations of these fields.
Field
Annotation
Name
Annotation Value
EndUserText.
label
Order
Quantity
Quantity
–
–
Semantics.
quantity.
unitOfMeasure
OrderQuantityUnit
OrderQuantityUnit
–
–
OrderQuantityUnit EndUserText.
label
Sales
unit
Unit
Unit
Sales unit
CreationDate
EndUserText.
label
Created
on
Created
on
Created
on
–
Semantics.
systemDate.
createdAt
true
true
(null)
–
OrderQuantity
Table 4.3
Z_View… Z_View… Z_View… Z_View…
A
B
C
D
Extract from the Active Element Annotations of the CDS Views from Listing 4.16 to Listing 4.19
CDS view Z_ViewWithAnnotationsB from Listing 4.17 uses CDS view
Z_ViewWithAnnotationsA as its data source.
define view entity Z_ViewWithAnnotationsB
as select from Z_ViewWithAnnotationsA
{
@Semantics.quantity.unitOfMeasure: 'OrderQuantityUnit'
cast( OrderQuantity as meng15 preserving type ) as OrderQuantity,
@EndUserText.label: 'Unit'
OrderQuantityUnit,
CreationDate
}
Listing 4.17
CDS View Z_ViewWithAnnotationsB
This example projects all the fields of CDS view Z_ViewWithAnnotationsA. Field
OrderQuantity is cast onto data element MENG15. As a result, text Quantity, which is
defined for data element MENG15, is implicitly assigned to field OrderQuantity as the
value of its annotation @EndUserText.label. Annotation
@Semantics.quantity.unitOfMeasure :'OrderQuantityUnit', which would usually be
inherited by the propagation logic, is lost due to the cast operation and needs to be
maintained locally.
Local annotation @EndUserText.label:'Unit' overrides the corresponding annotation of
field OrderQuantityUnit, which otherwise would have been propagated from underlying
CDS view Z_ViewWithAnnotationsA.
The propagated annotations @EndUserText.label :'Created on' and
@Semantics.systemDate.createdAt :true defined for field CreationDate aren’t overlaid by
any local annotation. Consequently, they extend the list of active annotations of this
field in CDS view Z_ViewWithAnnotationsB in accordance with Table 4.3 shown
previously.
CDS view Z_ViewWithAnnotationsC from Listing 4.18 uses CDS view
Z_ViewWithAnnotationsB as its data source.
define view entity Z_ViewWithAnnotationsC
as select from Z_ViewWithAnnotationsB
{
OrderQuantityUnit,
@Semantics.systemDate.createdAt: null
CreationDate
}
Listing 4.18
CDS View Z_ViewWithAnnotationsC
CDS view Z_ViewWithAnnotationsC projects fields OrderQuantityUnit and CreationDate
from its base view Z_ViewWithAnnotationsB. Field OrderQuantityUnit retains the active
annotations from the underlying CDS view. By annotating field CreationDate with
@Semantics.systemDate.createdAt :null, the corresponding propagated annotation is
effectively suppressed. As a result, only the label text @EndUserText.label:' Created
on' of its associated data element ERDAT remains active.
CDS view Z_ViewWithAnnotationsD from Listing 4.19 uses CDS view
Z_ViewWithAnnotationsC as its data source.
@Metadata.ignorePropagatedAnnotations:true
define view entity Z_ViewWithAnnotationsD
as select from Z_ViewWithAnnotationsC
{
OrderQuantityUnit
}
Listing 4.19
CDS View Z_ViewWithAnnotationsD
CDS view Z_ViewWithAnnotationsD projects field OrderQuantityUnit from its base CDS
view Z_ViewWithAnnotationsC. Annotation @Metadata.ignorePropagatedAnnotations:true
disables the propagation of element annotations completely. Accordingly, annotation
@EndUserText.label: 'Unit' is removed from the active annotations of field
OrderQuantityUnit. Instead, its label text Sales unit is defined by its associated data
element VRKME (refer to Table 4.3).
Like for CDS simple types, you can get an overview about the active annotations of a
CDS view model by executing its context menu function Open With • Active
Annotations. This launches the Active Annotations view, which is depicted for CDS
view Z_ViewWithAnnotationsD in Figure 4.6.
Figure 4.6
4.3.3
Active Annotations of CDS View Z_ViewWithAnnotationsD
Consistency Aspects
The propagation logic is applied mechanically by the infrastructure without performing
any semantic check. This can lead to model inconsistencies as well as to unwanted
side effects. This holds true especially for CDS annotations that not only define isolated
properties of the annotated CDS simple types or elements themselves but also have
the following characteristics:
Established correlations between the annotated elements and other elements, which
are therefore subject to integrity conditions
Strong relation to the annotated model itself, which means annotations aren’t
intended to be reused outside of their original defining context
In addition, propagated CDS annotations that constitute a semantic group can lead to
problems if they are only partially overwritten by local redefinitions.
Two examples of the aforementioned inconsistencies are explained next. The
corresponding CDS views are based on CDS view Z_ViewWithAnnotationsA from
Listing 4.16.
In Listing 4.20, CDS view Z_ViewWithAnnotationsE projects field OrderQuantity from its
base CDS view Z_ViewWithAnnotationsA. Furthermore, it defines new unit field
OrderQuantityUnit. Although this locally defined field has the same name as a field of
the data source, it’s completely decoupled from this base field and carries constant
value PC.
define view entity Z_ViewWithAnnotationsE
as select from Z_ViewWithAnnotationsA
{
OrderQuantity,
cast( 'PC' as abap.unit(3) ) as OrderQuantityUnit
}
Listing 4.20
CDS View Z_ViewWithAnnotationsE
Within CDS view Z_ViewWithAnnotationsE, active annotation @Semantics.
quantity.unitOfMeasure:'OrderQuantityUnit' of field OrderQuantity, which is
propagated from base CDS view Z_ViewWithAnnotationsA, references newly introduced
local field OrderQuantityUnit instead of the original base field. From a technical point of
view, this is a consistent model. However, this model establishes a unit reference to its
consumers that becomes incorrect if the value of the original referenced base field
OrderQuantityUnit of CDS view Z_ViewWithAnnotationsA is different from value PC,
which is defined in CDS view Z_ViewWithAnnotationsE.
To remove this functional inconsistency, the newly introduced field in affected CDS
view Z_ViewWithAnnotationsE could, for example, be renamed to PieceUnit.
Furthermore, the CDS view should add base field OrderQuantityUnit to its projection
list while keeping its original name. Corrected model Z_ViewWithAnnotationsF is
illustrated in Listing 4.21.
define view entity Z_ViewWithAnnotationsF
as select from Z_ViewWithAnnotationsA
{
OrderQuantity,
OrderQuantityUnit,
cast( 'PC' as abap.unit(3) ) as PieceUnit
}
Listing 4.21
CDS View Z_ViewWithAnnotationsF
Listing 4.22 illustrates a second example, in which the propagation logic leads to
technical inconsistencies. In depicted CDS view Z_ViewWithAnnotationsG, fields
OrderQuantity and OrderQuantityUnit, which are projected from its base view
Z_ViewWithAnnotationsA, are renamed to Quantity and QuantityUnit, respectively, by
using corresponding aliases.
define view entity Z_ViewWithAnnotationsG
as select from Z_ViewWithAnnotationsA
{
OrderQuantity
as Quantity,
OrderQuantityUnit as QuantityUnit
}
Listing 4.22
CDS View Z_ViewWithAnnotationsG
In general, renaming fields doesn’t prevent the propagation logic for element
annotations from being applied to the affected fields. Consequently, the renamed
elements inherit the active annotations of their base elements. Therefore, in CDS view
Z_ViewWithAnnotationsG, field Quantity has active annotation
@Semantics.quantity.unitOfMeasure :'OrderQuantityUnit'. However, because
projected basic field OrderQuantityUnit is renamed to QuantityUnit, the reference
relationship expressed by this latter annotation is incorrect.
To get a consistent model, you must annotate field OrderQuantity locally in CDS view
Z_ViewWithAnnotationsG with @Semantics.quantity.unitOfMeasure :'QuantityUnit'.
[ ! ] Consider Propagation Logic
When using a CDS model as a building block for your own CDS views, it’s up to you
to handle the inconsistencies resulting from the propagation logic. If you can’t fully
control the CDS models yourself but still want to keep the annotations of your own
CDS views stable, you should avoid engaging the propagation logic if in doubt. In
such a case, annotate your CDS views with
@Metadata.ignorePropagatedAnnotations:true, and add all the desired annotations
locally to your view model.
Also consider annotations originating from CDS simple types and data elements. If
you want to keep them stable, you may need to define your own types and assign
them via explicit cast operations to your CDS models.
4.4
Metadata Extensions
CDS metadata extensions are transportable development objects of
technical type DDLX. In principle, CDS metadata extensions allow you
to enrich and override the existing active annotations of a CDS
model, which supports metadata extensions. There may potentially
be a plurality of CDS metadata extensions for a single CDS entity.
CDS metadata extensions are organized in layers. They overlay one
another according to the layer that they are assigned to. Table 4.4
gives you an overview of the overlaying relationships between the
layers.
Layer
Overlays Layer (Including All Subordinate
Layers)
CUSTOMER
PARTNER
PARTNER
INDUSTRY
INDUSTRY
LOCALIZATION
LOCALIZATION CORE
CORE
Table 4.4
–
Layers of CDS Metadata Extensions
For example, CDS metadata extensions of layer PARTNER overlay all
CDS metadata extensions of layer INDUSTRY. This applies to all CDS
metadata extensions of subordinated layers LOCALIZATION and CORE
of layer INDUSTRY too. Within the same layer, the overlay order of
CDS metadata extensions isn’t defined and therefore isn’t
guaranteed.
[+] Improve Readability
Transferring annotations into CDS metadata extensions can help
make your CDS models easier to read. However, make sure that
you avoid unnecessary overlays and nontransparent distributions
of relocated annotations.
If you want to use CDS metadata extensions, avoid distributing
annotations of the same domain over multiple CDS metadata
extensions of the same layer. Instead, you should move, for
example, all UI annotations of a CDS model to the same metadata
extension.
You can create CDS metadata extensions in the same way as other
CDS models in the ADT environment. To do this, choose File • New
• Other… • ABAP • Core Data Services • Metadata Extension.
Names of CDS metadata extensions are defined in their own
namespace, which means their names can, in principle, match the
names of the annotated CDS models.
[+] Naming CDS Metadata Extensions
Adapt the name of your CDS metadata extension to the name of
the annotated CDS model for establishing a corresponding
relationship based on the chosen naming. However, you should
always use your customer namespace, such as a suitable prefix
for preventing naming conflicts with CDS metadata extensions
delivered by SAP.
In the source code of the CDS metadata extension, you first specify
the layer that the CDS metadata extension will be assigned to via
annotation @Metadata.layer. Then, you name the CDS view whose
annotations are to be changed after the introductory key words
annotate view. Afterward, you can start entering annotations for the
specified CDS view at the header and detail level.
Listing 4.23 shows an example of CDS view ZC_SalesOrderItem,
which will be equipped with annotations from a CDS metadata
extension. It therefore has annotation
@Metadata.allowExtensions:true.
@Metadata.allowExtensions: true
define view entity ZC_SalesOrderItem
as select from ZI_SalesOrderItem
{
@EndUserText.label: 'Sales Order'
key SalesOrder,
key SalesOrderItem,
Product
}
Listing 4.23
CDS View ZC_SalesOrderItem
Listing 4.24 shows an example of the CDS metadata extension of
this CDS view.
@Metadata.layer: #CUSTOMER
annotate view ZC_SalesOrderItem with
{
@UI.lineItem: [{importance: #HIGH}]
SalesOrder;
@UI.lineItem: [{importance: #HIGH}]
SalesOrderItem;
}
Listing 4.24
Metadata Extension of CDS View ZC_SalesOrderItem
This CDS metadata extension is assigned to the customer layer by
means of annotation @Metadata.layer:#CUSTOMER.
[»] Choose Layer Assignment
If you want to annotate SAP-delivered CDS view models in your
customer system, you should assign your CDS metadata
extensions to layer CUSTOMER. Layer PARTNER is intended for
corresponding enhancements by partners. When defining
metadata extensions for your own models, you can choose layer
CORE.
In the illustrated CDS metadata extension, key fields SalesOrder and
SalesOrderItem of CDS view ZC_SalesOrderItem are enriched with
annotation @UI.lineItem:[{importance:#HIGH}]. This annotation
marks the annotated fields as particularly important when showing
the data of the CDS view in a tabular display. If possible, the
corresponding columns should still be displayed, even if there isn’t
sufficient space for displaying all the columns of the table.
The other annotations of CDS view ZC_SalesOrderItem remain active
because they aren’t explicitly overlaid by corresponding annotations
of the CDS metadata extension. For example, field SalesOrder
retains its active annotation @EndUserText.label :'Sales Order'.
CDS metadata extensions are only allowed to annotate such CDS
views that have been prepared explicitly for them. These CDS
models have annotation @Metadata.allowExtensions:true (refer to
Listing 4.23). You can’t define CDS metadata extensions for other
CDS views.
In general, only a subset of all the specified annotations can be used
in CDS metadata extensions. Those annotations won’t require any
ABAP Data Dictionary activation. As an example, annotations of
domain UI, which incorporate controlling information for the
presentation and interaction options of UIs, are allowed to be used in
metadata extensions.
[»] Identify Permitted Annotations
The annotation definition specifies whether an annotation may be
used in CDS metadata extensions. The annotations admissible to
be used in CDS metadata extensions are annotated with
@MetadataExtension.usageAllowed:true.
If you define metadata extensions for CDS models that you’re not
responsible for, you should always carefully evaluate their effect and
dependencies, especially if you change the metadata of CDS models
delivered by SAP.
[ ! ] Changing SAP-Delivered CDS Models
You always have to evaluate the effects of changing the metadata
of SAP-delivered CDS models carefully. Even though from a
technical perspective, you may change the annotations of SAPdelivered CDS models without formally applying modifications by
leveraging CDS metadata extensions, the applied changes can
result in severe functional issues in the application logic. Due to
the inheritance mechanism, this may hold true even for application
logic that doesn’t directly consume the annotated CDS model but
consumes models derived from the CDS model.
In addition, even if an SAP-delivered CDS model was enabled for
metadata extensions, there is no guarantee of the lifecycle stability
of the corresponding extensions. In other words, the annotated
CDS model might be changed in an incompatible way or even be
deleted, requiring adaptions of existing metadata extensions.
If in doubt, avoid defining metadata extensions for SAP-delivered
CDS models. Instead, define your own CDS models.
4.5
Active Annotations
Active annotations of a CDS model are calculated by a step-by-step
superposition of the annotations entered in different sources:
1. The annotations of the field and parameter typing data elements
and CDS simple types are determined.
2. The element annotations are overlaid by the propagated active
element annotations of the underlying CDS models. This step is
skipped completely if the propagation logic is disabled by
annotation @Metadata. ignorePropagatedAnnotations :true, or it’s
skipped partially for elements that are calculated locally, for
example, by a cast operation.
3. The annotations, which are entered locally in the CDS view,
overlay the previous result.
4. The annotations entered in the CDS metadata extensions of the
CDS view overwrite the corresponding annotations that were
determined so far. Within this step, the layering of the CDS
metadata extensions is considered.
The resulting active element annotations of a CDS model are then
used as propagated element annotations in the determination of the
active annotations of its dependent CDS views.
Figure 4.7 illustrates these correlations by means of a sample CDS
view stack.
Figure 4.7
Determination of Active Annotations of CDS Views
In Figure 4.7, the locally entered annotations of CDS view A are
overlapped by CDS metadata extensions of layers CORE,
LOCALIZATION, and PARTNER. The result of this overlay represents the
active annotations of CDS view A. The active element annotations of
CDS view A are passed based on the propagation logic to CDS view
B. Here, they are overwritten by the locally entered annotations and
then by the CDS metadata extensions of layers CORE, INDUSTRY, and
CUSTOMER. The resulting active element annotations of CDS view B
are then propagated to CDS view C. This CDS view can again enrich
or superimpose these inherited annotations by its local annotations.
4.6
Summary
You can enrich the definition of the plain SQL processing logic of a
CDS model with semantic information by means of CDS annotations.
This additional metadata can be leveraged in several ways by
various consumers, such as the ABAP runtime environment and
implementation frameworks. You learned how CDS annotations are
defined and how CDS annotations can be added to CDS models.
From the perspective of CDS model consumers, its active
annotations are relevant. These active annotations are composed of
the locally defined CDS annotations, annotations originating from
data types, propagated element annotations, and annotations that
also may be entered in CDS metadata extensions.
In the next chapter, you’ll learn about CDS access controls, which
allow you to apply authorization checks when selecting data records
from CDS models.
5
Access Controls
This chapter introduces the basic concepts and functions of
core data services (CDS) access controls. You’ll also learn
how to implement and test CDS access controls.
From an authorization point of view, an end user should only have
access to the functions and data necessary for fulfilling specific
business tasks (following the principle of least privilege). This means
that a user should only have a minimum set of authorizations.
A system-supported authorization control can be set up at the
functional level as well as at the individual data records level (or for
partial information thereof). In particular, access to sensitive personal
data not only requires a functional protection of the application,
which is referred to as start authorization, but also a fine granular
control, which allows differentiating the access to the individual
protected data records (instance authorization) by evaluating the
values that they contain.
The following example illustrates the differences between the two
mentioned levels of authorization control: In principle, employees of
the sales and distribution department should be able to create and
view sales orders. In contrast, employees from the human resources
department shouldn’t be able to use the corresponding functions.
This can be achieved by not granting the latter employees access to
the applications of the sales and distribution department; that is, they
don’t receive any start authorizations for them.
If the sales department is structured according to the different sales
regions, sales employees who care for European customers should
be allowed to access their order data. However, sales employees
who are responsible for US customers only won’t be allowed to
access the orders of the European customers, even though they
may use the same sales applications; that is, all sales employees
may have the same start authorizations. The additional authorization
restrictions, which are needed for supporting this use case, require
control of access rights based on data records.
[»] Use of Access Controls
CDS access controls allow you to restrict users’ access rights to
specific data records of the CDS model selection results.
In this chapter, you’ll learn about modeling CDS access controls
(Section 5.1) and discover how CDS access controls work
(Section 5.2). In Section 5.3, you’ll be introduced to the common
patterns for implementing CDS access controls. Finally, you’ll learn
how CDS access controls can be tested in Section 5.4.
5.1
Fundamentals of Access Controls
CDS access controls are defined as CDS roles that leverage the
data control language (DCL). In the ABAP Development Tools (ADT)
environment, they are created in a guided dialog similar to the CDS
data models. This creation dialog can be opened, for example, by
going to the menu bar and choosing File • New • Other… • ABAP •
Core Data Services • Access Control.
In the first step of the creation wizard, you can enter the name
(Name) of the DCLS object, its description (Description), and the
CDS entity (Protected Entity), which refers to the data that will be
protected by the CDS access control. In the example in Figure 5.1,
the name of the DCLS matches the name of the protected CDS entity
ZI_SalesOrder in upper case letters.
Figure 5.1
Creation of CDS Access Control: First Step of Wizard
The subsequent steps are the same as when creating a CDS model
(see Chapter 1, Section 1.2.2). You may also select and apply a
suitable template for the CDS access control in the last step of the
wizard, as shown in Figure 5.2.
Figure 5.2
Creation of CDS Access Control: Last Step of Wizard
Listing 5.1 shows the implementation of the access control
immediately after leaving the creation wizard by clicking Finish.
@EndUserText.label: 'Auto assigned mapping role for ZI_SalesOrder'
@MappingRole: true
define role ZI_SALESORDER {
grant select on ZI_SalesOrder
where (entity_element_1,
entity_element_2)
= aspect pfcg_auth(authorization_object,
authorization_field_1,
authorization_field_2,
filter_field_1 = 'filter_value_1');
}
Listing 5.1
CDS Access Control after Finishing the Creation Wizard
In principle, CDS roles can grant users unrestricted access to the
selection results. However, CDS roles usually contain conditions
under which the access to the data of the protected CDS models is
granted to the users. These access rules may be composed of
various access conditions. The latter can be formulated by
correlating fields of the CDS models with the logged-on user, with
user-specific settings, with constant literal values, or with
authorization fields of authorization objects. These authorization
objects are the same as those that the Transaction PFCG–based
authorization concept is based on.
In the following, we’ll first focus on the usage of authorization objects
and fields, which covers the most frequent use cases. Access
controls defining access conditions by other means will be explained
later in this chapter.
Here, we adapt the access control from Listing 5.1 according to
Listing 5.2 by replacing placeholders entity_element_1 and
authorization_object in the where condition with CDS view field
SalesOrderType and authorization object V_VBAK_AAT.
@EndUserText.label: 'Auto assigned mapping role for ZI_SalesOrder'
@MappingRole: true
define role ZI_SalesOrder {
grant select on ZI_SalesOrder
where ( SalesOrderType ) =
aspect pfcg_auth ( V_VBAK_AAT,
AUART,
ACTVT = '03' );
}
Listing 5.2
CDS Role ZI_SalesOrder
Therein, CDS role ZI_SalesOrder protects CDS view ZI_SalesOrder
from Listing 5.3.
@AccessControl.authorizationCheck: #CHECK
define view entity ZI_SalesOrder
as select from ...
{
key SalesOrder,
SalesOrderType,
...
}
Listing 5.3
CDS View ZI_SalesOrder
CDS role ZI_SalesOrder restricts access to the selection results of
protected CDS view ZI_SalesOrder based on the order type. For this
purpose, field SalesOrderType is linked to corresponding
authorization field AUART of authorization object V_VBAK_AAT from
Figure 5.3. Accordingly, a user may only access a record of the sales
order document via protected CDS view ZI_SalesOrder if the
authorization that is granted to that user contains activity ACTVT with
value 03 (display authorization) for corresponding order type
SalesOrderType.
Figure 5.3
Authorization Object V_VBAK_AAT with Allowed Activity Values
CDS roles and their corresponding access rules often contain
several authorization conditions whose results are logically
connected. In this context, the applied conditions may also refer to
different authorization objects. Listing 5.4 shows such a complex
CDS role definition for CDS view ZI_SalesOrder from Listing 5.3.
@MappingRole: true
define role ZI_SalesOrder {
grant select on ZI_SalesOrder
where ( SalesOrderType ) =
aspect pfcg_auth ( V_VBAK_AAT,
AUART,
ACTVT = '03' )
and ( OrganizationDivision,
SalesOrganization,
DistributionChannel ) =
aspect pfcg_auth ( V_VBAK_VKO,
SPART,
VKORG,
VTWEG,
ACTVT = '03' );
}
Listing 5.4
CDS Role with Multiple Access Conditions
In addition to the access condition from Listing 5.2, this role definition
contains another condition that is combined with the first condition by
a logical and. This additional authorization condition incorporates
additional authorization object V_VBAK_VKO into the CDS access
control.
To obtain read access to the data of CDS view ZI_SalesOrder, the
user must therefore not only be authorized for the order type
(SalesOrderType) via authorization object V_VBAK_AAT but also for the
division (OrganizationDivision), the organization
(SalesOrganization), and the distribution channel
(DistributionChannel) via authorization object V_VBAK_VKO.
Note that within the modeled access condition, CDS view fields
OrganizationDivision, SalesOrganization, and DistributionChannel
are implicitly mapped according to their order onto the authorization
fields SPART, VKORG, and VTWEG, respectively. These bindings are
compounded by logical and relationships.
5.2
Mode of Action of Access Controls
When selecting data from a CDS model using the ABAP SQL
interface, the CDS roles, which are assigned to the CDS models, are
evaluated automatically.
[»] Definition of CDS Roles as Mapping Roles
The system automatically assigns CDS roles to all users. This is
expressed by annotation @MappingRole:true, which each CDS role
must specify.
Depending on the result of the evaluation, the select statement is
automatically enriched with additional filter criteria by the ABAP
runtime environment. The injected filter criteria are derived from the
access rules, which are defined in the CDS access controls, taking
into account the authorizations of the logged-on user.
[+] Evaluate Authorization Restrictions on the Database
Level
If you want to apply authorization checks on data records that are
selected from the database, you should leverage the CDS access
controls to restrict the selection result already in the database
efficiently. This approach avoids the need to calculate and transfer
superfluous data to the ABAP application server.
As an example, authorization role Z_SALES_DISPLAY, which is depicted
in Figure 5.4, grants read access (ACTVT = Display) to standard sales
orders (AUART = TAF) if authorization object V_VBAK_AAT is used.
Figure 5.4
Example for an Authorization Role
Let’s take a look at the selection statement in ABAP from Listing 5.5,
in which CDS view ZI_SalesOrder from Listing 5.3 acts as the data
source and will be protected by the CDS role from Listing 5.2.
SELECT *
FROM ZI_SalesOrder
INTO TABLE @DATA(lt_zi_salesorder).
Listing 5.5
Data Selection from CDS View ZI_SalesOrder in ABAP
If authorization role Z_SALES_DISPLAY from Figure 5.4 is assigned to
the user, the selection statement in ABAP from Listing 5.5 leads to
the database selection statement from Listing 5.6. As shown, the
effective data selection in the database takes the user’s
authorizations into account.
SELECT "MANDT", "SALESORDER"... *
FROM "ZI_SALESORDER"
WHERE "MANDT"
= '...'
AND "SALESORDERTYPE" = 'TAF'
Listing 5.6
Data Selection from CDS View ZI_SalesOrder in the Database
Accordingly, in addition to the implicit client handling ("MANDT"='...'),
the restriction to the permitted order type ("SALESORDERTYPE"='TAF'),
which is defined in the CDS role from Listing 5.2, is injected into the
where clause of the select statement. The restriction is then passed
to and processed on the database.
The way the effective select statement is influenced by the CDS
access controls depends on various influencing factors. One might
be the key definitions of the protected CDS models. For performance
reasons, you should always try to define the key of a CDS model
and keep it as short as possible. When using path expressions in
CDS access controls, missing or overly long key definitions can
otherwise lead to unnecessary additional filter criteria, which can
have a negative effect on the overall performance of the data
selection.
[ ! ] Ensure Uniqueness of Keys
If you specify a key in a protected CDS model, the key must
uniquely identify a single data record. The uniqueness must be
ensured for any arbitrary selection from the CDS model.
Otherwise, there's the risk that the applied filter logic of the CDS
access controls will create duplicate records in the selection
results lists.
When selecting data from a CDS model in an ABAP implementation,
only its own specific CDS access controls take effect. This means
that the CDS roles, which protect the underlying data sources of a
CDS model, have no effect.
The application of CDS access controls will be explained in more
detail by the sample CDS views from Figure 5.5 and their usage
according to Listing 5.7.
Figure 5.5
CDS Views with CDS Access Controls
SELECT *
FROM ViewA
INTO TABLE @DATA(lt_view_a).
SELECT *
FROM ViewC
INTO TABLE @DATA(lt_view_c).
SELECT ViewC~FieldC,
\_ViewD-FieldD
FROM ViewC
INTO TABLE @DATA(lt_fields_of_views_c_and_d).
SELECT ViewB~FieldB
FROM ViewC
INNER JOIN ViewB
ON ViewB~FieldC = ViewC~FieldC
INTO TABLE @DATA(lt_field_of_view_b).
Listing 5.7
ABAP Selection from CDS Views in Figure 5.5
In the first data selection from Listing 5.7, CDS role A of CDS view A
is effective and restricts the selection result according to the
authorizations of the user.
In the second data selection, CDS role C of CDS view C is taken into
account. Note that the access controls (CDS roles A and B) of the
data sources (CDS views A and B) of CDS view C have no meaning.
This would also hold true if CDS view C wasn’t protected by its own
CDS role C.
In the third selection statement, data is read from CDS view C, which
acts as the primary data source, as well as from its associated CDS
view D. The latter becomes a secondary data source due to path
expression \_ViewD.FieldD. As a result, both CDS access controls,
CDS roles C and D, are effective. Note that the restrictions, which
are defined by CDS role C, are reflected in the number of data
records of the selection result. Missing authorizations for accessing
the data of CDS view D lead to initial values for field FieldD in the
results list in ABAP.
The fourth data selection in Listing 5.7 is influenced by the CDS
access controls (CDS roles C and B) of CDS views C and B
analogous to the third data selection. However, due to the applied
inner join logic, missing access authorizations for data records of
CDS view B don’t lead to initial values for field FieldD in the results
list. Instead, missing access authorizations result in removing the
entire data record from the results list.
[»] Defining and Using Authorization Protections
The CDS access controls are only effective if you use the
protected CDS model as a direct data source within your data
selection in ABAP. Therefore, you must explicitly equip each CDS
model, which requires an instance-based authorization protection,
with its own CDS access control.
You can annotate your CDS models with
@AccessControl.authorizationCheck… to specify whether an
authorization check is required for a data selection. If this annotation
is missing, it’s expected that no authorization check is required. This
corresponds to implicitly annotating the corresponding CDS model
with @AccessControl.authorizationCheck :#NOT_REQUIRED.
Table 5.1 describes the admissible values of annotation
@AccessControl.authorizationCheck.
Annotation
Value
Description
MANDATORY
The annotated CDS model will have a CDS
access control. If no CDS access control is
defined, data selections result in runtime errors.
CHECK
The annotated CDS model will have a CDS
access control.
In contrast to value MANDATORY, no existence
check for the CDS access control is performed
at runtime.
NOT_REQUIRED
In general, a CDS access control isn’t required.
This value corresponds to the default value,
which is specified in the annotation definition.
NOT_ALLOWED
Defining a CDS access control isn’t allowed.
Note that this value has an effect on the runtime.
It prevents defined CDS access controls from
becoming effective.
PRIVILEGED_ONLY
A direct data selection from the annotated CDS
model is only possible through privileged
access, which requires a special addition to the
standard ABAP SQL selection statement
(Section 5.3.8).
Table 5.1
Admissible Values of the Annotation @AccessControl. authorizationCheck
[»] Document Authorization Checks
You should always specify the expected access control explicitly
by using annotation @AccessControl.authorizationCheck. This also
applies to implicit default value NOT_REQUIRED, which merely serves
documentation purposes. If a CDS model that is annotated
accordingly is protected by a CDS access control, this CDS
access control takes effect.
Even though we leverage value CHECK for simplicity reasons in
many examples, we recommend that in your productive CDS
models, you only use value MANDATORY instead of CHECK.
In principle, you can define multiple CDS roles for the same CDS
model. By default, these CDS roles are merged by applying a logical
or. Consequently, the effective authorization protection is less
restrictive because only the conditions of one of these individual
CDS roles must be fulfilled to get access to the data of the protected
CDS model. However, by enhancing the CDS role definitions
accordingly, you can make the overall access protection become
even stricter than when using a single CDS role. This will be
explained in Section 5.3.7.
[+] Naming
To increase the transparency of the models and facilitate error
analyses, you should always protect a CDS model with a single
CDS role only. Furthermore, you should choose the same name
for the protected CDS model, the assigned source definition of its
CDS access control (DCLS), and the CDS role defined in it.
5.3
Implementation Patterns for Access Controls
The implementations of CDS access controls follow recurring patterns in many cases, which
are discussed in subsequent sections. In the course of discussion, you’ll also learn about
some special features of CDS access controls in transactional applications, analytical query
services, and data selections based on ABAP SQL.
5.3.1
Implement Access Controls with Path Expressions
Within the implementation of CDS access controls, associations of the protected CDS models
can be used for defining path expressions. In the following sections, you’ll learn how you can
leverage these path expressions for harmonizing the access control logic of multiple
associated CDS models. Furthermore, you’ll learn how you can adapt the default evaluation
logic of path expressions. Finally, we’ll show you how to deal with CDS parameters when
using path expressions in access controls.
Define Uniform Access Controls
In many cases, all CDS models that are closely related to a single application object should
be protected in the same manner.
A typical example is the sales order document, which is structured into header, item, and
schedule line data. By default, a sales employee should be allowed to view the entire instance
of the sales order document and not only its parts. This means that a user should be allowed
to view the header data of a selected sales order instance as well as the corresponding data
of its items and schedule lines. To realize this requirement, the CDS access controls for the
corresponding CDS models must be implemented in a uniform way based on the same
access conditions. In this context, you can leverage path expressions in the CDS role
definitions for introducing fields that control the access and that aren’t already part of the field
list of the CDS models to be protected.
For example, the access protection of sales order header CDS view ZI_SalesOrder, which is
implemented by CDS role ZI_SalesOrder from Listing 5.2 shown earlier, can also be
implemented by CDS role ZI_SalesOrderItem of item CDS view ZI_SalesOrderItem. This is
done by leveraging association _SalesOrder, which relates the records of the items to the
records of the sales order headers. Listing 5.8 shows the corresponding CDS view model.
@AccessControl.authorizationCheck: #CHECK
define view entity ZI_SalesOrderItem
as select from ...
association [1..1] to ZI_SalesOrder as _SalesOrder
on $projection.SalesOrder = _SalesOrder.SalesOrder
{
key SalesOrder,
key SalesOrderItem,
_SalesOrder, ...
}
Listing 5.8
CDS View ZI_SalesOrderItem with an Association to CDS View ZI_SalesOrder
Listing 5.9 shows its protecting CDS role ZI_SalesOrderItem. It accesses field SalesOrderType
of CDS view ZI_SalesOrder via path expression _SalesOrder.SalesOrderType in its access
condition.
@MappingRole: true
define role ZI_SalesOrderItem {
grant select on ZI_SalesOrderItem
where ( _SalesOrder.SalesOrderType ) =
aspect pfcg_auth ( V_VBAK_AAT,
AUART,
ACTVT = '03' );
}
Listing 5.9
CDS Role ZI_SalesOrderItem with a Path Expression
As a result, the data records of the sales order item are protected in the same way as the
corresponding data record of the sales order header.
Similarly, the CDS view of sales order schedule line ZI_SalesOrderScheduleLine in Listing 5.10
can also be protected by using path expressions.
@AccessControl.authorizationCheck: #CHECK
define view entity ZI_SalesOrderScheduleLine
as select from ...
association [1..1] to ZI_SalesOrder as _SalesOrder
on $projection.SalesOrder = _SalesOrder.SalesOrder
{
key SalesOrder,
key SalesOrderItem,
key SalesOrderScheduleLine,
_SalesOrder, ...
}
Listing 5.10
CDS View ZI_SalesOrderScheduleLine with an Association to CDS View ZI_SalesOrder
The equally named CDS role ZI_SalesOrderScheduleLine of this CDS view is illustrated in
Listing 5.11.
@MappingRole: true
define role ZI_SalesOrderScheduleLine {
grant select on ZI_SalesOrderScheduleLine
where ( _SalesOrder.SalesOrderType ) =
aspect pfcg_auth ( V_VBAK_AAT,
AUART,
ACTVT = '03' );
}
Listing 5.11
CDS Role ZI_SalesOrderScheduleLine with a Path Expression
[»] Evaluation Logic for Path Expressions
The access conditions of the CDS roles may use path expressions that are based on
associations with a maximum target cardinality greater than 1. By default, these conditions
are evaluated by the infrastructure in such a way that they must be fulfilled for at least one
of the associated data records.
Adapt Evaluation Logic of Path Expressions
The access control logic applied when using path expressions can be adjusted in various
ways, as illustrated by the following example.
Let’s assume that you want to protect records of the sales order header so that users who are
authorized to access the ordered products contained in the sales order documents are also
authorized to access the sales order headers. In our example, the data of the sales order
header to be protected is defined by CDS view ZC_SlsOrdWithAccessCntrlByProd, as shown in
Listing 5.12.
@AccessControl.authorizationCheck: #CHECK
define view entity ZC_SlsOrdWithAccessCntrlByProd
as select distinct from t000
association [0..*] to ZC_SlsOrdItemForAccessControl as _Item
on $projection.SalesOrder = _Item.SalesOrder
{
key 'S1' as SalesOrder,
_Item
} union all select distinct from t000
association [0..*] to ZC_SlsOrdItemForAccessControl as _Item
on $projection.SalesOrder = _Item.SalesOrder
{
key 'S2' as SalesOrder,
_Item
} union all select distinct from t000
association [0..*] to ZC_SlsOrdItemForAccessControl as _Item
on $projection.SalesOrder = _Item.SalesOrder
{
key 'S3' as SalesOrder,
_Item
}
Listing 5.12
CDS View ZC_SlsOrdWithAccessCntrlByProd
CDS view ZC_SlsOrdWithAccessCntrlByProd defines three sales order header records (see
Table 5.2). In addition, it defines an association to CDS view ZC_SlsOrdItemForAccessControl,
as shown in Listing 5.13.
define view entity ZC_SlsOrdItemForAccessControl
as select distinct from t000
association [0..1] to ZC_ProductForAccessControl as _Product
on $projection.Product = _Product.Product
{
key 'S1' as SalesOrder,
key 'I1' as SalesOrderItem,
'P1' as Product,
_Product
} union select distinct from t000
association [0..1] to ZC_ProductForAccessControl as _Product
on $projection.Product = _Product.Product
{
key 'S1' as SalesOrder,
key 'I2' as SalesOrderItem,
'P1' as Product,
_Product
} union select distinct from t000
association [0..1] to ZC_ProductForAccessControl as _Product
on $projection.Product = _Product.Product
{
key 'S2' as SalesOrder,
key 'I1' as SalesOrderItem,
'P1' as Product,
_Product
} union select distinct from t000
association [0..1] to ZC_ProductForAccessControl as _Product
on $projection.Product = _Product.Product
{
key 'S2' as SalesOrder,
key 'I2' as SalesOrderItem,
'P2' as Product,
_Product
} union select distinct from t000
association [0..1] to ZC_ProductForAccessControl as _Product
on $projection.Product = _Product.Product
{
key 'S3' as SalesOrder,
key 'I1' as SalesOrderItem,
''
as Product,
_Product
}
Listing 5.13
CDS View ZC_SlsOrdItemForAccessControl
CDS view ZC_SlsOrdItemForAccessControl defines five records in total of sales order items
(see Table 5.2). These records refer to the three sales order header records of CDS view
ZC_SlsOrdWithAccessCntrlByProd and the two product records of associated CDS view
ZC_ProductForAccessControl from Listing 5.14.
define view entity ZC_ProductForAccessControl
as select distinct from t000
{
key 'P1' as Product,
'A1' as AuthorizationGroup
} union select distinct from t000
{
key 'P2' as Product,
'A2' as AuthorizationGroup
}
Listing 5.14
CDS View ZC_ProductForAccessControl
The two products defined by CDS view ZC_ProductForAccessControl have distinct
authorization groups (see Table 5.2).
CDS View
Field
SalesOrder SalesOrderItem Product AuthorizationGroup
ZC_SlsOrdWithAccessCntrlByProd S1
S2
S3
ZC_SlsOrdItemForAccessControl
S1
I1
P1
S1
I2
P1
S2
I1
P1
S2
I2
P2
S3
I1
-
ZC_ProductForAccessControl
Table 5.2
Records of CDS Views from Listing 5.12 to Listing 5.14
P1
A1
P2
A2
Now let’s assume that the user is authorized to access products that are assigned to
authorization group (BEGRU) A1 via authorization object M_MATE_MAT. As a result, when protecting
CDS view ZC_SlsOrdWithAccessCntrlByProd from Listing 5.12 via CDS role
ZC_SlsOrdWithAccessCntrlByProd from Listing 5.15, the user would be able to access sales
orders S1 and S2.
@MappingRole: true
define role ZC_SlsOrdWithAccessCntrlByProd {
grant select on ZC_SlsOrdWithAccessCntrlByProd
where ( _Item._Product.AuthorizationGroup )
?= aspect pfcg_auth ( M_MATE_MAT, BEGRU, actvt = '03' );
}
Listing 5.15
CDS Role ZC_SlsOrdWithAccessCntrlByProd
This is possible because path expression _Item._Product.AuthorizationGroup would be
valuated as A1 for at least one sales order item (refer to Table 5.2).
If this isn’t intended, but access will only be granted if all associated products are assigned to
authorization group A1, the all quantifier should be added to the expression, as shown in
Listing 5.16.
@MappingRole: true
define role ZC_SlsOrdWithAccessCntrlByProd {
grant select on ZC_SlsOrdWithAccessCntrlByProd
where all ( _Item._Product.AuthorizationGroup )
?= aspect pfcg_auth ( M_MATE_MAT, BEGRU, actvt = '03' );
}
Listing 5.16
CDS Role ZC_SlsOrdWithAccessCntrlByProd with the All Quantifier
Accordingly, the user would only be able to access sales order S1.
If the user should also have access to sales orders that don’t have items with product
references such as S3, CDS role ZC_SlsOrdWithAccessCntrlByProd needs to be enriched, as
illustrated in Listing 5.17.
@MappingRole: true
define role ZC_SlsOrdWithAccessCntrlByProd {
grant select on ZC_SlsOrdWithAccessCntrlByProd
where all ( _Item._Product.AuthorizationGroup bypass
when is null )
?= aspect pfcg_auth ( M_MATE_MAT, BEGRU, actvt = '03' );
}
Listing 5.17
CDS Role ZC_SlsOrdWithAccessCntrlByProd with All Quantifier and Bypass Logic
Therein, the bypass when statement defines a condition under which the evaluation result is to
be regarded as logically true. In our example, this is the case if no authorization group can be
determined when following the path expression.
Handle Parameters in Path Expressions
If the target CDS view of an association has parameters, these parameters must be supplied
with values if you use this association in path expressions of access conditions.
The CDS views defined in Listing 5.18 and Listing 5.19 serve illustration purposes.
Listing 5.18 shows CDS view ZC_SalesOrderWithParameters for the sales order header with
parameters.
@AccessControl.authorizationCheck: #CHECK
define view entity ZC_SalesOrderWithParameters
with parameters
P_Parameter1A : abap.char(1),
P_Parameter1B : abap.char(1),
P_Parameter1C : abap.char(1)
as select from ZI_SalesOrder
{
key SalesOrder,
SalesOrderType
}
Listing 5.18
CDS View ZC_SalesOrderWithParameters with Parameters
Listing 5.19 shows CDS view ZC_SalesOrderItemWithParams for the sales order item with
parameters. It defines association _SalesOrder to CDS view ZC_SalesOrderWithParameters
from Listing 5.18.
@AccessControl.authorizationCheck: #CHECK
define view entity ZC_SalesOrderItemWithParams
with parameters
P_Parameter2A : abap.char(1),
P_Parameter2B : abap.char(1)
as select from ZI_SalesOrderItem
association [0..1] to ZC_SalesOrderWithParameters as _SalesOrder
on $projection.SalesOrder = _SalesOrder.SalesOrder
{
key SalesOrder,
key SalesOrderItem,
_SalesOrder
}
Listing 5.19
CDS View ZC_SalesOrderItemWithParams with Parameters
CDS view ZC_SalesOrderItemWithParams will be protected in the same way as in the previous
examples; that is, the access to the data records is reserved for users who have the read
authorization for the corresponding order type of the sales order document. Because field
SalesOrderType isn’t defined locally in CDS view ZC_SalesOrderItemWithParams, it will be
fetched from associated target CDS view ZC_SalesOrderWithParameters. This logic is
implemented by CDS role ZC_SalesOrderItemWithParams from Listing 5.20.
@MappingRole: true
define role ZC_SalesOrderItemWithParams {
grant select on ZC_SalesOrderItemWithParams
where ( _SalesOrder( P_Parameter1A : $parameters.P_Parameter2A,
P_Parameter1B : $parameters.P_Parameter2B,
P_Parameter1C : 'X'
).SalesOrderType ) =
aspect pfcg_auth ( V_VBAK_AAT,
AUART,
ACTVT = '03' );
}
Listing 5.20
CDS Role ZC_SalesOrderItemWithParams with Parameters in Path Expression
In path expression _SalesOrder(…).SalesOrderType, you must bind all three parameters
(P_Parameter1A, P_Parameter1B, and P_Parameter1C) of target CDS view
ZC_SalesOrderWithParameters from Listing 5.18. In the given example, the first two parameters
(P_Parameter1A, P_Parameter1B) of the target CDS view are mapped onto suitable, semantically
matching parameters P_Parameter2A and P_Parameter2B of source CDS view
ZC_SalesOrderItemWithParams. The third parameter, P_Parameter1C, is set to constant value X.
It’s assumed that this value properly supplies this parameter in the given example.
[»] Path Expressions in CDS Roles
Fields that are required for defining the access conditions of your CDS roles can be either
included in the protected CDS model or accessed via path expressions in the CDS roles.
Both of these modeling options have advantages and disadvantages that must be
evaluated carefully based on the given use cases.
If fields that are relevant for defining CDS access controls are added via explicit joins, the
static complexity of the protected CDS views increases. Furthermore, the same data is
modeled redundantly. Even the number of data records in the selection result may increase
by multiplication effects if the cardinality of the included join partner is greater than 1.
However, from a performance perspective, it may be beneficial to include authorization
controlling fields directly in the protected CDS model, instead of including them via path
expressions in the corresponding CDS roles. This can make the evaluation of access rules
more efficient, specifically for CDS models that process and expose large amounts of data.
You can expect a further performance improvement for read accesses if you persist the
authorization-relevant fields redundantly in the protected entities and thus avoid
corresponding joins at runtime.
5.3.2
Inherit Implementation of Access Controls
The DCL syntax allows you to reuse the logic of existing CDS roles when implementing other
CDS roles. We’ll discuss how in the following sections.
Options for Inheritance
The applicable inheritance options are explained in the following example.
Listing 5.21 shows CDS view ZC_SalesOrder, which projects fields from CDS view
ZI_SalesOrder, shown previously in Listing 5.3.
@AccessControl.authorizationCheck: #CHECK
define view entity ZC_SalesOrder
as select from ZI_SalesOrder
{
key SalesOrder,
SalesOrderType
}
Listing 5.21
CDS View ZC_SalesOrder
Listing 5.22 displays associated CDS role ZC_SalesOrder.
@MappingRole: true
define role ZC_SalesOrder {
grant select on ZC_SalesOrder
where ( SalesOrderType ) =
aspect pfcg_auth ( V_VBAK_AAT,
AUART,
ACTVT = '03' );
}
Listing 5.22
CDS Role ZC_SalesOrder with Local Implementation
The implementation corresponds to the implementation of CDS role ZI_SalesOrder, shown
previously in Listing 5.2, which protects underlying base CDS view ZI_SalesOrder.
Instead of implementing CDS role ZC_SalesOrder from scratch, as depicted in Listing 5.22, you
can also reuse the logic of CDS role ZI_SalesOrder by applying the inheritance mechanism of
CDS access controls. From a technical perspective, there are three ways to implement this
inheritance logic.
As a first alternative, you can define the access conditions of DCL role ZC_SalesOrder by
inheriting all the access conditions defined for CDS view ZI_SalesOrder. Listing 5.23 shows
the corresponding implementation.
@MappingRole: true
define role ZC_SalesOrder {
grant select on ZC_SalesOrder
where inheriting conditions from entity ZI_SalesOrder;
}
Listing 5.23
CDS Role ZC_SalesOrder Inheriting Access Conditions from CDS View ZI_SalesOrder
As a second alternative, you can define the access conditions of DCL role ZC_SalesOrder by
inheriting all the access conditions of DCL role ZI_SalesOrder for its protected CDS view
ZI_SalesOrder. The resulting implementation is depicted in Listing 5.24.
@MappingRole: true
define role ZC_SalesOrder {
grant select on ZC_SalesOrder
where inherit ZI_SalesOrder for grant select on ZI_SalesOrder;
}
Listing 5.24
CDS Role ZC_SalesOrder Inheriting Access Conditions from CDS Role ZI_SalesOrder
As a third alternative, you can inherit the access rules from DCL role ZI_SalesOrder, as
illustrated in Listing 5.25.
@MappingRole: true
define role ZC_SalesOrder {
grant select on ZC_SalesOrder
inherit ZI_SalesOrder;
}
Listing 5.25
CDS Role ZC_SalesOrder Inheriting Access Rules from CDS Role ZI_SalesOrder
Note that in the given example, the effects of CDS role definitions from Listing 5.22 to
Listing 5.25 are identical. However, the presented third alternative for realizing the inheritance
mechanism presented in Listing 5.25 is considered obsolete and should no longer be used.
Technical Dependencies
The inheritance mechanism supports you in defining complex access rules only once and
reusing them where appropriate. However, you need to take some dependencies into account
when choosing the inheritance approach. They will be explained next.
The successful application of the inheritance mechanism usually requires a close semantic
relationship between the CDS models that will be protected. Often the models in question are
defined as projections (select from or projection on) from one another.
Technically, all elements (fields and associations) referenced in the implementation of the
inherited CDS role must be exposed with the same names in the CDS models to be protected
by the inheriting CDS roles. In our example, the projection list of CDS view ZC_SalesOrder
from Listing 5.21 must contain field SalesOrderType, which is referenced in CDS role
ZI_SalesOrder. Otherwise, any of the CDS role definitions ZC_SalesOrder discussed previously
would have syntactical errors.
[ ! ] Consider Side Effects of the Inheritance Logic
You should only reuse the implementation of a CDS role as a whole or as its parts if you
want to automatically synchronize your CDS role with the implementation changes of the
reused CDS role. Otherwise, there is the risk that you’ll inherit changes of access controls
unintentionally and without notice.
If new technical dependencies are introduced during the maintenance of an inherited CDS
role implementation, syntax errors may occur in the inheriting CDS roles. For example, if a
new field is referenced by the inherited access conditions, the inheriting CDS roles may
become inconsistent if the CDS models protected by them don’t expose the same field. In
particular, you should consider such dependencies if the inherited implementation is subject
to a different lifecycle than the CDS role that you created. If in doubt, it’s best to avoid using
the inheritance mechanism.
You can reduce the technical dependencies from the inherited DCL logic and thus make your
inheriting CDS roles more robust by replacing the referenced base CDS entity with an
association (path) pointing to it using key words replacing root with. As a result, all elements
in the inherited conditions are prefixed with the specified path expressions; that is, it’s no
longer required to include any of the elements, which are effectively used in the inherited CDS
role logic, in the protected CDS model itself. Instead, only a suitable association (path) to the
CDS entity from which the access conditions will be inherited must be defined.
[ ! ] Consider Performance Implications of the Delegation Approach
You should always check the performance implications of this delegation approach carefully
because it may introduce additional joins that can’t be pruned in the database processing
logic.
In this context, your inheriting CDS role can become even more robust if you define a fallback
for when the inherited role no longer exists by using addition default true. As a result of this
addition, access to the records of the protected CDS model will be granted if the inherited
CDS role no longer exists.
To leverage the delegation mechanism in our example, we have to equip protected CDS view
ZC_SalesOrder with association _SalesOrder to its base view ZI_SalesOrder, as shown in
Listing 5.26.
@AccessControl.authorizationCheck: #CHECK
define view entity ZC_SalesOrder
as select from ZI_SalesOrder
association [1..1] to ZI_SalesOrder as _SalesOrder
on $projection.SalesOrder = _SalesOrder.SalesOrder
{
key SalesOrder,
SalesOrderType,
_SalesOrder
}
Listing 5.26
CDS View ZC_SalesOrder with Association to CDS View ZI_SalesOrder
Listing 5.27 illustrates how the CDS role from Listing 5.23 would look after adapting it
accordingly.
@MappingRole: true
define role ZC_SalesOrder {
grant select on ZC_SalesOrder
where inheriting conditions from entity ZI_SalesOrder
default true
replacing { root with _SalesOrder };
}
Listing 5.27
CDS Role ZC_SalesOrder Inheriting Access Conditions from CDS View ZI_SalesOrder via Delegation
Note that from the perspective of CDS role ZC_SalesOrder, field SalesOrderType is no longer
required to be present in CDS view ZC_SalesOrder.
The delegation approach applying associations shown here is useful not only for improving
the stability of inheriting CDS roles but also for increasing the flexibility in using the inheritance
logic of the CDS role implementations.
In Section 5.3.1, we implemented CDS role ZI_SalesOrderItem from Listing 5.9 and CDS role
ZI_SalesOrderScheduleLine from Listing 5.11 without reusing CDS role ZI_SalesOrder from
Listing 5.2. Applying a simple inheritance logic wouldn’t work in these cases because field
SalesOrderType, which is referenced in the access condition of CDS role ZI_SalesOrder, is
neither included in the projection list of CDS view ZI_SalesOrderItem nor in the projection list
of CDS view ZI_SalesOrderScheduleLine. To enable a simple inheritance logic, you would
need to include field SalesOrderType directly in the CDS views to be protected via a join.
Alternatively, you can use a delegation approach for defining the inherited roles. For this
purpose, you can leverage association _SalesOrder, which relates CDS view
ZI_SalesOrderItem and CDS view ZI_SalesOrderScheduleLine with CDS view ZI_SalesOrder.
Listing 5.28 shows the definition of CDS role ZI_SalesOrderItem, which uses this association
for replacing the root information in the inherited access conditions. Additionally, this role
definition specifies the default evaluation result of the inherited access conditions as true.
@MappingRole: true
define role ZI_SalesOrderItem {
grant select on ZI_SalesOrderItem
where inheriting conditions from entity ZI_SalesOrder
default true
replacing { root with _SalesOrder };
}
Listing 5.28
CDS Role ZI_SalesOrderItem with Inheritance Logic
Similarly, you could implement CDS role ZI_SalesOrderScheduleLine. However, because the
implementations of the CDS roles for the sales order item and the schedule line are identical,
one of these implementations could also be reused for defining the other CDS role. For
example, CDS role ZI_SalesOrderScheduleLine could inherit the implementation of CDS role
ZI_SalesOrderItem. Listing 5.29 shows the adjusted implementation of CDS role
ZI_SalesOrderScheduleLine.
@MappingRole: true
define role ZI_SalesOrderScheduleLine {
grant select on ZI_SalesOrderScheduleLine
where inherit ZI_SalesOrderItem for grant select on
ZI_SalesOrderItem;
}
Listing 5.29
CDS Role ZI_SalesOrderScheduleLine with Inheritance Logic
Redefine Inherited Access Conditions
Now we want to show you how you can redefine inherited access conditions to serve your
envisioned use cases. Let’s assume that dedicated value help view ZC_SalesOrderVH is
defined according to Listing 5.30 as a plain projection of CDS view ZI_SalesOrder
from Listing 5.3 and that the latter is protected by CDS role ZI_SalesOrder from Listing 5.2.
@AccessControl.authorizationCheck: #CHECK
define view entity ZC_SalesOrderVH
as select from ZI_SalesOrder
{
key SalesOrder,
SalesOrderType
}
Listing 5.30
CDS View ZC_SalesOrderVH
Let’s further assume that a user will be enabled to access the records of this CDS view if the
user has either display authorizations (ACTVT=03) or merely value help authorizations
(ACTVT=F4) for the related sales order documents.
In this case, the corresponding access rule can be modeled as depicted in Listing 5.31.
@MappingRole: true
define role ZC_SalesOrderVH {
grant select on ZC_SalesOrderVH
where inheriting conditions from entity ZI_SalesOrder
replacing { pfcg_filter field actvt value '03' with 'F4' }
or
inheriting conditions from entity ZI_SalesOrder;
}
Listing 5.31
CDS Role ZC_SalesOrderVH with Extended Inheritance Logic
In this example, CDS role ZC_SalesOrderVH inherits the access conditions from CDS entity
ZI_SalesOrder. It combines these access conditions by a logical or with the same set of
access conditions evaluating value help activity F4 instead of display activity 03 as specified by
statement replacing { pfcg_filter field actvt value '03' with 'F4' }.
The resulting effective CDS role definition is shown in Listing 5.32.
@MappingRole: true
define role ZC_SalesOrderVH {
grant select on ZC_SalesOrderVH
where ( SalesOrderType ) =
aspect pfcg_auth ( V_VBAK_AAT,
AUART,
ACTVT = 'F4' )
or ( SalesOrderType ) =
aspect pfcg_auth ( V_VBAK_AAT,
AUART,
ACTVT = '03' )
;
}
Listing 5.32
Effective CDS Role ZC_SalesOrderVH
Optional Elements
When defining CDS roles, you may specify optional elements by using the statement with
optional elements (...). Optional elements are CDS elements that are referenced within the
CDS role implementation and that carry a default valuation of true or false. The default value
applies if the corresponding CDS element is missing for an inheriting CDS role. It defines
whether the using condition will be logically interpreted as true or false.
To better understand this concept, let’s assume that our inherited CDS role ZI_SalesOrder
from Listing 5.2 will be enhanced by the additional access conditions defined in Listing 5.4.
Such an extension would make inheriting CDS role definitions such as ZC_SalesOrder from
Listing 5.23 become technically inconsistent because protected CDS view ZC_SalesOrder from
Listing 5.21 doesn’t contain the newly introduced fields OrganizationDivision,
SalesOrganization, and DistributionChannel. If the introduction of these fields in CDS view
ZC_SalesOrder isn’t intended temporarily and its access control will remain unchanged, you
can define these elements as optional elements with the default value true in the inherited
CDS role, as shown in Listing 5.33.
@MappingRole: true
define role ZI_SalesOrder {
grant select on ZI_SalesOrder
with optional elements ( OrganizationDivision default true,
SalesOrganization default true,
DistributionChannel default true
)
where ( SalesOrderType ) =
aspect pfcg_auth ( V_VBAK_AAT,
AUART,
ACTVT = '03' )
and ( OrganizationDivision,
SalesOrganization,
DistributionChannel ) =
aspect pfcg_auth ( V_VBAK_VKO,
SPART,
VKORG,
VTWEG,
ACTVT = '03' )
;
}
Listing 5.33
CDS Role with Optional Elements
As a result of this addition, inheriting CDS role ZC_SalesOrder from Listing 5.23 remains
consistent. Warnings are shown in the ADT editor (see Figure 5.6) because optional elements
in general may indicate some missing adaptions of the protected CDS entities.
Figure 5.6
Warnings in CDS Roles Caused by Nonexistent Optional Elements
If you want to exclude access conditions long term from being inherited, you should adapt
your CDS role definition accordingly. For this purpose, you can leverage syntax elements
replacing conditions with. Listing 5.34 illustrates the application of this replacement
functionality in CDS role ZC_SalesOrder.
@MappingRole: true
define role ZC_SalesOrder {
grant select on ZC_SalesOrder
where inheriting conditions from entity ZI_SalesOrder
replacing { conditions on any of ( SalesOrganization,
OrganizationDivision,
DistributionChannel )
with void };
}
Listing 5.34
CDS Role with Access Conditions Excluded from Inheritance Logic
In the given example, the access conditions defined for fields SalesOrganization,
OrganizationDivision, and DistributionChannel are replaced by void. Thus, the
corresponding access conditions are suppressed, and the warnings shown previously in
Figure 5.6 disappear.
Renamed Elements
You learned that the replacing with statement can be used for technically decoupling the
inheriting CDS role from its inherited CDS role (refer to Listing 5.27) as well as for disabling
inherited access conditions (refer to Listing 5.34). However, the corresponding functionality
may also be useful if you renamed the elements used within the inherited access conditions,
as illustrated by CDS view ZC_SalesOrderWithRenamedFields from Listing 5.35.
@AccessControl.authorizationCheck: #CHECK
define view entity ZC_SalesOrderWithRenamedFields
as select from ZI_SalesOrder
{
key SalesOrder
as RenamedSalesOrder,
SalesOrderType as RenamedSalesOrderType
}
Listing 5.35
CDS View ZC_SalesOrderWithRenamedFields
Here, field SalesOrderType (used by CDS role ZI_SalesOrder from Listing 5.2) is renamed to
RenamedSalesOrderType. To apply the inheritance mechanism, you must establish a mapping of
these field names using the replacing with statement, as shown in Listing 5.36.
@MappingRole: true
define role ZC_SalesOrderWithRenamedFields {
grant select on ZC_SalesOrderWithRenamedFields
where inherit ZI_SalesOrder for grant select on ZI_SalesOrder
replacing { element SalesOrderType with RenamedSalesOrderType }
;
}
Listing 5.36
5.3.3
CDS Role ZC_SalesOrderWithRenamedFields
Implement Access Controls without Using Authorization Objects
Besides applying Transaction PFCG–based authorization checks, CDS role logic also offers
you other options for implementing the envisioned authorization enforcements for all CDS
access controls or parts thereof. We’ll present these options in the following subsections.
Access Controls with Direct Reference to the User
In the CDS role definitions, you can directly evaluate the logged-on user. Syntax element
aspect user allows you to reference the user in the access conditions that you define.
Listing 5.37 illustrates an example in which users are authorized to read the sales orders from
CDS view ZC_SalesOrder, which they created (documented by field CreatedByUser).
@MappingRole: true
define role ZC_SalesOrderCreatedByMe {
grant select on ZC_SalesOrder
where CreatedByUser = aspect user;
}
Listing 5.37
CDS Role with Direct Reference to the User
As an alternative to this modeling, you can create dedicated CDS view
ZC_SalesOrderCreatedByMe in which the applied where condition filters the records that match
the user given by session variable $session.user, as shown in Listing 5.38.
@AccessControl.authorizationCheck: #NOT_REQUIRED
define view entity ZC_SalesOrderCreatedByMe
as select from ZC_SalesOrder
{
key SalesOrder,
SalesOrderType
}
where CreatedByUser = $session.user
Listing 5.38
CDS View with User-Dependent Filtering
The main difference between the two presented approaches is that the restrictions resulting
from CDS role ZC_SalesOrderCreatedByMe only apply to direct data selections from CDS view
ZC_SalesOrder in ABAP. In contrast, the where filter, which is captured in CDS view
ZC_SalesOrderCreatedByMe, is applied for each selection from this CDS view. This is especially
the case if CDS view ZC_SalesOrderCreatedByMe is reused as a data source by another CDS
view.
Access Controls Using Self-Defined Aspects
In some cases, you may want to apply an access control based on your own entity that holds
user-specific information. In such cases, you can evaluate the usage of self-defined aspects.
Besides the protected CDS entity and its CDS role, you have to define the CDS entity
providing the user-specific settings and the self-defined aspect itself, which is illustrated in the
following example.
Let’s assume you have a list of users who are responsible for dedicated sales order types.
This list will become the foundation for controlling the access to sales orders. That is, users
will be allowed to select data from sales orders types for which they are responsible.
Listing 5.39 shows an example.
@AccessControl.auditing.specification: '...'
@AccessControl.auditing.type: #CUSTOM
define view entity Z_ViewAsDataSourceForDclAspect
as select distinct from t000
{
key cast( abap.char'USER_A' as vdm_userid ) as UserID,
abap.char'TAF'
as SalesOrderType
}
union select distinct from t000
{
key abap.char'USER_B' as UserID,
abap.char'OAF'
as SalesOrderType
}
Listing 5.39
CDS View with User-Specific Settings
Here, CDS view Z_ViewAsDataSourceForDclAspect returns two records. The first record
specifies that user USER_A is assigned the sales order type TAF, whereas user USER_B is
assigned the sales order type OAF.
To enable the CDS view for usage in self-defined aspects, it needs to be equipped with
annotations @AccessControl.auditing.specification and @AccessControl.auditing.type.
These annotations will define how the records of the annotated CDS view can be audited.
[ ! ] Audit Requirements
It must be possible to reconstruct the authorizations of a user for any point in time. When
using self-defined aspects, it’s your responsibility to ensure that the data provisioned by the
CDS entities used in your self-defined aspects meets the corresponding requirements. This
isn’t automatically ensured by the ABAP infrastructure! In other words, you actively have to
take suitable measures to achieve compliance. If you can’t guarantee this, you must not
introduce self-defined aspects.
This definition will be used as the data source for the self-defined aspect Z_DCLASPECT shown
in Listing 5.40.
define accesspolicy Z_DCLASPECT {
define aspect Z_DCLASPECT as
select from Z_ViewAsDataSourceForDclAspect
with user element UserID
{
SalesOrderType
}
}
Listing 5.40
Self-Defined Aspect
Self-defined aspects are defined similar to CDS roles within DCLS objects. They are
encapsulated by the wrapping statement define accesspolicy...{...}. The aspect itself
defines a select statement on the CDS entity, which provides the user-specific information
and lists those fields that will be applicable for comparisons in the CDS roles. The field that
will be mapped onto the logged-on user has to be specified after the key words with user
element. In our example, CDS view Z_ViewAsDataSourceForDclAspect from Listing 5.39 acts as
the data source. Its field UserID is associated to the logged-on user, and its field
SalesOrderType is projected for defining access conditions.
CDS role Z_ViewWithDclAspect shown in Listing 5.41 leverages aspect Z_DclAspect for
defining access restrictions for CDS view Z_ViewWithDclAspect from Listing 5.42.
@MappingRole: true
define role Z_ViewWithDclAspect {
grant select on Z_ViewWithDclAspect
where
( SalesOrderType ) = aspect Z_DclAspect;
}
Listing 5.41
CDS Role with Self-Defined Aspect
It binds field SalesOrderType of CDS view Z_ViewWithDclAspect to field SalesOrderType of
aspect Z_DclAspect, which in turn is bound to field SalesOrderType of CDS view
Z_ViewAsDataSourceForDclAspect.
define view entity Z_ViewWithDclAspect
as select distinct from t000
{
key abap.char'S1' as SalesOrder,
abap.char'TAF' as SalesOrderType
} union all select distinct from t000
{
key abap.char'S2' as SalesOrder,
abap.char'TAF' as SalesOrderType
} union all select distinct from t000
{
key abap.char'S3' as SalesOrder,
abap.char'OAF' as SalesOrderType
}
Listing 5.42
CDS View Protected by CDS Role with Self-Defined Aspect
CDS view Z_ViewWithDclAspect from Listing 5.42 specifies three records. Two of them are
assigned to sales order type TAF, and one is assigned to sales order type OAF. Accordingly,
based on the definition of CDS view Z_ViewAsDataSourceForDclAspect, user USER_A will be able
to access sales orders S1 and S2, whereas USER_B will be able to access sales order S3.
Access Controls Using Literal Values
You can also use constant literal values for defining access conditions that won’t depend on
the logged-on user. These user-independent conditions are mainly suitable for granting all
users access to certain data records or for locking access for all users by default. In the latter
case, a special addition to the select statement in ABAP is required to still read the affected
data records (Section 5.3.8).
Usually, user-independent access conditions are combined with user-dependent access
conditions. Listing 5.43 shows an example.
@MappingRole: true
define role ZI_SalesOrder {
grant select on ZI_SalesOrder
where ( SalesOrderType ) =
aspect pfcg_auth ( V_VBAK_AAT,
AUART,
ACTVT = '03' )
or SalesOrderType = '';
}
Listing 5.43
CDS Role with User-Dependent and User-Independent Access Conditions
CDS role ZI_SalesOrder from Listing 5.43 grants all users access to the sales order records
whose field SalesOrderType has an initial value. The access rights to other sales order records
are controlled by the authorizations of the individual users.
In some cases, it may be necessary to define authorization conditions that not only check the
equivalence of a field value with a constant value but also check for initial and null values in
parallel. Operator ?= allows you to model such conditions in your CDS role definition
efficiently.
[»] Consider Implications of Operator ?=
When using operator ?=, the corresponding access condition effectively contains three
comparisons that may influence the performance of the data selection. If a comparison with
the null value or initial value isn’t required, you should not use operator ?=, but explicitly
implement the necessary comparisons instead.
Listing 5.44 and Listing 5.45 show examples of alternative implementations of CDS role
ZI_SalesOrderItemForAnyUser, which both have the same effect.
In Listing 5.44, three access conditions are entered explicitly using path expressions
_SalesOrder.SalesOrderType=….
@MappingRole: true
define role ZI_SalesOrderItemForAnyUser {
grant select on ZI_SalesOrderItem
where _SalesOrder.SalesOrderType = ''
or _SalesOrder.SalesOrderType is null
or _SalesOrder.SalesOrderType = 'TAF';
}
Listing 5.44
CDS Role without Operator ?=
Listing 5.45 expresses the same logic with a single condition using operator ?=.
@MappingRole: true
define role ZC_SalesOrderItemForAnyUser {
grant select on ZI_SalesOrderItem
where _SalesOrder.SalesOrderType ?= 'TAF';
}
Listing 5.45
5.3.4
CDS Role with Operator ?=
Implement Access Controls for Analytical Queries
Analytical queries are defined by CDS views, which are annotated with @Analytics.query:true
or are assigned to CDS provider contract analytical_query. These CDS views are interpreted
by the analytic engine for realizing the functionality of the corresponding queries. The analytic
engine doesn’t perform direct selections from these query CDS views themselves. Instead,
the query CDS views only serve as carriers for proprietary analytical metadata. At runtime, the
analytic engine selects the data directly from the underlying data sources of the query views.
Therefore, the CDS roles of the CDS views, which represent analytical queries, have no effect
when leveraging the query CDS views via the analytical infrastructure. Of course, if there are
direct selections from the query CDS views via the ABAP SQL interface, their CDS roles
become effective too. However, such consumption scenarios aren’t intended for analytical
query CDS views. Therefore, analytical queries can also be defined as transient CDS views.
To establish an effective CDS access control for an analytical consumption scenario, the
primary data source, which is directly accessed (select from or projection on) by the query
CDS view, must be protected. Besides this primary data source, the analytic engine may
access dimensions, value help entities, texts, hierarchies, and hierarchy directories in the
context of the query execution. Whether the various accesses to these CDS models are
executed with or without considering their CDS access controls depends on their actual
usages. Figure 5.7 illustrates these dependencies schematically. For more details regarding
the analytical network in general, see Chapter 10.
Figure 5.7
Analytical Query with Its Data Sources and CDS Roles
Our example depicts the following authorization checks:
Primary data sources
The query CDS view selects data from a cube or dimension view (@Analytics.dataCategory
:#CUBE or #DIMENSION), which acts as its primary data source. Whereas the CDS role of the
query doesn’t have any effect for the consumer of the query functionality, the CDS role of
the cube or dimension view determines which records the user is allowed to access.
Display attributes
If fields of the associated dimension views are included as display attributes in the result set
of the analytical query, their data is always read privileged; that is, the illustrated CDS roles
of the associated dimension views have no effect in this context.
Value help and lookup entities
However, dimension views may also be used as value help providers within an analytical
query: when executing the value help functionality for query variables, the CDS roles of the
assigned dimension views are evaluated. Similarly, the CDS roles of CDS views, which act
as either dedicated value help providers (@Consumption.valueHelpDefinition …) or lookup
entities (@Consumption.deriviation.lookupEntity …), are effective.
Value help on query results
If the value help is performed on the query result, the value list is constructed in a two stepapproach by default. First, the accessible field values of the primary data source of the
query are determined by taking its CDS role into account. Second, the identified field values
are used for filtering the records of the associated dimension view, which is accessed
without considering its CDS role.
Hierarchies and hierarchy directories
The CDS roles of hierarchy views (@ObjectModel.dataCategory : #HIERARCHY) have no
impact on their consuming queries. In contrast, the CDS roles of their assigned hierarchy
directories (@Hierarchy.parentChild. directory …) restrict the value help functionality for
hierarchies as well as the records the user is allowed to access via consuming query views.
Texts
For hierarchy directories, the CDS roles of their assigned texts (@ObjectModel.dataCategory
: #TEXT) are evaluated too. This is different compared to the texts of other CDS views that
are used within the analytical network. These texts, like the texts of dimensions, are read
privileged; that is, their own CDS roles are ignored by the analytic engine.
Reused CDS views
Note that unlike the query CDS view, which is to be accessed exclusively via the analytical
infrastructure, the analytical network may comprise CDS models, which are reused in other
contexts too. The affected reused CDS models may require suitable protection by CDS
access controls, even if the corresponding CDS roles are ignored when accessing them via
the analytic engine.
5.3.5
Implement Access Controls for Transactional Applications
CDS access controls are used for read access within transactional applications too. In the
following subsections, you’ll learn specific rules you need to adhere to when defining CDS
access controls for transactional CDS projection views as well as when protecting data
selections from draft documents. Detailed information about the usage of CDS models for
implementation of transactional applications is presented in Chapter 11.
CDS Projection Views
Data models that you define for your transactional applications are subject to several
consistency constraints. In general, the authorization enforcement is defined in the CDS
views, which act as the fundamental data and behavior models of your business objects. On
top of this core functionality, you can implement projections that adapt the core functionality to
specific use cases. In this context, access rules are inherited directly from the underlying CDS
entities. A schematic example of related dependencies is illustrated by CDS views from
Listing 5.46 to Listing 5.48 and their assigned CDS roles from Listing 5.49 to Listing 5.51.
Listing 5.46 shows CDS view Z_ViewWithProviderContractA, which defines the basic data
model of the application.
@AccessControl.authorizationCheck: #CHECK
define root view entity Z_ViewWithProviderContractA
as select distinct from t000
{
key abap.char'A' as KeyField
}
Listing 5.46
CDS View Z_ViewWithProviderContractA
Listing 5.47 illustrates CDS projection view Z_ViewWithProviderContractB, which is based on
CDS view Z_ViewWithProviderContractA and assigned CDS provider contract
transactional_interface.
@AccessControl.authorizationCheck: #CHECK
define root view entity Z_ViewWithProviderContractB
provider contract transactional_interface
as projection on Z_ViewWithProviderContractA
{
key KeyField
}
Listing 5.47
CDS Projection View Z_ViewWithProviderContractB
CDS projection view Z_ViewWithProviderContractB itself acts as data source for CDS
projection view Z_ViewWithProviderContractC from Listing 5.48. CDS projection view
Z_ViewWithProviderContractC underlies CDS provider contract transactional_query.
@AccessControl.authorizationCheck: #CHECK
define root view entity Z_ViewWithProviderContractC
provider contract transactional_query
as projection on Z_ViewWithProviderContractB
{
key KeyField
}
Listing 5.48
CDS Projection View Z_ViewWithProviderContractC
CDS role Z_ViewWithProviderContractA from Listing 5.49 protects the equally named CDS
view from Listing 5.46, shown earlier.
@MappingRole: true
define role Z_ViewWithProviderContractA {
grant select on Z_ViewWithProviderContractA
where KeyField = 'A';
}
Listing 5.49
CDS Role Z_ViewWithProviderContractA
Similarly, CDS role Z_ViewWithProviderContractB from Listing 5.50 protects equally named
CDS projection view from Listing 5.47. Its definition leverages the inheritance mechanism for
access conditions from underlying CDS view Z_ViewWithProviderContractA. You’ll notice that
there are error messages issued by the ADT editor if you try to redefine CDS role
Z_ViewWithProviderContractB, for example, by directly entering the access conditions from
Listing 5.49. In other words, you’re forced to apply the inheritance mechanism.
@MappingRole: true
define role Z_ViewWithProviderContractB {
grant select on Z_ViewWithProviderContractB
where inheriting conditions from entity
Z_ViewWithProviderContractA;
}
Listing 5.50
CDS Role Z_ViewWithProviderContractB
This enforcement also applies to CDS role Z_ViewWithProviderContractC from Listing 5.51
protecting equally CDS projection view from Listing 5.48. It inherits the access conditions from
directly underlying CDS role Z_ViewWithProviderContractB. Inheriting the access conditions
from fundamental CDS role Z_ViewWithProviderContractA isn’t allowed.
@MappingRole: true
define role Z_ViewWithProviderContractC {
grant select on Z_ViewWithProviderContractC
where inheriting conditions from entity
Z_ViewWithProviderContractB;
}
Listing 5.51
CDS Role Z_ViewWithProviderContractC
Draft Documents
In transactional applications, temporary states of data can be saved as draft documents next
to active data. Draft documents capture the entire data of a business object instance. By
default, users who are allowed to access root node instances of an active business object
instance are also granted access to the data of the corresponding root node and all its
associated compositional item instances in draft documents.
If inside the compositional hierarchy of a business object, the CDS access controls vary
between the different nodes of the business object, and these different access restrictions will
be retained in the draft document, you must take dedicated actions: To realize distinct access
rules, you must define CDS views with plain selects from the draft tables, which expose all
their fields, keeping their original names. For these CDS views, you then need to implement
CDS roles with the envisioned access rules. Finally, you need to register these CDS views in
the behavior definition of your business object as draft query views.
Listing 5.52 shows a schematic example where the behavior for CDS view R_<Entity>TP is
enriched with draft query view R_<Entity>Draft for draft table <DRAFT_ENTITY_TABLE>.
define behavior for R_<Entity>TP ...
draft table <DRAFT_ENTITY_TABLE> query R_<Entity>Draft
...
Listing 5.52
5.3.6
Behavior Definition with Draft Query View
Implement Access Controls on the Field Level
CDS access controls allow you to restrict access to complete data records of selection results
from CDS models. A more detailed authorization control at the field level currently isn’t
supported by the CDS access controls. If you require such fine granular control of
authorizations for your use case, you must define additional CDS models with dedicated
projection lists and then assign appropriate CDS access controls to them.
You can set up these additional CDS models so that they include only those fields for which a
specialized CDS access control is required. The field list of the existing CDS model is then
reduced according to those fields for which a generalized access control is sufficient. In other
words, the refinement of this CDS view must anticipate which access variants should be
supported. You can then associate the new CDS models from the generally accessible CDS
model.
Alternatively, you can define a separate CDS model per individual user group to be
differentiated. Each CDS model contains all the fields of the existing CDS model that will be
accessible with a given authorization. Access to these dedicated CDS models is then
assigned to the authorized users and replaces their access to the existing CDS model.
[»] Authorization Check for Extension Fields
You can’t define dedicated CDS access controls for CDS extended views. Instead, you
must define the CDS role of the enhanced CDS view in such a way that it also provides
adequate authorization protection for the extension fields. If the established authorization
control of the extended CDS view isn’t sufficient, you should relocate the extension fields
into a separate CDS view and equip this new CDS view with a tailored CDS role.
5.3.7
Change Access Controls of SAP-Delivered CDS Models
You can’t change the CDS role definitions delivered by SAP to suit your requirements without
applying technical modifications. However, by defining an additional CDS role, you can
effectively change the existing access control of an SAP CDS model. From a functional
perspective, two mechanisms are supported for adapting a CDS role via another CDS role:
Replace existing CDS access controls.
Merge the logic of your CDS roles with existing CDS roles.
Only the CDS models that are annotated with @AccessControl.authorizationCheck:
#NOT_ALLOWED represent an exception in that this annotation effectively prevents the application
of CDS access controls at runtime. As a result, an additional CDS role doesn’t have any
effect.
When redefining a CDS access control by using syntax element redefinition, you replace its
logic completely, as demonstrated in Listing 5.53.
@MappingRole: true
define role ZC_SalesOrderRedefined {
grant select on ZC_SalesOrder
redefinition
where SalesOrderType = 'TAF';
}
Listing 5.53
Replace a CDS Access Control
Here, the access protection of CDS view ZC_SalesOrder is changed in such a way that users
may access all data records with the sales order type TAF. All other access restrictions of CDS
view ZC_SalesOrder are disabled.
Instead of replacing the existing access protection of a CDS model, you may also incorporate
it completely in your redefined CDS role. You can achieve this by using statement inheriting
conditions from super, as shown in Listing 5.54.
@MappingRole: true
define role ZC_SalesOrderRedefined {
grant select on ZC_SalesOrder
redefinition
where inheriting conditions from super
and SalesOrderType = 'TAF';
}
Listing 5.54
Incorporate and Redefine a CDS Access Control
According to the CDS role ZC_SalesOrderRedefined definition from Listing 5.54, the selection
result from CDS view ZC_SalesOrder will only contain those records for which the user was
originally authorized (based on the redefined CDS access control) and which also have sales
order type TAF.
[ ! ] Only Single Redefinitions
You can redefine the CDS access control of a CDS entity only once per operation.
If you define a CDS role for a CDS entity without applying the redefinition option, the access
rules defined within your CDS role are combined by default with the existing access rules by a
logical or. You can make this logical relationship transparent in your CDS role by equipping it
with the statement combination mode or (see Listing 5.55).
@MappingRole: true
define role ZC_SalesOrderCombined {
grant select on ZC_SalesOrder
combination mode or
where SalesOrderType = 'TAF';
}
Listing 5.55
Additional CDS Role Combined with OR
CDS role ZC_SalesOrderCombined from Listing 5.55 grants all users of CDS view ZC_SalesOrder
unrestricted read access to all sales orders records of type TAF. For the remaining records, the
standard access restrictions apply as defined by other CDS roles.
If you want to combine the access rules of your CDS role with existing access rules by a
logical and, you have to enter the statement combination mode and into your CDS role, as
shown in Listing 5.56.
@MappingRole: true
define role ZC_SalesOrderCombined {
grant select on ZC_SalesOrder
combination mode and
where SalesOrderType = 'TAF';
}
Listing 5.56
Additional CDS Role Combined with AND
Here, the access protection of CDS view ZC_SalesOrder becomes more restrictive; that is,
users are no longer allowed to select records of sales order types other than TAF.
With respect to the impact of adding CDS access rules, three cases have to be distinguished:
If you add a CDS role to a CDS entity that previously wasn’t protected, the data selection
becomes subject to an authorization check that may restrict the results list.
If you create a CDS role for a CDS entity that is already protected by another CDS role, and
implicitly or explicitly choose merging them by a logical or, the resulting authorization
enforcement becomes less restrictive.
With a full access rule, that is, a CDS role without a condition, you can even completely
override the authorization check of an already existing or future CDS access control.
Listing 5.57 shows an example in which CDS role ZI_SalesOrderUnrestricted grants all
users read access to all records of CDS view ZI_SalesOrder without an effective restriction
by any other CDS role.
@MappingRole: true
define role ZI_SalesOrderUnrestricted {
grant select on ZI_SalesOrder;
}
Listing 5.57
Full Access Rule for CDS View ZI_SalesOrder
If you combine your CDS role with existing access rules by a logical and, the overall access
control becomes more restrictive.
[ ! ] Change Authorization Checks Consistently
You should only change authorization checks for an SAP-delivered CDS model by adding
your own CDS access controls if you’ve carefully analyzed the impact of your intended
change. Otherwise, this change can result in serious functional problems and data
inconsistencies!
For example, the internal validation logic of an SAP application may assume that it can
select all records of an unprotected CDS model without any authorization-based
restrictions. If you define an access control for this CDS view, depending on the
authorizations of the logged-on user, the logic may only be able to access parts of the data
and thus draw incorrect conclusions. In addition to the mentioned potential functional
problems, there may also be technical restrictions to consider. For example, the inheritance
mechanism for dependent CDS roles may break if you introduce new nonoptional elements.
If in doubt, you should instead define your own CDS models based on the affected CDS
models delivered by SAP. You can then equip your own CDS models with the desired CDS
access controls to avoid unwanted side effects.
5.3.8
Block Standard Data Selections from CDS Models
If a CDS model exposes sensitive data, but a direct selection from this model isn’t required,
you can define an appropriate CDS role that protects this model from standard read accesses.
Extension include views (see Chapter 15) are practical use cases. A direct data selection from
these CDS views in ABAP isn’t expected. Instead of selecting data from these technical
helper views, consumers will select data from the extended CDS views.
To block the data selection, you can introduce a condition in the CDS role definition that can
never be fulfilled. Listing 5.58 shows an example of such a CDS role.
@MappingRole: true
define role ZC_SalesOrderPrivilegedOnly {
grant select on ZC_SalesOrderPrivilegedOnly
where SalesOrder is null
and SalesOrder is not null;
}
Listing 5.58
CDS Role Whose Condition Can’t Be Fulfilled
Authorization condition <field> is null and <field> is not null can never be fulfilled. This can
also be stated explicitly with the simplified where statement illustrated in Listing 5.59.
@MappingRole: true
define role ZC_SalesOrderPrivilegedOnly {
grant select on ZC_SalesOrderPrivilegedOnly
where false;
}
Listing 5.59
Alternative Implementation of a CDS Role Whose Condition Can’t Be Fulfilled
Such a CDS role prevents a regular data selection from protected CDS view
ZC_SalesOrderPrivilegedOnly. For documenting this usage restriction for all its consumers,
you should annotate the protected CDS model according to Listing 5.60.
@AccessControl.authorizationCheck: #PRIVILEGED_ONLY
define view entity ZC_SalesOrderPrivilegedOnly ...
Listing 5.60
CDS View Whose Authorization Protection Doesn’t Allow a Direct Data Selection
If a direct read access to the data from protected CDS model ZC_SalesOrderPrivilegedOnly is
still required, the selection needs to be privileged by adding key words with privileged access
or privileged access to your select statement in ABAP. Listing 5.61 illustrates an example.
SELECT *
FROM zc_salesorderprivilegedonly
WITH PRIVILEGED ACCESS
INTO TABLE @DATA(lt_zc_salesorderprivilegedonly).
Listing 5.61
Privileged Data Selection in ABAP
[»] Privileged Data Selection
You can bypass any CDS access control if you privilege the data selection in your ABAP
implementation. A privileged selection may be necessary, for example, if the CDS model
that serves as the data source for your selection is protected by a regular DCL role, but
your application logic won’t depend on the authorizations of the current user.
As already mentioned, there are two alternatives for privileging the accesses to data sources
in your select statements. Listing 5.61 leverages key words with privileged access. Accesses
to (probably joined) CDS models using this statement are performed without considering their
CDS roles. Accesses to other CDS models within the same data selection keep their CDS
access controls. You can’t privilege path expressions by applying key words with privileged
access. Thus, if you want to access associated fields without restrictions, you must replace
path expressions with privileged joins in your select statements in ABAP.
To achieve complete privileged access, you must add key words privileged access to the end
of your select statement. Applying this statement also privileges accesses via path
expressions. Listing 5.62 illustrates a data selection from Listing 5.61 adapted accordingly.
SELECT *
FROM zc_salesorderprivilegedonly
INTO TABLE @DATA(lt_zc_salesorderprivilegedonly)
PRIVILEGED ACCESS.
Listing 5.62
Completely Privileged Data Selection in ABAP
In your CDS models, you can mark associations whose target records are sufficiently
protected by the authorization checks of the corresponding source records via annotation
@AccessControl.privilegedAssociations. At present, this annotation is only evaluated by the
SADL infrastructure. In SADL-based OData services, the navigation properties that are
mapped onto these privileged associations can be used for retrieving the data of the target
entity sets without applying their own access controls when navigating from their accessible
source entity records.
5.3.9
Decouple Access Controls from User Input
When defining the access conditions within your CDS roles, you must ensure that the
authorization check can’t be circumvented by the interactions of the users. For example, a
security risk can result from using parameters or fields in access conditions whose values
originate from unchecked external input, as explained by the following example.
CDS view ZC_SalesOrderWithUserInput from Listing 5.63 exposes its parameter P_UserInput
as field UserInput.
@AccessControl.authorizationCheck: #CHECK
define view entity ZC_SalesOrderWithUserInput
with parameters
P_UserInput : abap.char(4)
as select from ZI_SalesOrder
{
key SalesOrder,
$parameters.P_UserInput as UserInput
}
Listing 5.63
CDS View with an Included Parameter Value
CDS view ZC_SalesOrderWithUserInput is protected by the equally named CDS role
ZC_SalesOrderWithUserInput from Listing 5.64.
@MappingRole: true
define role ZC_SalesOrderWithUserInput {
grant select on ZC_SalesOrderWithUserInput
where ( UserInput ) =
aspect pfcg_auth ( V_VBAK_AAT,
AUART,
ACTVT = '03' );
}
Listing 5.64
CDS Role ZC_SalesOrderWithUserInput
This CDS role leverages field UserInput associated to the CDS view parameter for defining its
access rule. If the user has read permissions for sales orders of type TAF and is able to
formulate a selection request according to Listing 5.65, the user can effectively access all
sales orders without any restriction.
SELECT *
FROM zc_salesorderwithuserinput( p_userinput = 'TAF' )
INTO TABLE @DATA(lt_sales_order_with_user_input).
Listing 5.65
Selection from CDS View ZC_SalesOrderWithUserInput in ABAP
This highly simplified example is designed to raise awareness for issues that might occur
when defining your CDS roles.
[ ! ] Check Data Input
Only use trusted data sources or data that is sufficiently validated when defining access
conditions.
5.3.10 Map CDS Fields onto Fields of Authorization Objects Using
Indirection
You can establish direct mappings between fields of the protected CDS entities with fields of
authorization objects by using aspect pfcg_auth, as shown in the various sample CDS roles
discussed so far. However, there are cases in which such a direct correlation doesn’t exist.
For example, if a user is authorized based on a hierarchically organized access policy, the
records of the CDS entities to be accessed may not contain the matching hierarchy
information. Instead, a dedicated access policy with a pfcg_mapping needs to be defined,
which includes another CDS entity that allows binding the fields of the protected CDS entity
onto the fields of the authorization object. The pfcg_mapping itself is then used to specify the
required bindings within aspect pfcg_auth statements.
For further details, look at the content shipped by SAP S/4HANA. For example, you find
corresponding access policy mappings for cost center hierarchies (I_CostCenterHierAuth) and
profit center hierarchies (I_ProfitCenterHierAuth).
5.4
Test Access Controls
You should test the functionality of your CDS access controls in the
context of the services that expose the protected CDS models.
These integration tests, for example, should include your analytical
applications and OData services. Ideally, all the functions and
selection options available to the consumers of your services should
be covered by your tests.
Checks of the CDS access controls should in particular answer the
following questions:
Is a user actually able to select all data records as expected
according to the granted authorizations?
Is a user allowed to select more data records than permitted by
the granted authorizations?
To identify regressions more easily, your tests should preferably be
automated. In this context, you may benefit from the test automation
for CDS models (see Chapter 16). If test automation isn’t feasible,
you must perform your tests manually.
You can execute manual tests of the CDS access controls at the
level of the individual protected CDS models by using Transaction
SACMSEL. This transaction allows you to select data from a CDS
model and analyze the effects of the user’s authorizations on the
selection result.
Figure 5.8 shows the initial screen of Transaction SACMSEL where
you first specify the CDS model to be tested. Furthermore, you need
to define the user for which you want to perform the evaluation. In
our case, we want to test the data selection from CDS view
ZI_SalesOrder by user DCL_TESTER.
Figure 5.8
Entry Screen of the Test Environment for CDS Access Controls
In addition, you have to choose the enforcement layer on which the
CDS access controls will be applied. From a tester’s perspective,
two layers are important.
When selecting the None (show CDS Access Restrictions only)
option in the Enforcement Layer for CDS Access Restrictions
area of the screen, there is no effective runtime selection performed.
Instead, the CDS access control infrastructure interprets the
authorizations of the users and provides an overview of the result of
its evaluation. You can trigger the simulation by pressing (F8). The
system opens the overview screen shown in Figure 5.9.
The displayed overview screen contains the following information:
CDS Access Restrictions / SQL Trace
Contains information about how the access control would be
applied, for example, via a where clause.
Matching Access Controls
Contains the relevant CDS roles.
Authorizations of User for PFCG-Aspects
Contains the relevant authorizations assigned to the user.
Figure 5.9
Simulation Result for Enforcement Layer None
When selecting the Database Interface option in the Enforcement
Layer for CDS Access Restrictions area, a data selection for the
given user is being performed with the productive implementation of
the access control infrastructure injecting the user’s access
restrictions. This requires you to also specify the password of the
user for whom you want to perform the analysis, if the specified user
differs from the logged-on user.
By pressing (F8), you can start the simulation. Once finished, an
overview screen opens (see Figure 5.10).
Figure 5.10
Simulation Result for Enforcement Layer Database Interface
In this screen, you find the additional Result section that shows
which records were selected, that is, which records the user has
access to. In addition, you find SQL trace information for the
performed data selection in the CDS Access Restrictions/SQL
Trace section. If tracing isn’t feasible or if the trace information can’t
be determined, this section provides you with instructions about how
you can perform a corresponding analysis.
You should check whether the individual aspects of the simulation
results meet your expectations. You can increase the test coverage
by varying the user’s authorizations. You can correct errors by
adjusting the CDS models or CDS role definitions and retest them by
repeating the data selections.
5.5
Summary
CDS access controls allow you to define instance-based
authorization checks, which are executed automatically on the
database level when selecting data from the protected CDS model in
ABAP. You learned how to implement CDS access controls tailored
for a given use case by leveraging suitable implementation patterns.
We also discussed how to make CDS access controls ineffective by
applying privileged data selection in ABAP. Finally, you learned how
to test the functionality of CDS access controls.
In the next chapter, you’ll learn how you can create business
services that allow you to access the CDS functionality from outside
the ABAP system.
6
Business Services
In this chapter, you’ll get insights into the modeling of business
services. Business services allow you to expose your core
data services (CDS) models to system-external consumers
such as SAP Fiori user interface (UI) applications and A2X
service consumers.
The ABAP infrastructure supports you in exposing the functionality of
your CDS models in external services. In this context, the CDS
models may define both the metadata as well as the standard data
provisioning logic of the derived service entities.
To tailor your CDS models for an intended consumption scenario,
you should define corresponding service-specific views. Technically,
these views can be realized as standard CDS view entities, which
you’ve learned about in previous chapters. In the context of the
ABAP RESTful application programming model, dedicated
projection-type CDS views were also introduced for this purpose.
These projection views are primarily aimed at supporting an
automated delegation of the transactional processing logic through
the CDS view stack. In Section 6.1, you’ll learn how such projection
views can be defined.
Based on the consumption-specific CDS models, you can define a
service definition. The service definition bundles the CDS models
that will be exposed in a single business service; that is, the service
definition describes the scope of a service. Section 6.2 provides you
with an overview of such service definitions.
With the help of service bindings, you define the envisioned usages
as well as the protocol versions of your business services. For
example, you can expose your service definition as a UI service
based on OData protocol V4. Alternatively, you may expose your
business service, for example, as a Web API service. Service
bindings will be discussed in detail in Section 6.3.
[»] Advantages of Business Services
In comparison to other variants of exposing CDS models as
services, business services provide significant advantages.
When auto-exposing CDS models via CDS annotation
@OData.publish:true, neither the protocol nor the usage can be
specified. Furthermore, the scope of CDS entities included in the
service can’t be controlled explicitly. Instead, the latter is derived
from a framework-based interpretation of associated and
referenced CDS entities.
When exposing CDS models using reference data source
mappings in SAP Gateway Service Builder (Transaction SEGW)
projects, several ABAP objects get generated. These generated
objects may need to be synchronized manually if the CDS models
were changed. Therefore, you should generate business services
based on the Service Adaptation Definition Language (SADL)
using the service definitions and service bindings presented in this
chapter.
To expose an analytical query view as an OData service, you still
have to use the auto-exposure mechanism; that is, you still have
to annotate the query view with CDS annotation
@OData.publish:true. In this context, you can’t use service
definitions and service bindings.
Depending on the type of the resulting service, different test options
are available. In Section 6.4, we’ll introduce the service URLs and
preview functionality that provides you with manual testing
capabilities for your OData services.
6.1
Projection Views
Projection views represent a special flavor of CDS views. The
purpose of these CDS views is the definition of service interfaces on
top of generalized CDS models. In general, they don’t provide
independent additional functionality. Instead, they compose the
underlying functions in such a way that they can be easily consumed
by consumers of the service interfaces.
There are some differences between regular CDS views and
projection-type CDS views. First, projection views only support
defining simple projections of the underlying data sources. Complex
select statements can’t be realized in projection views. Furthermore,
projection views can’t serve as data sources in view building; that is,
projection views can only be used to implement the uppermost layer
of a view hierarchy. Only projection-type CDS views with CDS
provider contract transactional_interface represent an exception.
They allow defining another layer of projection-type CDS views with
CDS provider contract transactional_query on top of them. You
learn more about CDS provider contracts in Chapter 2, Section 2.19.
Finally, projection views can specify compositional structures only if
they are based on data sources that themselves define
compositional structures (see Chapter 3, Section 3.3). The latter
aspect is illustrated in the following example.
Listing 6.1 and Listing 6.2 provide an example of a composition of
two base CDS views. CDS view ZI_Product in Listing 6.1 defines the
root CDS view of this composition.
define root view entity ZI_Product
as select from zproduct
composition [0..*] of ZI_ProductText as _Text
{
key product
as Product,
product_type
as ProductType,
creation_date_time as CreationDateTime,
_Text
}
Listing 6.1
Base Root CDS View ZI_Product
Listing 6.2 shows the associated compositional child CDS view
ZI_ProductText.
@ObjectModel.dataCategory: #TEXT
define view entity ZI_ProductText
as select from zproducttext
association to parent ZI_Product as _Product
on $projection.Product = _Product.Product
{
key language
as Language,
key product
as Product,
product_name
as ProductName,
_Product
}
Listing 6.2
Base Child CDS View ZI_ProductText
On top of this base composition, a composition implemented by
projection views is defined. The root projection view ZC_Product of
this composition is shown in Listing 6.3.
define root view entity ZC_Product
as projection on ZI_Product
{
@Consumption.valueHelpDefinition: [
{ entity: { name: 'ZI_ProductStdVH',
element: 'Product' } } ]
@ObjectModel.text.element: ['ProductName']
key Product,
ProductType,
@Semantics.text: true
_Text.ProductName : localized,
_Text : redirected to composition child ZC_ProductText
}
Listing 6.3
Projection Root CDS View ZC_Product
This projection is derived from root CDS view ZI_Product from
Listing 6.1 and projects base fields Product and ProductType. In
addition, it projects base composition association _Text. By applying
expression redirected to composition child, original target view
ZI_ProductText is replaced by child projection CDS view
ZC_ProductText from Listing 6.4. Instead of defining this
compositional relationship locally from scratch, the projection CDS
view maps this association explicitly onto the corresponding
compositional relationship of its base CDS view. This is required for
establishing a proper mapping of the association for the transactional
processing logic.
@ObjectModel.dataCategory: #TEXT
define view entity ZC_ProductText
as projection on ZI_ProductText
{
@Semantics.language: true
key Language,
@ObjectModel.text.element: ['ProductName']
key Product,
@Semantics.text: true
ProductName,
_Product : redirected to parent ZC_Product
}
Listing 6.4
Projection Child CDS View ZC_ProductText
Similarly, child projection view ZC_ProductText from Listing 6.4 maps
the projected to parent association _Product of its data source
ZI_ProductText from Listing 6.2 onto composition parent CDS view
ZC_Product using statement redirected to parent.
Because service interfaces often expose language-dependent texts,
projection views support a special function for denormalizing texts.
This is reflected in expression _Text.ProductName : localized of root
projection CDS view ZC_Product from Listing 6.3. Target CDS view
ZC_ProductText of the _Text association holds language-dependent
texts. The infrastructure is instructed by key word localized to
extract text ProductName in the logon language of the user. Contentwise, this is equivalent to applying path expression
_Text[1:Language=$session.system_language].ProductName in a
standard CDS view. However, the syntax variant chosen in the
projection view allows further optimizations of the text handling in the
future. For example, instead of applying the logon language as a
filter, language vectors could be evaluated by the infrastructure for
deriving the text value.
[»] Usage of Projection-Type CDS Views
Service-specific CDS views, which will be enabled for
transactional processing, have to be implemented as CDS views
of type projection. This also holds true for analytical query CDS
views that are assigned the CDS provider contract
analytical_query. The latter must also be defined as transient.
For defining other service functionality, standard CDS view entities
may be used as an alternative. Such CDS views allows you to
implement the projection logic too. They allow you to define the
CDS view logic in a more flexible manner, that is, with less
technical restrictions.
CDS view ZI_ProductStdVH from Listing 6.5 will serve as a value help
provider in a UI service. It’s defined as a regular CDS view.
@ObjectModel.supportedCapabilities: [#VALUE_HELP_PROVIDER]
@ObjectModel.dataCategory: #VALUE_HELP
@Search.searchable: true
define view entity ZI_ProductStdVH
as select from ZI_Product
{
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 0.8
@ObjectModel.text.element: ['ProductName']
key Product,
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 0.8
@Semantics.text: true
_Text[1:Language=$session.system_language].ProductName
}
Listing 6.5
Value Help CDS View ZI_ProductStdVH
CDS view ZI_ProductStdVH selects from base root CDS view
ZI_Product. It’s referenced by CDS view ZC_Product from Listing 6.3
as a value help provider for its field Product, which is annotated with
@Consumption.valueHelpDefinition .... You’ll learn more about value
help views in Chapter 13, Section 13.1.
Note that view ZI_ProductStdVH could also have been defined as a
projection-type CDS view because it’s logically implementing a
projection. Listing 6.6 demonstrates the necessary adaptations of
Listing 6.5 for realizing such a projection-type CDS view, including
the usage of the text denormalization by key word localized.
@ObjectModel.supportedCapabilities: [#VALUE_HELP_PROVIDER]
@ObjectModel.dataCategory: #VALUE_HELP
@Search.searchable: true
define root view entity ZI_ProductStdVH
as projection on ZI_Product
{
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 0.8
@Search.ranking: #HIGH
@ObjectModel.text.element: ['ProductName']
key Product,
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 0.8
@Search.ranking: #HIGH
@Semantics.text: true
_Text.ProductName as ProductName: localized
}
Listing 6.6
Value Help Projection-Type CDS View ZI_ProductStdVH
6.2
Service Definitions
Service definitions determine the set of entities exposed in business
services. Theoretically, these definitions are independent from the
applied service bindings and thus independent of the protocols (e.g.,
OData) and usage scenarios (e.g., UI). Technically, it’s possible to
reuse a single service definition in multiple service bindings and
business services. Practically, however, the service definition is
closely related to the envisioned service binding for meeting the
functional requirements of the business service. Therefore, we
recommend exposing a service definition in a single service only.
You can create service definitions similar to the CDS models via the
creation dialog of the ABAP Development Tools (ADT). The dialog
can be launched, for example, by selecting File • New • Other…
from the main menu. Figure 6.1 shows the popup that appears after
triggering this function. Choose ABAP • Business Services •
Service Definition, and confirm your selection by clicking Next >.
This opens the creation wizard for service definitions.
Figure 6.1
Select the Wizard for Creating a Service Definition
Alternatively, the creation wizard can be launched from the Project
Explorer view. Select the CDS model that you want to expose, and
right-click to open the context menu, as shown in Figure 6.2.
Figure 6.2
Create a Service Definition from Project Explorer
Choose the New Service Definition option to open the creation
wizard shown in Figure 6.3. Note that the previously selected CDS
model ZC_Product and its embedding package
TEST_CDS_REFERENCE_APPLICATION are automatically transferred to the
Exposed Entity and Package input fields.
Now you have to specify the name, description, and source type of
the service definition in the corresponding fields of the same name.
In our example, we want to create a service definition for the display
of products, so we entered the Name “ZUI_Product_Display”,
Description “Product Display”, and Source Type “Definition”, as
illustrated in Figure 6.3.
Figure 6.3
First Step of the Wizard for Creating Service Definitions
[»] Define Naming Conventions for Service Definitions
Note that the names of the service definitions reside in the same
namespace as other ABAP Data Dictionary objects. This implies
that you must not use the same name for a service definition and a
CDS view.
In the virtual data model (VDM) of SAP S/4HANA, service
definitions that will be used for defining UI services will get the
prefix UI_. Service definitions for Information Access (InA) UI
services get the prefix INA_UI_. The name of service definitions
that will serve for defining stable, remote-enabled programming
interfaces will start with the prefix API_. After these prefixes,
typically the semantic name of the main entity of the service is
added. Suitable suffixes help distinguish the service definitions
further and prevent name clashes.
In principle, you can follow these naming conventions when
defining your own services too. However, you should prefix the
resulting names with your own customer namespace.
In the subsequent step of the creation wizard, you can specify the
transport request for transporting the service definition. However,
because you’re working in a local package, you can leave the wizard
by clicking Finish. This opens the text editor for service definitions
with the content shown in Listing 6.7, in which CDS view ZC_Product
from Listing 6.3 is automatically exposed.
@EndUserText.label: 'Product Display'
define service ZUI_Product_Display {
expose ZC_Product;
}
Listing 6.7
Service Definition after Leaving the Creation Wizard
Next, we’ll expose projection view ZC_ProductText from Listing 6.4 in
the newly created service definition too. Furthermore, we rename the
exposed entities to Product and ProductText, respectively, by using
alias function as. The resulting service definition is shown in
Listing 6.8.
define service ZUI_Product_Display {
expose ZC_Product as Product;
expose ZC_ProductText as ProductText;
}
Listing 6.8
Service Definition ZUI_Product_Display
[+] Expose Specialized CDS Entities
We recommend only exposing specialized CDS entities in your
service definitions. The CDS entities will be tailored for your use
case. In particular, they allow you to remove unwanted
functionality from your underlying reused CDS models as well as
to keep your service stable (if required).
As already mentioned, the service definition lists the CDS entities
that will be exposed in the derived service. However, CDS entities
that merely serve for defining value helps usually don’t need to be
listed (Section 6.3.1).
If the service definition from Listing 6.8 is used for creating an OData
service, alias names Product and ProductText define the names of
the OData entity sets. Via these OData entity sets, you can access
the data of the underlying CDS models.
[+] Define Semantic Entity Names
Typically, names of the CDS models contain technically motivated
prefixes and suffixes to make them become unique. Such name
components should not be reused for the names of the entities
exposed by your business services. Instead, speaking semantic
names should be defined if this is supported by the ABAP
infrastructure. For example, a CDS view that is called
C_SlsOrdItmTP_2 could be exposed as an entity called
SalesOrderItem. This increases the readability of the resulting
service functionality. In addition, defining semantic entity names
allows you to replace the exposed CDS models in your service at
a later time with less disruption for consumers.
Note that you still have to prefix the semantic names with your
customer namespace, if you extend a service definition that is
delivered by SAP, to avoid name clashes. Furthermore, you need
to consider that not all the service bindings support alias names
for the exposed entities.
By default, the names of the OData entity types that define the
structure of the OData entity sets are composed from the names of
the OData entity sets and suffix Type. The OData entity types and
their components, as well as the OData entity properties, share the
same namespace. As a result, name clashes occur if the exposed
CDS models have equally named elements. For example, service
definition ZUI_Product_Display from Listing 6.8 with its referenced
CDS view ZC_Product from Listing 6.3 results in the OData entity type
definition from Listing 6.9. This definition is formally inconsistent
because the name of entity type ProductType matches the name of a
property defined therein.
...
<EntityType Name="ProductType" …>
<Key>
<PropertyRef Name="Product"/>
</Key>
<Property Name="Product" Type="Edm.String" .../>
<Property Name="ProductType" Type="Edm.String" .../>
<Property Name="ProductName" Type="Edm.String" .../>
<NavigationProperty Name="_Text" .../>
</EntityType>
...
Listing 6.9
Inconsistent OData Entity Type Definition
To avoid or resolve such name clashes, you can specify the name of
the OData entity type by annotating the exposed CDS model with
@OData.entityType.name.... Listing 6.10 illustrates an example.
@OData.entityType.name: 'Product_Type'
define root view entity ZC_Product
as projection on ZI_Product
...
Listing 6.10
CDS Entity with Named OData Entity Type
The name of the OData entity type derived from CDS view
ZC_Product is explicitly specified as Product_Type. This allows CDS
view ZC_Product to be exposed with field ProductType while at the
same time renaming the entity set to Product without making the
OData service inconsistent.
[ ! ] Avoid Name Clashes
It’s important that you prevent potential name clashes by applying
consistent naming rules. We recommend that you always annotate
(@OData. entityType.name ...) the name of the OData entity type in
the CDS entities that will be exposed in your OData services. In
SAP S/4HANA, the corresponding name will be composed from
the semantic entity name and suffix _Type.
You can apply the same convention but may need to prefix the
resulting name with your custom namespace.
Besides creating and adapting service definitions, you can also
extend them. As a prerequisite, the service definition to be extended
has to be annotated with
@AbapCatalog.extensibility.extensible:true. Listing 6.11 illustrates
the accordingly adjusted service definition from Listing 6.8.
@AbapCatalog.extensibility.extensible: true
define service ZUI_Product_Display {
expose ZC_Product
as Product;
expose ZC_ProductText as ProductText;
}
Listing 6.11
Service Definition ZUI_Product_Display with Extension Option
You can create extensions of service definitions similar to the service
definitions themselves. In the creation wizard you only have to set
the Source Type to Extension and specify the name of the
extended service definition in the Referenced Object input field. In
our case, we want to create extend ZX_UI_Product_Display for
service definition ZUI_Product_Display. Therein, we want to expose
CDS view ZI_Customer with alias name ZZ1_Customer. Listing 6.12
shows the resulting extension.
extend service ZUI_PRODUCT_DISPLAY with {
expose ZI_Customer as ZZ1_Customer;
}
Listing 6.12
Extension of Service Definition ZUI_Product_Display
6.3
Service Bindings
The service binding defines the protocol and the use case that a
service will support. The entities and their relations (including their
functionality) that a service exposes are derived from the service
definitions, which are assigned to the service binding. We’ll walk
through the steps to create service bindings for both OData UI
services in Section 6.3.1 and OData Web API services in
Section 6.3.2. Furthermore, we’ll briefly introduce you to service
bindings of InA services in Section 6.3.3 and SQL Web API services
in Section 6.3.4.
6.3.1
OData UI Services
You can create a service binding by using the creation dialog of the
ADT environment. The dialog can be opened by selecting File • New
• Other… from the main menu. In the popup that appears, select
ABAP • Business Services • Service Binding (see Figure 6.4).
Click Next > to launch the creation wizard.
Figure 6.4
Select the Wizard for Creating a Service Binding
You can also launch the creation wizard from the Project Explorer
view. Right-click service definition ZUI_Product_Display, and then
select New Service Binding from the context menu that appears.
When using this option, the selected service definition and its
package are automatically transferred to the first step of the wizard,
as shown in Figure 6.5.
Besides the prefilled name of the package (Package) and the
service definition (Service Definition), you have to enter the name
(Name) and description (Description), as well as choose the service
binding type from the Binding Type dropdown. In our case, we want
to create UI service binding ZUI_PRODUCT_DISPLAY, which has the
same name (converted to uppercase) as its assigned service
definition ZUI_Product_Display.
Figure 6.5
First Step of the Wizard for Creating Service Bindings
[+] Naming of Service Bindings
We recommend aligning the name of the service binding with its
referenced service definition to simplify correlating the dependent
development objects.
Like the name of the service binding, the Binding Type selected
from the dropdown menu can’t be changed after leaving the creation
wizard. The admissible values for the service binding types are listed
in Table 6.1.
Binding
Type
Protocol
Description
OData V2 UI
OData V2
UI service
OData V4 UI
OData V4
UI service
OData V2 Web API
OData V2
Remote OData API service
OData V4 Web API
OData V4
Remote OData API service
INA - UI
InA
Analytical query service for
UI consumption
SQL - Web
API
Open Database
Connectivity (ODBC)
Remote SQL API service
Table 6.1
Service Binding Types
Because we want to create a UI service based on OData V4, we
chose OData V4 - UI from the Binding Type dropdown.
[+] Use the Latest OData Protocol Version
There are several functional differences between OData V2 and
OData V4. Furthermore, the metadata of the corresponding OData
services may differ due to changes in the Entity Data Model (EDM)
typing and the applicable naming rules for the service
components. For example, in OData V4 services, you may find
navigation properties whose names start with an underscore. In
OData V2 services, the ABAP infrastructure automatically adds
prefix to to such names to comply with the corresponding OData
standard, which doesn’t allow an underscore to be used as the
first letter. An overview of the OData specifications is provided at
www.odata.org/documentation.
We recommend selecting the OData V4 protocol version to benefit
from the latest OData features.
After you’ve entered the requested information, you can navigate to
the succeeding step of the creation wizard by clicking Next >. In the
next step, you can enter a transport request, which is used for
recording and transporting the applied changes. However, because
you created a local object, you can immediately leave the dialog by
clicking Finish. After closing the creation wizard, a form-based editor
for the service binding opens, as shown in Figure 6.6.
Figure 6.6
Service Binding after Closing the Creation Wizard
In this form-based editor, you find various pieces of information
about the service binding. This information comprises an overview of
the included service versions with their activation statuses. In our
case, the initial version 1.0.0 (i.e., major version 1, minor version 0,
patch version 0) of the business services is displayed. It’s based on
service definition ZUI_Product_Display, which by default determines
service name ZUI_Product_Display. We want to rename this service
to Product. To achieve this, click on the service name
ZUI_PRODUCT_DISPLAY in the table on the left-hand side, and
enter the new name in the Service Name input field on the righthand side, as shown in Figure 6.7.
Figure 6.7
Service Binding after Renaming and Activating the Service
Even after its activation, the business service isn’t yet usable
because there is no published service endpoint (see Local Service
Endpoint: Unpublished in Figure 6.7). You can publish your service
by clicking on the Publish button. Figure 6.8 shows the service
binding after the service endpoint was published locally (note Local
Service Endpoint: Published).
Figure 6.8
Service Binding after Publishing the Service
[»] Publish OData Services
In a productive system, the OData V2–based business services
have to be published explicitly via Transaction
/IWFND/MAINT_SERVICE (Activate and Maintain Services) to be
able to invoke them from a REST client or OData client.
For OData V4 services, the publication is performed using
Transaction /IWFND/V4_ADMIN.
Depending on the setup of the system, OData services are
published either in the backend system (i.e., in a local SAP
Gateway system) or in the frontend server system (i.e., in an SAP
Gateway hub). You can find more information about setting up the
SAP Gateway at http://s-prs.co/v529405.
The Service Version Details section of the published UI service
binding form provides important summarizing information about the
generated OData service. This information comprises the list (Entity
Set and Association) of exposed entities Product and ProductText
of service definition ZUI_Product_Display from Listing 6.8. In addition,
this list illustrates that associations _Text and _Product between the
exposed CDS models are automatically included as OData
navigation properties.
Note that in our UI business service, a value help service based on
value help CDS view ZI_ProductStdVH from Listing 6.5, shown
earlier, is incorporated too, even though this CDS view isn’t explicitly
exposed in service definition ZUI_Product_Display and therefore not
listed in the Service Version Details section. Listing 6.13 shows the
corresponding extract of the service metadata. Therein, you can find
a reference to a dedicated value help service
../../../../srvd_f4/sap/zi_productstdvh/0001….
[»] Different Realizations of Value Helps in OData V2 and V4
Services
In contrast to the depicted OData V4 service, such separated
value help services don’t exist in OData V2 services. Instead, the
entire value help support is defined within the OData V2 service
itself.
...
<Annotations Target="SAP__self.Product_Type/Product">
...
<Annotation Term="SAP__common.ValueListReferences">
<Collection>
<String>../../../../srvd_f4/sap/zi_productstdvh/0001;
ps='srvd-zui_product_display-0001';
va='com.sap.gateway.srvd.zui_product_display.v0001.etzc_product.product'/$metadata
</String>
</Collection>
</Annotation>
</Annotations>
...
Listing 6.13
Value Help Metadata of the OData UI Service
The automated inclusion of value help entities will be explained in
Chapter 13, Section 13.1.
6.3.2
OData Web API Services
Apart from OData UI services, you can also create OData Web API
services. OData Web API services represent external APIs that can
be consumed from remote systems. In contrast to OData UI
services, some aspects of the CDS models aren’t transferred to the
resulting OData Web API services. This will be illustrated by the
OData web API service binding ZAPI_PRODUCT, which you’ll create
based on service definition ZUI_Product_Display. Note that this reuse
of the service definition is just intended for demonstrating differences
in the service binding types; in practical use, you should not reuse UI
service definitions for defining API service bindings.
[»] Define Business Services for Specific Use Cases
The service definition(s) exposed in a service binding support its
envisioned use cases. You shouldn’t rely on the automated
adaptions by the infrastructure. Instead, you should define your
service definitions and their exposed CDS models in accordance
with their usages. In this context, we recommend exposing a
single service definition per service version only.
You can create an OData Web API service binding similar to a UI
service binding. In the creation wizard, you choose OData V4 - Web
API from the Binding Type field. Figure 6.9 shows the service
binding after renaming, activating, and publishing the service.
Figure 6.9
Published OData Web API Service Binding
Note that even though the same service definition,
ZUI_Product_Display, was used for defining OData Web API service
binding ZAPI_PRODUCT and OData UI service binding
ZUI_PRODUCT_DISPLAY, their service URLs differ. This occur not only
because the names of the service bindings deviate but also because
the URLs are composed from varying segments. Furthermore, there
is no preview function offered for the OData Web API service
compared to the OData UI service due to the missing exposure of UI
annotations in Web API services. If you take a closer look at the
metadata of the OData Web API service, you’ll also recognize that
for value help CDS view ZI_ProductStdVH, there is no corresponding
value help service exposed by the service.
6.3.3
InA UI Services
InA UI services expose analytical queries for analytical UI
applications. As a consequence, service definitions used by InA UI
service bindings may only expose analytical query CDS views. In
this context, you should define and use transient analytical query
CDS views for leveraging the entire functionality offered by InA UI
services. The following example will demonstrate various
development objects constituting such an InA UI service.
Analytical cube CDS view ZI_SalesOrderItemCube from Listing 6.14
provides data for sales order items.
@Metadata.ignorePropagatedAnnotations: true
@Analytics.dataCategory: #CUBE
define view entity ZI_SalesOrderItemCube
with parameters
@Consumption.defaultValue: 'EUR'
P_DisplayCurrency : vdm_v_display_currency
as select from
ZI_SalesOrderItem as ITEM
left outer to one join ZI_SalesOrder
as SO
on
SO.SalesOrder = ITEM.SalesOrder
left outer to one join ZI_Product
as PROD on
PROD.Product = ITEM.Product
{
key ITEM.SalesOrder,
key ITEM.SalesOrderItem,
ITEM.Product,…
$parameters.P_DisplayCurrency as DisplayCurrency,
@DefaultAggregation: #SUM
@Semantics.amount.currencyCode: 'DisplayCurrency'
currency_conversion(
amount
=> ITEM.NetAmount,
source_currency
=> ITEM.TransactionCurrency,
target_currency
=> $parameters.P_DisplayCurrency,
exchange_rate_date => ITEM.CreationDate,
exchange_rate_type => 'M',
error_handling
=> 'FAIL_ON_ERROR',
round
=> 'X',
decimal_shift
=> 'X',
decimal_shift_back => 'X'
)
as
NetAmountInDisplayCurrency
}
Listing 6.14
Analytical Cube CDS View
On top of this analytical cube CDS view, transient analytical query
CDS view ZC_SalesOrderProductAmountQ from Listing 6.15 is defined.
@AccessControl.authorizationCheck: #NOT_ALLOWED
@Metadata.ignorePropagatedAnnotations: true
define transient view entity ZC_SalesOrderProductAmountQ
provider contract analytical_query
with parameters
@Consumption.defaultValue: 'EUR'
P_DisplayCurrency : vdm_v_display_currency
as projection on ZI_SalesOrderItemCube( P_DisplayCurrency :
$parameters.P_DisplayCurrency )
{
SalesOrder,
Product,
@Semantics.amount.currencyCode: 'DisplayCurrency'
NetAmountInDisplayCurrency,
DisplayCurrency
}
Listing 6.15
Transient Analytical Query CDS View
This transient analytical query CDS view itself is exposed in service
definition ZUI_INA_SalesOrderProductInfo from Listing 6.16 without
specifying an alias name.
define service ZUI_INA_SalesOrderProductInfo
provider contracts ina {
expose ZC_SalesOrderProductAmountQ;
}
Listing 6.16
Service Definition ZUI_INA_SalesOrderProductInfo
Similar to CDS provider contract analytical_query entered in the
definition of transient analytical query CDS view
ZC_SalesOrderProductAmountQ from Listing 6.15, service definition
ZUI_INA_SalesOrderProductInfo from Listing 6.16 defines service
provider contract ina, classifying its intended usage in an Ina UI
service binding. This classification enables the infrastructure to
perform consistency checks on the service definition level. Related
checks could otherwise only be performed once the service definition
was incorporated into a service binding.
Figure 6.10 illustrates service binding
ZUI_INA_SalesOrderProductInfo of type InA – UI built on top of the
equally named service definition from Listing 6.16 after it was
activated successfully.
Figure 6.10
Service Binding ZUI_INA_SalesOrderProductInfo
As you can see from Figure 6.10, in contrast to the OData services,
there are no dedicated access options to the metadata and
functionality of the activated InA UI service provided by the ADT
environment.
You can find further information about InA UI services, such as hints
regarding access controlling your Ina UI services, in the SAP
documentation at http://s-prs.co/v564200.
6.3.4
SQL Web API Services
SQL Web API services are used for accessing your CDS views via
ODBC connections. In this context, the ABAP application server acts
similar to the SAP HANA database. Further information, including
samples, can be found in the SAP documentation at http://sprs.co/v564201.
6.4
Testing Business Services
You can test your business services in several ways. In this section,
we’ll explain you how you can test your OData services manually
using your browser.
6.4.1
Use OData Service URLs
The published service bindings can be invoked by clicking on the
displayed link in the Service URL field, that is
.../sap/opu/odata4/sap/zui_product_display/srvd/sap/product/00
01/, shown earlier in Figure 6.8 and
.../sap/opu/odata4/sap/zapi_product/srvd_a2x/sap/product/0001/
in Figure 6.9.
You can adjust these service URLs by adding parameters that allow
you to request specific information about the service. For example,
you can request the display of the metadata of the UI service by
using the following request:
.../sap/opu/odata4/sap/zui_product_display/srvd/sap/product/00
01/$metadata. Apart from the metadata, you can also request data
for the individual entity sets of the services. For example, you can
request the data of the UI service product by using the following
request:
.../sap/opu/odata4/sap/zui_product_display/srvd/sap/product/00
01/Product.
By varying the service URLs, you can simulate different accesses to
the functionality of the services and compare their responses with
your expectations.
6.4.2
Use UI Previews
For OData UI services, you can also get a first impression of an SAP
Fiori elements UI by using the Preview function shown earlier in
Figure 6.8. This UI application can be used for manually testing your
OData service too.
[»] UI Definitions
OData services comprise a lot of metadata that can be used for
defining a UI application. With SAP Fiori elements, SAP offers a
template-based approach (see http://s-prs.co/v529406), which
leverages this metadata for implementing SAP Fiori apps
efficiently.
The CDS syntax supports this approach by offering to capture a
plurality of UI-specific metadata by means of CDS annotations.
These CDS annotations are transferred by the ABAP infrastructure
for enriching the OData metadata. UIs, which are based on SAP
Fiori elements, interpret this metadata. Due to this mechanism,
you can create a working SAP Fiori app based on CDS models.
In the following instructions, we’ll show you how to create such an
application. First, you must enrich your CDS projection CDS view
ZC_Product from Listing 6.3 with additional annotations that control
the UI presentation of this CDS model, as illustrated by Listing 6.17.
define root view entity ZC_Product
as projection on ZI_Product
{
@Consumption.valueHelpDefinition: [{
entity: {
name: 'ZI_ProductStdVH',
element: 'Product' } } ]
@EndUserText.label: 'Product'
@ObjectModel.text.element: ['ProductName']
@UI.lineItem: [{ importance: #HIGH }]
@UI.selectionField: [{element: 'Product'}]
key Product,
@EndUserText.label: 'Product Type'
@UI.lineItem: [{ importance: #HIGH }]
ProductType,
@Semantics.text: true
_Text.ProductName : localized,
_Text : redirected to composition child ZC_ProductText
}
Listing 6.17
CDS View ZC_Product with UI-Controlling Annotations
In the given example, the UI will show a table with two columns:
Product and ProductType. This is defined by annotations
@UI.lineItem .... The column captions will get label texts Product
and Product Type, as defined by annotation @EndUserText.label ....
In addition, a filter will be offered for field Product. This is defined by
annotation @UI.selectionField ....
After adjusting and activating CDS view ZC_Product, return to the
form-based display of service binding ZUI_PRODUCT_DISPLAY. In the
Service Version Details section, select the Product entity set. Then
click Preview, as shown earlier in Figure 6.8, to access the screen
shown in Figure 6.11.
Figure 6.11
UI Preview
The UI preview illustrated in Figure 6.11 shows the previously
described elements of the UI service. A value help supports you in
entering filter values for the Product field. In the results list, you’ll
find the Product column next to the Product Type column.
You can use the UI preview for verifying various effects of changing
CDS annotations that control the UI layout.
6.5
Summary
In this chapter, you learned how business services can be
implemented. Based on service-specific CDS views that project the
functionality of the underlying CDS models, you defined a service
definition. On top of the service definition, you defined OData UI and
Web API service bindings. After their publication, you were able to
test them manually by calling their service URLs directly or indirectly
by using the Preview functionality. In addition, you learned
fundamentals about Ina UI and SQL Web services.
In the next chapter, you’ll learn how you can integrate native SAP
HANA functions in your CDS models that may not be supported by
the CDS syntax itself.
7 Native SAP HANA Functions
in CDS
Core data services (CDS) views use the capabilities of SAP
HANA via SQL. As a result, direct access isn’t possible to
some advanced capabilities of SAP HANA. However, table
functions in CDS allow the execution of SAP HANA SQLScript
and can be combined with CDS views to allow usage of native
SAP HANA functionality when defining CDS models.
The SAP HANA relational database system not only can be used via
the structured query language (SQL) but also offers tools and
function libraries for many areas, such as text analysis and search,
processing of geo data and graphs, financial mathematics, predictive
analytics, and machine learning. You can use these capabilities via
the SAP HANA–specific language SQLScript, which combines SQL
and imperative language elements.
Using ABAP-Managed Database Procedures (AMDPs), it’s possible
to execute SAP HANA SQLScript from ABAP while passing data via
input parameters and receiving the results. This method is leveraged
for CDS table functions. Technically, an SAP HANA table function is
created and executed. A CDS table function can be used as the data
source for a CDS view, making it possible to use native capabilities
of SAP HANA in CDS.
Section 7.1 explains with a simple example how you can implement
a complete CDS table function and what you must consider. We also
show how a table function is best combined with other CDS views.
Section 7.2 gives an overview of typical use cases for CDS table
functions, and Section 7.3 concludes the chapter with some hints
and recommendations from engineering practice.
[»] Coverage Constraints
In this book, we can’t cover all aspects of CDS table functions and
AMDP. We therefore concentrate on a practical example that
shows the basics but also some less obvious aspects. In the SAP
documentation for CDS table functions and AMDP functions
(http://s-prs.co/v564202), you’ll find more detailed information.
7.1 Implementation of a CDS Table
Function
In this example, we’ll define a CDS table function that represents a
list of countries, similar to SAP view I_Country. An extract is shown
in Listing 7.1.
@EndUserText.label: 'Country'
@AbapCatalog.sqlViewName: 'IFICOUNTRY'
@ClientHandling.algorithm: #SESSION_VARIABLE
define view I_Country as select from t005
{ key land1 as Country,
intca3 as CountryThreeLetterISOCode,
intcn3 as CountryThreeDigitISOCode
}
Listing 7.1
Extract of View I_Country as Delivered by SAP
We’ll implement a view and a table function in CDS, implement a
class method in ABAP, and generate an SAP HANA table function
(see Figure 7.1).
Figure 7.1
CDS Table Function Objects
We begin with the CDS table function. The following implementation
steps are analogous to the implementation steps of a CDS view (see
Chapter 1, Section 1.2, for more details):
1. Create a new CDS data definition, give it a name (e.g.,
“Z_TableFunctionCountry”), and enter a description (e.g., “Read
Countries via a CDS Table Function”).
2. In the CDS editor, enter the definition of the table function, as
shown in Listing 7.2.
@ClientHandling.type:
#CLIENT_DEPENDENT
@ClientHandling.algorithm: #SESSION_VARIABLE
define table function Z_TableFunctionCountry
with parameters
@Environment.systemField: #CLIENT
P_SAPClient : vdm_v_sap_client
returns
{ mandt
: vdm_v_sap_client;
Country
: land1_gp;
CountryThreeLetterISOCode : intca3;
CountryThreeDigitISOCode : intcn3;
CountryISOCode
: intca;
CountryCurrency
: waers_005;
IndexBasedCurrency
: curin;
HardCurrency
: curha;
TaxCalculationProcedure
: kalsm_d;
}
implemented by method
zcl_table_function_country=>get_countries
Listing 7.2
Definition of a CDS Table Function
3. Activate the table function.
With that, you’ve defined the interface of the table function and
specified some properties that are important for its integration in
CDS. Note the following breakdown of the components:
@ClientHandling.type: #CLIENT_DEPENDENT
@ClientHandling.algorithm: #SESSION_VARIABLE
The first annotation states that this table function has an SAP
client attribute. When the table function is consumed in the ABAP
environment, selection results are limited by ABAP to the SAP
client the user is logged on to. The second annotation chooses an
approach based on session variable CDS_CLIENT, which is set by
the ABAP environment in SAP HANA. This is the standard
approach for handling the SAP client for CDS views and table
functions in SAP S/4HANA, and it’s necessary if your table
function is supposed to use such views.
define table function
You define a CDS table function, not a view or other CDS entity.
Z_TableFunctionCountry
The name of the table function is given in camel case notation for
better readability. The name of the data definition and the table
function should be the same. Both names have a maximum length
of 30 characters.
P_SAPClient
Every client-dependent table function needs an input parameter
for the client. Annotation @Environment.systemField: #CLIENT
ensures that the current client is provided as a parameter when
testing the table function. If the table function is used as a data
source in a CDS view, the parameter must be filled. Usually, CDS
session variable $session.client is used for that.
Further parameters are possible and can be used in SQLScript.
mandt
A field for the client must be explicitly provided in the list of table
function result fields. It must have technical ABAP Data Dictionary
type CLNT, but you could choose a different name.
Country : land1_gp;
In CDS views, the type of a field is usually derived from the data
source the field is derived (projected) from. This is different for
table functions. You must explicitly assign an ABAP data type to
all fields of the result structure.
implemented by method
In the last line of the table function definition, a class method of an
ABAP objects class is given. We’ll create this class method in the
next step. The activation of the table function, or rather of its
interface definition, is already possible though. The class method
will provide the SQLScript implementation of the table function.
[»] Adding Associations and Annotations
In a CDS table function, you can’t define associations. In previous
software versions, you couldn’t even annotate fields. To provide
these important CDS properties, you should wrap every CDS table
function in a CDS view with the same return structure. You can
add associations and annotations to the CDS wrapper view and
should always use the wrapper instead of the table function
directly. Later in this chapter, an example is given in Listing 7.5
and explained.
Now, it’s time to implement the AMDP class method by following
these steps:
1. Create ABAP class ZCL_TABLE_FUNCTION_COUNTRY and class
method get_countries, as shown in Listing 7.3.
CLASS ZCL_TABLE_FUNCTION_COUNTRY DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_amdp_marker_hdb.
CLASS-METHODS get_countries
FOR TABLE FUNCTION Z_TableFunctionCountry.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS ZCL_TABLE_FUNCTION_COUNTRY IMPLEMENTATION.
METHOD get_countries
BY DATABASE FUNCTION FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY
USING I_Country.
RETURN
SELECT
:P_SAPClient as mandt,
Country,
CountryThreeLetterISOCode,
CountryThreeDigitISOCode,
CountryISOCode,
CountryCurrency,
IndexBasedCurrency,
HardCurrency,
TaxCalculationProcedure
FROM
I_Country
WHERE
mandt = :P_SAPClient;
ENDMETHOD.
ENDCLASS.
Listing 7.3
AMDP Class and Class Method with SQLScript
2. Save and activate the class.
With that, you’ve provided the SQLScript code for the SAP HANA
table function. Let’s walk through a breakdown of the components
here as well:
if_amdp_marker_hdb
The class implements interface if_amdp_marker_hdb, which
identifies it as an AMDP implementation.
BY DATABASE FUNCTION FOR HDB LANGUAGE SQLSCRIPT
CDS table functions are always implemented by an SAP HANA
table function with SQLScript. There are also AMDPs that are
implemented by a database procedure. These can be called either
from ABAP or from an AMDP table function, so they are indirectly
accessible from CDS.
OPTIONS READ-ONLY
A table function can read data but never change it. Using a
change operation in the implementation of a table function leads
to an error.
USING I_Country
The implementation uses CDS view I_Country. This is an object
that is created and managed by the ABAP Data Dictionary.
You must declare all database objects deriving from the ABAP
Data Dictionary here, before using them in the SQLScript
implementation of the table function. This includes database
tables and AMDPs. SAP uses this declaration for crossreferencing and ensuring consistent upgrades.
Other database objects, even objects from other database
schemas, can be used without declaration.
RETURN
The SQLScript code follows key word RETURN.
SELECT
The example shows a simple SQL request in native SAP HANA
syntax. The client field is filled from the client parameter, and the
selection is restricted by the WHERE condition to the client provided
by parameter P_SAPClient.
I_Country
In the SAP HANA database, no object with the name I_Country
exists. For CDS views, the corresponding database object is
created with its SQL view name (in our example, IFICOUNTRY), as
you can see by referring to Listing 7.1. In SAP NetWeaver
versions before release 7.53, only the SQL view name could be
used in the SQLScript of a table function, and it had to be
declared in the USING clause. Now the CDS view name can be
used in the table function instead, and the AMDP infrastructure
automatically replaces it with the right name of the database
object for execution on SAP HANA. The situation is even simpler
for CDS view entities, as their corresponding database objects
have the same name, and no substitution is necessary.
In the implementation of the AMDP method, you can directly use all
objects in the database schema of the ABAP system. You can also
access database objects from other schemas by prefixing them with
the schema name in standard SQLScript syntax, for example,
"MY_SCHEMA"."MY_TABLE". AMDP also provides logical schemas to
support cases with changing physical schemas.
[ ! ] Don’t Use SQL Views as Data Sources
With SAP S/4HANA releases 2022 and 2023, SAP started to
convert classical ABAP Data Dictionary-based CDS views to CDS
view entities. In this process, the SQL view is deleted and can’t be
used in a table function implementation anymore. SAP could
convert any ABAP Data Dictionary-based view in the future.
Now your CDS table function is ready for testing in the Eclipse data
preview. Its output, as shown in Figure 7.2, shows the same rows as
CDS view I_Country.
Figure 7.2
List of Countries
You can search for the newly created SAP HANA table function in
the Catalog of SAP HANA studio under the schema of the ABAP
system. Under Functions, you’ll find function
Z_TABLEFUNCTIONCOUNTRY#stub, as shown in Figure 7.3.
Figure 7.3
Catalog of the SAP HANA Database
By clicking on the function name in the Catalog, you can navigate to
and display the Create Statement that shows the function’s SQL
code, as given in Listing 7.4.
CREATE FUNCTION "Z_TABLEFUNCTIONCOUNTRY#stub" (
"P_SAPCLIENT" NVARCHAR (000003)
)
RETURNS TABLE (
"MANDT" NVARCHAR (000003),
"COUNTRY" NVARCHAR (000003),
"COUNTRYTHREELETTERISOCODE" NVARCHAR (000003),
"COUNTRYTHREEDIGITISOCODE" NVARCHAR (000003),
"COUNTRYISOCODE" NVARCHAR (000002),
"COUNTRYCURRENCY" NVARCHAR (000005),
"INDEXBASEDCURRENCY" NVARCHAR (000005),
"HARDCURRENCY" NVARCHAR (000005),
"TAXCALCULATIONPROCEDURE" NVARCHAR (000006)
)
LANGUAGE SQLSCRIPT SQL SECURITY INVOKER AS BEGIN
RETURN SELECT
'000' as "MANDT",
''
as "COUNTRY",
''
as "COUNTRYTHREELETTERISOCODE",
'000' as "COUNTRYTHREEDIGITISOCODE"
''
as "COUNTRYISOCODE",
''
as "COUNTRYCURRENCY",
''
as "INDEXBASEDCURRENCY",
''
as "HARDCURRENCY",
''
as "TAXCALCULATIONPROCEDURE"
FROM "ABAP#DUMMY#header_only" ();
END;
Listing 7.4
Placeholder for the SAP HANA Table Function
You’ll recognize the interface of your table function; however, this is
only a placeholder. On the first execution of the table function, the
ABAP server transfers the real SQLScript code to SAP HANA and
executes it. Afterward, it’s available for subsequent calls.
If you want to execute a table function or a SQL view of a CDS view
that uses a table function as the data source directly in SAP HANA
for testing purposes, make sure that the table function was executed
at least once from ABAP or CDS. This guarantees that the right
SQLScript implementation is loaded to SAP HANA.
In addition, make sure you’re working in the right schema and setting
the right ABAP client in the session variable. Then you can execute
the table function in the SQL Console in SAP HANA, for example, as
follows:
set 'CDS_CLIENT' = '001';
select * from Z_TableFunctionCountry('001');
As you can see, the native execution of the generated SAP HANA
table functions or, similarly, SQL views generated from a CDS view,
requires additional settings. Because technology may change, SAP
only guarantees proper execution of CDS table functions and CDS
views from the ABAP or CDS environment; this includes execution
from an AMDP class method.
Besides mandatory SAP HANA session variable CDS_CLIENT holding
the SAP client, the variables SAP_SYSTEM_DATE with the system date
and LOCALE_SAP with the system language are often needed. Set
them in SAP HANA before executing a table function directly on SAP
HANA, or any other database object related to a CDS or ABAP Data
Dictionary object. Be careful when executing directly on SAP HANA,
as SAP may introduce further session variables or other settings in
the future. The currently used session variables are given in
Table 7.1 (also see Table 2.2 in Chapter 2, Section 2.8).
CDS Session Variable
SAP HANA Session Variable
$session.client
CDS_CLIENT
$session.system_date
SAP_SYSTEM_DATE
$session.system_language LOCALE_SAP
$session.user
APPLICATIONUSER
$session.user_date
USER_DATE
$session.user_timezone
USER_TIMEZONE
Table 7.1
CDS and SAP HANA Session Variables
Two important features of CDS views are missing in the CDS table
function: associations and field annotations. You can neither define
associations directly in the table function itself nor project them from
a data source. Therefore, it’s best to add associations, annotations,
and a key definition in a wrapper CDS view. Create CDS view
Z_CountryViaTableFunction, as shown in Listing 7.5, as a wrapper
view and activate it.
@EndUserText.label: 'Country (via Table Function)'
@Analytics.dataCategory: #DIMENSION
@AbapCatalog.sqlViewName: 'Z_COUNTRYTF'
@ObjectModel.representativeKey: 'Country'
@ClientHandling.algorithm: #SESSION_VARIABLE
define view Z_CountryViaTableFunction
as select from Z_TableFunctionCountry
( P_SAPClient : $session.client )
association [0..*] to I_CountryText as _Text
on $projection.Country = _Text.Country
association [0..1] to I_Currency
as _CountryCurrency
on $projection.CountryCurrency = _CountryCurrency.Currency
{
key Country,
CountryThreeLetterISOCode,
CountryThreeDigitISOCode,
CountryISOCode,
@ObjectModel.foreignKey.association: '_CountryCurrency'
CountryCurrency,
IndexBasedCurrency,
HardCurrency,
TaxCalculationProcedure,
_Text,
_CountryCurrency
}
Listing 7.5
Wrapper View for a CDS Table Function
Take care to assign the session client to the parameter of the table
function:
select from Z_TableFunctionCountry ( P_SAPClient : $session.client )
Now your table function is ready to be used via its wrapper view like
any other CDS view.
7.2
Application Scenarios
The simplest examples of CDS table functions are SQL requests that
use special capabilities available in SAP HANA SQL, for example:
Convenient functions for processing strings, such as regular
expressions (function REPLACE_REGEXPR, etc.) or the concatenation
of strings from multiple data rows (aggregate expression
STRING_AGG)
Functions for the factory calendar (ADD_WORKDAYS,
WORKDAYS_BETWEEN)
Full text search with many options (CONTAINS predicate)
Window functions (key word OVER PARTITION BY)
Functions for navigating and aggregating in hierarchies (functions
HIERARCHY or HIERARCHY_DESCENDANTS_AGGREGATE)
More complex CDS table functions apply the imperative
programming style of SQLScript with classic control structures to
determine the processing steps explicitly. Recursive calls are
possible as well, which can be necessary for complex data
structures.
Generally, AMDP technology and CDS table functions allow an easy
combination of native SAP HANA capabilities with application data in
ABAP.
CDS table functions are often used in the area of predictive analytics
or machine learning, such as revenue or liquidity forecasts, or
predictions of contract consumption in purchasing. These scenarios
are trained with customer-specific historic data. With the results of
this training, a procedure in SAP HANA, called prediction procedure,
is updated, which represents the newly trained customer-specific
prediction logic. It serves as the interface to the ABAP environment:
you can call the prediction procedure from a CDS table function or
from an AMDP procedure with new application data as input and get
the result of the prediction in return. In practice, a CDS table function
selects the requested input data, calls the prediction procedure with
it, and returns the prediction result combined with the input data.
7.3
Improve Performance and Avoid Errors
We’ve gone through the steps for a successful implementation of
CDS table functions while presenting the example in Section 7.1.
You should consider a few further aspects to avoid surprises, prevent
errors, and understand the performance of your table function.
CDS table functions can only execute read operations on the SAP
HANA database. Data as well as database objects must already
exist before they can be used. If you need new database objects,
you can create these in the ABAP development environment or as
native SAP HANA objects directly in SAP HANA. For native SAP
HANA objects, you must take care of the lifecycle management and
the transport of these objects to the production system yourself.
Before ABAP release 7.52, a limitation existed that prevented a
further CDS table function in a chain of stacked CDS views from
forming a data source of a CDS table function. This limitation has
been removed with release 7.52.
A CDS table function delivered by SAP can’t be extended by a CDS
view extension with custom fields. You would have to modify the
AMDP class method of a CDS table function from SAP to extend it
with custom fields from its data sources. With the extension
association presented in Chapter 15, Section 15.2.2, it’s possible,
however, to extend the wrapper view of the table function with
custom fields if these can be accessed via an association.
The runtime behavior of a CDS table function can sometimes be
quite surprising. You get a better understanding if you consider how
the SAP HANA database optimizer works. When executing a SQL
SELECT request, the optimizer recursively resolves all data sources
given in the request, down to the pure database tables, and
combines their definitions into one single SQL request. In this
process, the CDS table functions are resolved as well, and their
SQLScript code is combined with all the other SQL code. The
resulting request can be quite large, but it’s optimized to a
streamlined execution plan. The optimization also considers
imperative language elements of SQLScript or SAP HANA functions
or procedures called in the table function. This step is done before
the actual selection of data.
If your CDS table function implements pure SQL, there are no
disadvantages compared to using views instead. If your table
function contains imperative language elements of SQLScript or calls
an SAP HANA procedure, these can prohibit certain optimizations
that are possible for pure SQL requests. Consider, for example, a
SQL select request to a CDS table function with a where condition.
Semantically, this condition must be applied to the result of the
request, but a common optimization step tries to apply the where
condition to the data sources of the table function as well to minimize
the data volume processed by the table function. This optimization is
only possible, however, if the optimizer can analyze the processing
logic of the table function and ensure that the optimization won’t
invalidate the result of the table function. Procedural logic or the use
of nontransparent SAP HANA procedures can prohibit this. As a
countermeasure, you can, for example, pass appropriate filter values
via parameters to the CDS table function and use these as where
conditions on the data sources.
The AMDP technology lies in the intersection of two powerful
technologies—ABAP and SAP HANA—leading to some special
features. Developers need expertise in both technologies. More
advanced SQLScript programming requires deeper expertise in SAP
HANA.
The type systems of SAP HANA and ABAP are different, and SAP
HANA sometimes executes calculations differently from ABAP; that
is, it calculates or rounds out with different precision. Deviations of
the results are possible. You should therefore plan enough time for
tests. In addition, be sure to consider the increased implementation
effort for the ABAP class method and the wrapper view, as well as
the explicit definition of data types, redefinition of associations and
field annotations, and client handling. In the relevant cases, the
increased effort for the CDS table function will pay off.
7.4
Summary
In this chapter, you learned how to use a CDS table function for
leveraging capabilities of SAP HANA that aren’t directly available in
CDS views. We introduced a best practice approach for the
implementation of table functions by wrapping them with CDS views,
and we illustrated this with a simple example. Several application
examples and practical tips completed this chapter.
With this chapter, we’ve finished the introduction of the CDS
technology basis that started in Chapter 1. In the next chapter, we
show how it’s applied to application data.
8
Modeling Application Data
Core data services (CDS) support for modeling semantic
properties of application data far exceeds the capabilities of
traditional database views. Together with a modern technical
infrastructure, this simplifies the development of new
applications.
In previous chapters, you’ve learned how to define CDS entities or
views and use them in ABAP programs. With that knowledge, you
can execute SQL mass operations and complex calculations directly
in SAP HANA and benefit from SAP HANA’s processing capabilities
for large data sets. Moreover, you can conveniently formulate your
requests with associations and their path notation. With this
approach, you’re still within the scope of the classic programming
model for applications where you must repeatedly implement many
details anew via individual programming.
However, new infrastructure components in the SAP NetWeaver
Application Server for ABAP (SAP NetWeaver AS for ABAP) and
semantic metadata of CDS entities enable a new programming
model. This reduces individual programming to a minimum. In this
model, the infrastructure executes many recurring, error-prone
programming tasks that are generically controlled by businessmotivated semantic annotations of CDS entities.
Section 8.1 gives a short overview of the application architecture and
the programming model in SAP S/4HANA. The following sections
present several types of meta information for application data that
are evaluated by the new application infrastructure. They cover the
following aspects:
Field labels (Section 8.2)
Field properties, such as quantities and amounts, aggregation
behavior, system times, and texts in natural language
(Section 8.3)
Foreign key relations (Section 8.4)
Text relations (Section 8.5)
Composition relations (Section 8.6)
Time-dependent data (Section 8.7)
In Chapter 10 and Chapter 11, we’ll discuss analytical and
transactional applications, respectively, in more detail and present
further powerful metadata for CDS entities that control their
processing by the infrastructure.
8.1 Application Architecture in SAP
S/4HANA
The core tasks of a business application are to read data, prepare it
for display, present it to a user, receive new input or data changes by
the user, check its consistency, process its impact, and finally persist
the new data.
In modern user interfaces (UIs), such as SAP Fiori, that are
completely geared to the needs of the user, the first part of the user
interaction plays an important role in that many different types of
information are relevant for the user’s task and should be directly
available to support the user’s decisions. The preparation of the
required data and its display create substantial development efforts
and require knowledge in different application areas. However, for
the second part of the user interaction—checking and processing
data—proven program parts can be reused in most cases.
Experience shows that more than 90% of data accesses are read
accesses. Write access happens less frequently. To reduce
development effort, program complexity, and maintenance effort, the
programming model of SAP S/4HANA was optimized for read
accesses. It uses CDS views that prepare the raw data in a reusable
way and add semantic metadata. The metadata is evaluated by
infrastructure components, thus reducing the volume of individual
programming.
You can see the difference in programming models when comparing
the classic SAP Fiori architecture in Figure 8.1 with the latest
architecture in Figure 8.2, optimized for read accesses.
Figure 8.1
Classic SAP Fiori Architecture
Figure 8.2
New Architecture for Read Access
In the much-simplified representation of the two programming
models, you see the common base structure: SAP Fiori apps use
OData services that are provided by the ABAP application server,
which in turn accesses data via structured query language (SQL).
The technical provisioning is done by the service infrastructure in
both models.
Differences exist in the implementation of the service provider of the
OData service and in the definition of the OData service itself. In the
classic model, the OData service and its service provider are both
individually implemented. In the new model, CDS entities define the
structure of the OData service and its components. A generic
application infrastructure serves as the generic service provider, and
its behavior is controlled by a set of CDS entities relevant for the
OData service and its metadata. Therefore, the definition of CDS
entities is the essential step in the development of a new OData
service.
As the structure of the OData service corresponds to the structure of
the CDS views, a read request to the OData service can be
translated to SQL selections from these CDS views. The result of the
SQL SELECT request is translated into corresponding entity sets of the
OData service and returned as a response to the read request.
Special cases that, for example, need a special logic for the
implementation of individual fields can optionally be implemented in
supplied extension methods.
Metadata of the CDS views controls how the OData service for the
CDS views is formed and how an OData request is translated into a
SQL request. Some CDS metadata is translated into the
corresponding metadata of the OData service and thus exposed to
the service consumers.
[»] SAP Fiori Elements
This creates further potential to simplify the development of SAP
Fiori apps with SAP Fiori elements. SAP Fiori elements allows the
construction of an SAP Fiori app from reusable smart templates
and smart controls, whose concrete layout is controlled by the
OData service and its annotations. As the UI annotations needed
for this can already be defined in the CDS views in use, the
essential parts of an SAP Fiori app can be defined completely in
CDS. We’ll show a concrete example for this in Chapter 11,
Section 11.9.2.
In Figure 8.3, we added write access to data as well as the option to
use other communication channels. This completes the conceptual
model of the application architecture in SAP S/4HANA.
Figure 8.3
Overview of the New Architecture
For data changes, existing application logic for checking and
processing data and for updating the database is often connected.
Therefore, the development effort in this programming model is
mainly spent on defining CDS entities with the appropriate metadata,
which is the central topic of this book.
The major piece of the generic application infrastructure is the ABAP
application infrastructure, a new component of the ABAP application
server introduced with SAP NetWeaver 7.5. It’s complemented for
analytic applications by the analytic engine, a component of SAP
Business Warehouse (SAP BW), which is also available in SAP
S/4HANA. The service infrastructure includes SAP Gateway and
other components, for example, for the communication protocol of
analytic applications.
The CDS-based programming model of SAP S/4HANA and the SAP
Business Technology Platform (SAP BTP), ABAP environment,
offers the following benefits, compared to the classic ABAP
programming model:
Increased speed in the selection and preparation of big data sets
for the UI as CDS views execute these steps directly in SAP
HANA
Simplified development and high consistency in the offered OData
services through model-driven development based on a unified
data model (virtual data model [VDM], which will be introduced in
Chapter 9)
Flexibility by optionally using implemented ABAP logic if needed
Proven components of the ABAP platform, such as the integrated
development and transport environment, as well as general
application services, such as user management and integrated
authorization checks, complete the programming model.
8.2
Field Labels
In the following sections, we’ll present some metadata used in CDS
models. We start with field labels. Every data field in a table or a
CDS view or entity needs not only a field name but also a description
in the language of a user, that is, a field label. The technical field
names in CDS entities should be understandable as well but are only
expressed in one language—usually English. Field labels, though,
are supposed to be translated into many languages.
Field labels provide important semantic information about a data
field. In the SAP S/4HANA programming model, field labels can be
defined in multiple ways: directly in the UI, derived from a data
element or a CDS simple type (see Chapter 2, Section 2.6) that
defines the field’s data type, and via a field annotation in a CDS
model:
@EndUserText.label: '<field label>'
An annotated field label can be translated like other short texts.
The field label from the data element or CDS simple type guarantees
identical field labels for all usages of the data field. This avoids
multiple translations and ensures consistent terminology in all UIs
and apps.
8.2.1
Determination of a Field Label
In a CDS model, a field label can be determined by the data type,
data element or CDS simple type, or an annotation. In simple cases,
an annotation takes precedence. The situation is more complex if the
field’s data type is changed by a cast function or if the annotation is
propagated from a data source (also see Chapter 2, Section 2.4, on
the cast function, and Chapter 4, Section 4.3.2, on the propagation
logic in general). Table 8.1 shows a stack of views, V1 to V5, that
demonstrate the precise rules.
View Annotation
Cast Data Element or
To
Simple Type
Field
Label
V5
–
–
D2
C
V4
–
D2
D2
C
V3
(propagated)
–
D1
B
V2
@EndUserText.label: 'B'
–
D1
B
V1
–
–
D1
A
Table 8.1
Determination of a Field Label
The field labels are determined as follows:
1. The starting point is CDS view V1 with field F having data
element D1 as the type. This type defines the field label A.
2. View V2 selects field F from V1. In view V2, field F also has data
type D1. However, in view V2, field F also has annotation
@EndUserText.label:'B' and therefore gets field label B because
the annotation takes precedence over the data type.
3. In view V3, which selects field F from view V2, field F still has
data type D1 but also field label B as the annotation is
propagated to view V3.
4. In view V4, which selects field F from view V3, the data type of
field F is changed to simple type D2 by a cast function. Then,
field F gets field label C from simple type D2. The cast has
higher priority than propagated annotation @EndUserText.label.
In fact, propagation of this annotation is stopped by cast, and it’s
not propagated to field F in view V4.
An explicit annotation in view V4 would have taken precedence
over the cast data type, however.
5. In view V5, which selects field F from view V4, the simple type
and the field label are kept because view V4 has stopped the
propagation of the annotation.
The technical realization of this logic treats the field label of the data
element or simple type like a propagated @EndUserText.label
annotation. Therefore, for all these views, the development
environment shows active annotation @EndUserText.label with the
respective field label. You can verify this with the Active
Annotations function from the context menu of the CDS view in the
ABAP Development Tools (ADT).
8.2.2
Length of a Field Label
A data element can cover many situations as it offers field labels in
three lengths as well as a label for column headings and a short
description text. For fields in CDS views, however, only three label
text variants are available, which correspond with the possible labels
for properties in OData services:
A field label
A short description called quick info
A column heading
Only two label text variants can be defined by annotations in a CDS
view:
Field label by @EndUserText.label
Short description by @EndUserText.quickInfo
Labels of simple types can be defined by these two annotations as
well, plus a column heading by annotation @EndUserText.heading.
The selection of field labels for a data element is more complex. The
mapping between the data element Short Description and
quickInfo is clear, as is the correlation between the data element
Heading and heading. More difficult, however, is the choice between
the Short, Medium, or Long texts for label. Figure 8.4 shows an
example of a typical data element.
Figure 8.4
Field Label Texts of a Data Element
In earlier releases, the medium text of the data element was always
chosen as the field label in an OData service. This often led to
cryptic abbreviations. On the other hand, field labels shouldn’t be too
long. Therefore, the logic was adapted for SAP S/4HANA and now
selects the longest field label with a maximum length of 20
characters. With that approach, most cases can be handled.
In some cases, labels with length 20 are still too short to express the
field semantics properly. Longer field labels can be achieved in three
ways:
Annotate the field directly in the CDS view.
Definition of a data element without short or medium label texts,
as shown in Figure 8.5. Then, the single remaining text is used as
the field label. In this approach, 40 characters are available for the
label text.
Use a CDS simple type that defines a longer label, as shown in
Listing 8.1.
Figure 8.5
Data Element with a Long Field Label
@EndUserText.label: 'Distribution to requesting profit center'
@EndUserText.quickinfo: 'Distribution to requesting profit center'
define type ZBT_IsDistrdToRqqgPrftCtr: XFELD
Listing 8.1
CDS Simple Type with a Long Field Label
All three variants of OData field labels are provided by the
application infrastructure in the metadata of an OData service.
Analytical applications support only the field label, which is taken
from the CDS view.
CDS parameters also have labels. Like field labels, they can be
taken from the data type of the parameter, according to the same
logic, or defined by annotation @EndUserText. There is no propagation
logic for parameter annotations, however.
Besides the labels, you can define further language-dependent texts
in CDS models, such as a label for the CDS entity itself, or several
texts on UIs by UI annotations. However, these texts can’t be derived
from data types—only from CDS view annotations.
8.3
Field Semantics
The technical type of a data field tells very little about the business
semantics of a field. With the technical type alone, an intelligent
infrastructure can’t offer semantics-dependent functions to support
the developer. The field labels or the field documentation, on the
other hand, address human readers and are too informal to be a
reliable source for automated processing.
Therefore, the seasoned ABAP Data Dictionary already introduced
formalized descriptions of some semantic aspects of fields in
addition to the data type, for example, to identify amount fields and
their related currency. This information enables automated
processing by the ABAP infrastructure.
CDS takes several steps forward. Annotations at CDS fields can
formalize any semantic aspect of a data field. This method is applied
in various ways, and you’ll see many examples throughout this book.
In this section, we’ll present several frequently used field
annotations.
8.3.1
Quantities and Amounts
Every quantity field has a unit field, and every amount field has a
currency field. ABAP Data Dictionary can store these semantic
properties and relations for tables and structures. For CDS views,
they can be expressed as the following CDS annotations:
Unit field
A unit field is characterized by annotation
@Semantics.unitOfMeasure: true.
Quantity field
When indicating a quantity field, the related unit field is provided
as @Semantics.quantity.unitOfMeasure: '<unit field>'.
Currency field
A currency field is characterized by @Semantics.currencyCode:
true.
Amount field
For amount fields, the related currency field is again indicated as
@Semantics.amount.currencyCode: '<currency field>'.
By specifying the reference field at the quantity or amount field,
multiple fields can reference the same unit or currency field.
Listing 8.2 shows some examples for these annotations from SAP
standard view I_SalesOrderItem.
@Semantics.quantity.unitOfMeasure: 'OrderQuantityUnit'
OrderQuantity,
@Semantics.unitOfMeasure: true
OrderQuantityUnit,
@Semantics.amount.currencyCode: 'TransactionCurrency'
NetAmount,
@Semantics.currencyCode: true
TransactionCurrency,
Listing 8.2
Quantity and Unit, Amount and Currency
[ ! ] Functional Impact of Reference Fields
For amount fields, the ABAP data type CURR is frequently used.
The correct interpretation of such a field requires the related
currency. Always take care to properly connect an amount field of
that type with its related currency field. Otherwise, even the value
of the amount could change when the amount field is retrieved or
displayed.
The CDS annotations for quantities, units, amounts, and currencies
are translated by the application infrastructure into analogous
annotations of an OData service. They are available to consumers of
the OData service. Analytical applications leverage these CDS
annotations as well, as you’ll see in Chapter 10.
8.3.2
Aggregation Behavior
SQL SELECT from CDS entities can explicitly specify the desired
aggregation of a data field. Depending on the type of field, the
following options exist:
For numeric fields, aggregation can be executed as summation or
as an average calculation.
For sortable fields, a maximum or a minimum can be determined.
For any fields, different values can be counted as an aggregation.
Some data fields in a CDS entity are often aggregated and have a
preferred method of aggregation. The net amounts of sales order
items, for example, are usually summed up but not the net prices.
For net prices, sometimes a minimum or maximum determination is
useful, but mostly they serve as additional information. You can
assign a standard aggregation to such a field that fits its semantics.
This enables the infrastructure to request aggregated data without
explicitly specifying the type of aggregation. By default, fields are
aggregated according to their annotated standard aggregation.
Assume, for example, that field NetAmount in the sales order items
view is annotated for standard aggregation summation. In this case,
an aggregating selection of the business field division and the net
amount results in a list of all divisions together with the sum of the
net amounts of all sales order items within that division. Such a
result is already a simple analysis of business data.
Due to a change in the taxonomy for CDS annotations, there are two
variants for specifying a standard aggregation: the outdated variant
@DefaultAggregation, which is still used occasionally, and the correct
new variant @Aggregation.default. Possible types for the standard
aggregation are shown in Table 8.2.
Aggregation
Type
Description
#AVG
Average calculation: Sum of all values, divided
by the number of values
#COUNT_DISTINCT
Number of distinct values
#FORMULA
Special form for analytic queries (see
Chapter 10, Section 10.3.4 for more details)
#MAX
Maximum of all values
#MIN
Minimum of all values
#NONE
No standard aggregation
#SUM
Sum of all values
Table 8.2
Supported Aggregation Types
Listing 8.3 shows some examples of this annotation from SAP
standard view I_SalesOrderItemCube.
@DefaultAggregation: #SUM
OrderQuantity,
@DefaultAggregation: #SUM
NetAmount,
Listing 8.3
Standard Aggregation
Fields without a standard aggregation annotation won’t be
aggregated. This is equivalent to standard aggregation #NONE.
The annotation for a standard aggregation (except #NONE) has a big
impact on the consumers of a view. In analytical views, this leads to
the interpretation of the annotated field as an analytical measure.
You’ll find more details on this in Chapter 10, Section 10.2.
In the metadata of an OData service (OData V2) based on a CDS
view, a field with annotated standard aggregation is marked as a
measure. A read request for the entity set corresponding to the CDS
view is performed as an aggregating selection. The type of
aggregation is determined by the annotated standard aggregation.
For executing the request, the application infrastructure uses a SQL
SELECT with the standard aggregation of the annotated fields,
grouped by the other requested fields. The aggregation is finally
executed by SAP HANA. With this method, OData services can be
leveraged for simple analytics.
8.3.3
System Times
Applications usually store the creation date/time of a data record, as
well as the date it was last changed, in database tables together with
the application data. This is important semantic information. The
point in time of the last change, for example, can be used in data
replication or for optimistic locking mechanisms. A prerequisite is the
reliable determination and storage of this information at creation and
at every change. CDS annotations can be used to identify fields with
this information as system times (see Table 8.3).
Annotation
Field Semantics
Annotation
Field Semantics
@Semantics.systemDateTime.
createdAt
Point in time of creation (ABAP type
TIMESTAMP or TIMESTAMPL)
@Semantics.systemDateTime.
lastChangedAt
Point in time of the last change
(ABAP type TIMESTAMP or TIMESTAMPL)
@Semantics.systemDate.
createdAt
Date of creation (ABAP type DATS)
@Semantics.systemDate.
lastChangedAt
Date of last change (ABAP type
DATS)
@Semantics.systemTime.
createdAt
Clock time of creation (ABAP type
TIMS); only reasonable in
combination with a creation date
@Semantics.systemTime.
lastChangedAt
Clock time of the last change (ABAP
type TIMS); only reasonable in
combination with a last change date
Table 8.3
CDS Annotations for System Times
Fields with system times exist not only in tables but also in CDS
views. For views that combine data from multiple tables, you must
carefully consider which fields should be annotated as system times
using the following rules:
Only a single point in time of creation is reasonable; it should
relate to the main data source of the view.
The point in time of the last change must consider possible
changes of all data fields of the CDS view, independent of their
origin.
Listing 8.4 shows examples of system times from the SAP standard
view I_SalesOrder.
@Semantics.systemDate.createdAt: true
CreationDate,
@Semantics.systemTime.createdAt: true
CreationTime,
@Semantics.systemDate.lastChangedAt: true
LastChangeDate,
@Semantics.systemDateTime.lastChangedAt: true
LastChangeDateTime,
Listing 8.4
8.3.4
System Times
Text and Languages
Fields that contain a text in natural language should be
distinguishable from codes or other technical information. They
should preferably be displayed to a human user, but they are usually
irrelevant for technical processing. For this purpose, CDS annotation
@Semantics.text is available.
Natural language texts are usually written in a certain language;
there are a few exceptions, such as names of humans or
organizations. If this language is provided in another field of a view,
this field is annotated with @Semantics.language. An explicit
connection between the fields, such as quantities or amounts, isn’t
possible, however. Usually, all text fields of an entity share the same
language.
Listing 8.5 shows examples for this from SAP standard view
I_CountryText. This is a language-dependent text view that we’ll
introduce in Section 8.5.
@Semantics.language
key spras as Language,
@Semantics.text: true
cast(landx50 as fis_landx50 preserving type ) as CountryName,
Listing 8.5
8.3.5
Annotations for Text and Language
Information for the Fiscal Year
CDS annotations can also express application-specific semantics.
An example is the information for a fiscal year in financial accounting
and its periods. A fiscal year can deviate from a calendar year and
consists of flexibly defined periods. The indication of fiscal year
information enables an appropriate formatting of the data. The
annotations are shown in Table 8.4.
Annotation
Field Semantics
@Semantics.fiscal.yearVariant
Fiscal year variant; defines the
properties of the fiscal year
@Semantics.fiscal.period
Fiscal period given by three digits
@Semantics.fiscal.year
Fiscal year given by four digits
@Semantics.fiscal.yearPeriod
Fiscal year period; the
combination of fiscal year and
period
@Semantics.fiscal.quarter
Fiscal quarter given by one digit
@Semantics.fiscal.yearQuarter
Combination of fiscal year and
quarter
@Semantics.fiscal.week
Fiscal week given by two digits
@Semantics.fiscal.yearWeek
Combination of fiscal year and
week
@Semantics.fiscal.dayOfYear
Number of a day in a fiscal year
Table 8.4
Information for the Fiscal Year
Listing 8.6 shows examples from SAP standard view
I_JournalEntryItem.
@Semantics.fiscal.year: true
I_GLAccountLineItemRawData.LedgerFiscalYear,
@Semantics.fiscal.period: true
I_GLAccountLineItemRawData.FiscalPeriod,
@Semantics.fiscal.yearVariant: true
I_GLAccountLineItemRawData.FiscalYearVariant,
@Semantics.fiscal.yearPeriod: true
I_GLAccountLineItemRawData.FiscalYearPeriod,
Listing 8.6
Information for the Fiscal Year
8.4
Foreign Key Relations
You may be familiar with the concept of foreign keys from ABAP
Data Dictionary. The basic target is to restrict the possible values for
a data field of a foreign key table to the available values of a key field
in a check table. This may sound complex, but it quickly becomes
clear with an example (see Figure 8.6).
Figure 8.6
Foreign Keys in ABAP Data Dictionary
In the example, the data record for an address has field Country to
provide the country of the address. Only countries from a country
table should be allowed as admissible values for this field. Therefore,
the address table plays the role of foreign key table, and field
Country is the foreign key field. The table of countries plays the role
of check table with Country as the key field. This relation between
the tables and the relevant fields can be stored as a foreign key in
the ABAP Data Dictionary.
Foreign key relations can exist between CDS entities as well. The
relationship between the foreign key view and the value view or
entity view can be defined neatly by an association. In CDS, we
avoid the term “check view” because the foreign key relationship
alone doesn’t define a consistency check but only expresses the
semantic relationship between the persisted data. The entity view
provides possible field values or, more precisely, represents the list
of instances that the foreign key field can reference (see Figure 8.7).
Listing 8.7 shows an example of such an association from CDS view
I_ProfitCenter to CDS view I_Country.
define view I_Country as select from t005
{ key land1 as Country,
…
define view I_ProfitCenter as select distinct from cepc
association[0..1] to I_Country as _Country
on $projection.Country = _Country.Country
{ …
land1 as
Country,
…
Listing 8.7
Association Used by a Foreign Key Relation
Figure 8.7
Foreign Keys in CDS
The definition of an association doesn’t yet establish a foreign key
relation. The association could be defined even if field Country in
CDS view I_ProfitCenter could take any values. Its foreign key
character and identification of the foreign key field are defined by
CDS annotation @ObjectModel.foreignKey.association at the foreign
key field (see Listing 8.8). The association given in the annotation is
called the foreign key association for this field.
define view I_ProfitCenter as select distinct from cepc
association[0..1] to I_Country as _Country
on $projection.Country = _Country.Country
{ …
@ObjectModel.foreignKey.association: '_Country'
land1 as
Country,
…
Listing 8.8
Annotation of a Foreign Key Association
The cardinality of a foreign key association must be either [0..1] or
[1..1]; that is, either at most one country or exactly one country
exists for a profit center. The cardinality for the reverse direction of
the association can be [0..*], meaning that there can be no profit
centers or any number of profit centers for a country.
This way of defining a foreign key association also works for entity
views with more than one key field. Listing 8.9 shows such an
example for regions (federal states, provinces, etc.).
define view I_Region as select from t005s
association [1..1] to I_Country as _Country
on $projection.Country = _Country.Country
{
@ObjectModel.foreignKey.association: '_Country'
key t005s.land1 as Country,
key t005s.bland as Region,
…
}
define view I_ProfitCenter as select distinct from cepc
association[0..1] to I_Country as _Country
on $projection.Country = _Country.Country
association[0..1] to I_Region as _Region
on
$projection.Country = _Region.Country
and $projection.Region = _Region.Region
{ …
@ObjectModel.foreignKey.association: '_Country'
land1 as
Country,
@ObjectModel.foreignKey.association: '_Region'
regio as
Region,
…
}
Listing 8.9
Foreign Key Association with Two Key Fields
Figure 8.8 shows how the three views from the listings are
connected by foreign key associations.
Figure 8.8
Foreign Key Associations
For CDS, the foreign key concept of ABAP Data Dictionary was
enhanced by a useful aspect: the indication of a representative key
field by annotation @ObjectModel.representativeKey. The motivation
for this can be seen in the preceding example. In Listing 8.9, it’s
clear from the semantics and the naming that field Country has
association _Country as the foreign key association and not
association _Region.
From a technical perspective, you could also specify association
_Region at field Country as the foreign key association. This has the
following disadvantages, however:
Not all countries are available as values but only those for which
regions are recorded.
For a consumer of the foreign key view, it’s not possible to retrieve
further information about the country via the foreign key
association.
This second point, in particular, is an import benefit of the data
model: following an association reveals further details of the starting
point, that is, the foreign key field. Foreign key associations in CDS
are meant to serve this purpose and retrieve further data of the entity
represented by the foreign key field.
By indicating the representative key field of an entity view, usage of a
wrong association as the foreign key association can be detected. In
a consistent model, the foreign key association must bind the foreign
key field to the representative key field of the entity view; that is, the
two fields must be equal in the ON condition of the association
definition.
Listing 8.10 shows annotation @ObjectModel.representativeKey in
the views I_Country and I_Region.
@ObjectModel.representativeKey: 'Country'
define view I_Country as select from t005
{ key land1 as Country,
…
@ObjectModel.representativeKey: 'Region'
define view I_Region as select from t005s
association [1..1] to I_Country as _Country
on $projection.Country = _Country.Country
{
@ObjectModel.foreignKey.association: '_Country'
key t005s.land1 as Country,
key t005s.bland as Region,
…
}
Listing 8.10
Representative Key Fields
The representative key field of an entity view is the part of the key
that semantically represents an entity (row) of the view. In the view of
regions, this is key field Region, not key field Country. Because the
view name also reflects the semantics of the entity, the
representative key name and the view name are usually very similar.
Not every CDS view needs a representative key field. Views with a
representative key field represent discrete entities that can be
connected by foreign keys to enrich a data model semantically.
Foreign key relations are an elementary modeling pattern in CDS.
Foreign key associations are used to do the following:
Serve as the standard source of value help if no other explicit
value help is defined (see Chapter 13, Section 13.1).
Define dimensions in the analytical model (see Chapter 10,
Section 10.2.4).
Mark the view that provides detailed information for a data field.
Provide automated input checks in transactional applications.
8.5
Text Relations
Many data fields contain codes or IDs to represent an entity of the
business world, for example, a country code or customer ID. While
computers are very good at processing such codes, a human
consumer wants to see a name or description in natural language.
Such texts are stored in different fields or even different tables, so a
connection between the coded field and the text field must be
created in the data model, also known as a text relation. This
enables the infrastructure to automatically detect and use the texts. If
texts exist in multiple languages, the logon language of the user, or
an appropriate substitute, would be chosen for filtering the text to be
displayed.
CDS supports two variants of text relations: within a CDS view or
entity, or between two different models. Both variants are based on
annotations: @ObjectModel.text.element for within a model or
@ObjectModel.text.association for between models.
You can see an example for the first variant in SAP standard view
I_Bank for banks. A relevant extract is shown in Listing 8.11.
define view I_Bank as select from bnka
{ …
@ObjectModel.foreignKey.association: '_Country'
key banks as BankCountry,
@ObjectModel.text.element: [ 'BankName' ]
key bankl as BankInternalID,
@Semantics.text: true
banka as BankName,
…
}
Listing 8.11
Text Relation within a View
In this example, BankInternalID is a coded representation of a bank,
and BankName is a text field for the name of the bank. In addition, take
note of field annotation @Semantics.text from Section 8.3.4.
Annotation @ObjectModel.text.element at the field with the coded
representation references a list of fields that contain a descriptive
text. If the list contains text fields, the first one is used as standard
text. Note that this variant doesn’t support language-dependent
texts.
An example for the second variant is the view for countries,
I_Country, and its text view, I_CountryText, with the related country
names in different languages. Listing 8.12 shows the relevant parts.
define view I_Country as select from t005
association [0..*] to I_CountryText as _Text
on $projection.Country = _Text.Country
{ @ObjectModel.text.association: '_Text'
key land1 as Country,
…
}
@ObjectModel.dataCategory: #TEXT
@ObjectModel.representativeKey: 'Country'
define view I_CountryText as select from t005t
{ key land1 as Country,
@Semantics.language: true
key spras as Language,
@Semantics.text: true
landx50 as CountryName,
…
}
Listing 8.12
Text Relation to a Text View
View I_Country only has the coded representation Country as a field.
Its textual description—actually, its name—CountryName is language
dependent and stored in view I_CountryText. The relation between
the views is established by association _Text. Annotation
@ObjectModel.text.association: '_Text' characterizes the
association as text association for field Country, and the description
of this field can be found in the associated view.
In associated view I_CountryText, field CountryName is annotated as a
text field and therefore used as the description. If there are multiple
text fields in the view, the first text field will be the standard text.
Views or entities that essentially contain only textual descriptions can
be marked as text views by view annotation
@ObjectModel.dataCategory: #TEXT.
To clarify which field the text view provides the text for, one of the
key fields is marked as the representative key field by annotation
@ObjectModel.representativeKey. This clearly defines the semantics
of the view in case there are multiple key fields.
Text associations always bind the annotated field in the source view
with the representative key field of the text view. Usually, a text view
is language dependent. The field with the language of the texts must
be a key field and must be annotated with
@Semantics.language: true. The infrastructure usually filters on the
logon language of the user when retrieving a language-dependent
text.
View I_ProfitCenter from the preceding section (refer to Listing 8.8)
doesn’t have a text association for its field Country. Still, the
infrastructure can automatically determine a text field for it by first
following the foreign key association to view I_Country. Its
representative key field Country has a text association that leads to a
text (see Figure 8.9).
Figure 8.9
Foreign Key and Text Association
[»] Localized Text in CDS Projection Views
Projection views are CDS models with specific syntax that are
used in transactional models. They leverage the text metadata and
associations in their syntax element localized to easily define
fields for language-dependent text in their model. See Chapter 11,
Section 11.5.4, for examples.
8.6
Composition Relations
Many types of related business data are distributed in a normalized
form to multiple tables, for example, the header data and items of a
sales or purchase order. CDS views for analytics often combine such
data into a single view, but CDS views or entities for transactional
applications reflect a normalized distribution of data to multiple CDS
entities.
In the data model, we want to express which CDS entities belong
together and form a well-defined composition, an object or business
object. The CDS entities or views that contribute to a (business)
object are called object nodes, or business object nodes. For this
purpose, composition relations are introduced that bring the object
nodes into hierarchical parent-child relations. A node can have at
most one superordinate parent node but any number of subordinate
child nodes. A composition relation implies an existential
dependency, meaning that a data row in a child node can only be
created if a related row in its parent node exists, and the deletion of
a data row in the parent node forces the deletion of all related rows
in child nodes. For every object, one node is marked as a root node.
This is the only node in the object without a parent node. In the sales
order object, for example, CDS view I_SalesOrder for the order
header data is the root node. An object can be identified by its root
node.
[»] New Syntax Elements for Compositions
Composition relations in CDS were originally expressed by CDS
annotations (see Chapter 4). In the context of transactional
applications, additional syntax elements for compositions were
introduced into the CDS syntax to better support the development
of transactional applications: define root, parent, and composition.
You have already learned about these new capabilities in
Chapter 3, Section 3.3. In the following, we focus on the classic
approach based on annotations, which is more common in basic
CDS data models.
Composition relations between nodes are defined as usual by
associations of the CDS entities. Annotation
@ObjectModel.association.type assigns a type to the associations
that marks them as composition associations. Three types of
composition associations are distinguished:
#TO_COMPOSITION_PARENT
The association points to the parent node.
#TO_COMPOSITION_CHILD
The association points to a child node.
#TO_COMPOSITION_ROOT
The association points to the root node of the object.
An association can simultaneously have types
#TO_COMPOSITION_PARENT and #TO_COMPOSITION_ROOT. The root node is
identified by annotation @ObjectModel.compositionRoot: true.
The sales order model of SAP consists of CDS views I_SalesOrder
for the order header, I_SalesOrderItem for the items, and
I_SalesOrderScheduleLine for the schedule lines. It has the
composition associations shown in Figure 8.10.
Figure 8.10
Composition Associations
The relevant parts of the view definitions are shown in Listing 8.13.
@ObjectModel.compositionRoot: true
define view I_SalesOrder …
association [0..*] to I_SalesOrderItem as _Item
on $projection.SalesOrder = _Item.SalesOrder
{ …
@ObjectModel.association.type: [#TO_COMPOSITION_CHILD]
_Item,
…
}
define view I_SalesOrderItem …
association [1..1] to I_SalesOrder as _SalesOrder
on $projection.SalesOrder = _SalesOrder.SalesOrder
association [0..*] to I_SalesOrderScheduleLine
as _ScheduleLine
on
$projection.SalesOrder = _ScheduleLine.SalesOrder
and $projection.SalesOrderItem = _ScheduleLine.SalesOrderItem
{ …
@ObjectModel.association.type:
[#TO_COMPOSITION_PARENT, #TO_COMPOSITION_ROOT]
_SalesOrder,
@ObjectModel.association.type: [#TO_COMPOSITION_CHILD]
_ScheduleLine,
…
}
define view I_SalesOrderScheduleLine …
association [1..1] to I_SalesOrder as _SalesOrder
on $projection.SalesOrder = _SalesOrder.SalesOrder
association [1..1] to I_SalesOrderItem as _SalesOrderItem
on
$projection.SalesOrderItem =
_SalesOrderItem.SalesOrderItem
and $projection.SalesOrder = _SalesOrderItem.SalesOrder
{ …
@ObjectModel.association.type: [#TO_COMPOSITION_ROOT]
_SalesOrder,
@ObjectModel.association.type: [#TO_COMPOSITION_PARENT]
_SalesOrderItem,
…
}
Listing 8.13
CDS Views with Composition Relations
You’ll see in more detail how compositions are used in Chapter 11,
Section 11.3.1.
8.7
Time-Dependent Data
Time-dependent data or temporal data items in business
applications are mainly attributes of master data objects that change
their value over time, usually at a certain calendar day. Typical
examples include the salary of an employee or the value-added tax
percentage of a country.
This is a business-driven planned time dependency, not a versioning
of data specifying the point in time of a change. That second
versioning is called system time dependency and is usually applied
for change-tracking or revision purposes. Business time-dependency
is usually described by dates and therefore has the granularity of
days. Time-dependent data is usually stored in separate tables that
have an additional key field for the time dimension.
With CDS views or entities, business time-dependent data can be
described by a common modeling pattern as follows:
A business time-dependent entity has two date fields for the
validity period of the time-dependent attributes. These date fields
are annotated with @Semantics.businessDate.from: true and
@Semantics.businessDate.to: true, respectively.
The validity period comprises the from date and the to date.
At least one of the date fields is part of the view’s key. The
remaining fields of the key are called entity keys.
The validity periods of two view rows with the same entity key
must not overlap.
The combined timeline of validity periods of all rows with the same
entity key can have multiple gaps between periods.
Both date fields should be stored on the database for efficient
access.
[ ! ] Annotate Only Time-Dependent Views
When using annotations @Semantics.businessDate.from and
@Semantics. businessDate.to, take care that the view actually has
all qualities of a business time-dependent entity.
The infrastructure recognizes a business time-dependent entity at its
annotations and key. When processing the entity data, it can use an
appropriate key date as the filter in this case.
Listing 8.14 shows how view I_CostCenter is annotated as a
business time-dependent CDS entity.
define view I_CostCenter as select …
{ key kokrs as ControllingArea,
key kostl as CostCenter,
@Semantics.businessDate.to: true
key datbi as ValidityEndDate,
@Semantics.businessDate.from: true
datab as ValidityStartDate,
…
}
Listing 8.14
Business Time-Dependent View
8.8
Summary
We started with a high-level overview of the programming model of
SAP S/4HANA and showed the central role played by CDS. We then
provided many examples of how semantic properties of the data can
be captured in CDS metadata, which then controls the generic
processing of data by the infrastructure.
In the following chapters, in particular Chapter 10 and Chapter 11 on
modeling analytical and transactional applications, respectively,
you’ll learn about even more options for defining applications by
CDS views, their associations, and their annotations. But first, let’s
take a closer look at SAP S/4HANA’s virtual data model (VDM) in
Chapter 9.
9 The Virtual Data Model of SAP
S/4HANA
SAP S/4HANA represents business data by a virtual data
model (VDM) that is primarily realized by core data services
(CDS) models. It enables generally understandable access to
the data of the SAP S/4HANA system.
The numerous database tables of a business software system store
valuable company data. Due to the complex structure and technical
names of these tables and their fields, the meaning of that data is
only comprehensible for technical experts. The virtual data model
(VDM) of SAP S/4HANA exposes the data together with its business
semantics in an understandable and directly usable form. It’s used
instead of direct access to database tables (see Figure 9.1). The
data structures and terminology of the VDM are mainly kept in
external service interfaces, SAP Fiori apps, and data interfaces.
Figure 9.1
Virtual Data Model and Its Consumers
The VDM consists, on one hand, of CDS views and entities that are
developed according to SAP-internal standards, and, on the other
hand, of model entities, the SAP object types, which correspond to
business objects or concepts. Further SAP object node types
connect the SAP object types with CDS entities of the VDM. SAP
object types and object node types are custom development objects.
SAP object types represent well-defined business objects such as
customer orders, customers, or products, as well as code lists or
configuration objects such as currencies or payment terms. SAP
object node types model the internal structure of an object type, for
example, the header, items, and schedule lines of a sales order.
Multiple CDS entities can relate to an SAP object node type and
provide its details: structure of fields, relations (associations) to other
SAP object node types (other CDS entities), or semantic details
expressed by annotations. For different purposes or contexts, there
can be specific CDS entities for the same SAP object node type.
The VDM is the foundation for application data in transactional
applications, for analytical models, for external interfaces, and also
for the data that external consumers use for analytics or artificial
intelligence.
This chapter explains the VDM in detail, including how it’s structured
and how you can use it. It presents a small part of the VDM around
the sales order business document as an example. Section 9.1
explains the motivation for the VDM and its use in application
development. Section 9.2 introduces SAP object types and object
node types and their relation to CDS entities. Section 9.3 covers the
different categories of views in VDM and their relations. Section 9.4
describes the VDM naming rules for SAP object types, object nodes
types, CDS entities, and their fields. Section 9.5 presents a typical
CDS view of the VDM, as well as an explanation of its special
properties. Section 9.6 concludes with a discussion of how to find
suitable VDM views in an SAP S/4HANA system.
9.1
Why a Virtual Data Model?
The VDM forms the central data model for application data. It’s used
by transactional and analytical apps as well as service and data
APIs, thus enforcing consistency and facilitating the combination of
different apps and APIs. The definition of specific models for these
applications or APIs isn’t necessary, or at least is greatly simplified,
by reusing the VDM. Yet the guidelines for VDM have further targets:
intuitive understanding of data structures from a business
perspective, consistent names, homogeneity of the comprehensive
model, and avoiding redundancies and duplicate developments.
Perhaps you know such goals from other modeling initiatives. The
VDM adds two extra targets: the efficient execution of the model and
its practical capability. These goals are ensured by direct execution
of CDS views in the SAP HANA database and by the consistent use
of VDM views for the development of apps and services in SAP
S/4HANA.
This makes the VDM an ideal foundation for your own developments
and extensions. You don’t need to start at the lowest level of
database tables and can leverage VDM views to build your own
custom views. You benefit from the business-oriented VDM and can
quickly develop custom APIs and transactional or analytical apps on
your custom views. When using CDS entities, you should consider
the lifecycle of CDS views and elements (see Chapter 14) to benefit
from the compatibility across software versions of released CDS
views.
The development of VDM puts the following aspects in focus:
VDM views are understandable from a nontechnical business
perspective. They are enhanced with additional semantics, for
example, the relation to other VDM views.
The major part of VDM views is supposed to be reused by other
development teams and, after releasing them, by customers and
partners.
The VDM implemented by CDS is the leading model for SAP
S/4HANA; other data models are derived from it, for example,
analytical or OData models.
VDM views are executed directly by the SAP HANA database and
benefit from its manifold capabilities and speed.
VDM views are used for the development of all new SAP standard
apps in SAP S/4HANA.
VDM views are based on CDS and leverage the robust ABAP
infrastructure.
The VDM covers many but not yet all application areas and their
data. It grows continuously with the implementation of each new
application. You’ll learn more details in the next sections.
9.2 SAP Object Types and Object Node
Types
SAP object types and SAP object node types hold very little
information. Their essential task is to represent a business-related
object to which other entities or development objects are related.
SAP object types and object node types are defined in the ABAP
Development Tools (ADT) as SAP Object Type and SAP Object
Node Type. Figure 9.2 shows the SAP object type SalesOrder in
ADT.
Figure 9.2
SAP Object Type: SalesOrder
The attribute Object Type Code of an SAP object type is a numeric
alternative key that can be used for identifying the SAP object type in
integrations between systems or cloud services. The attribute Type
Category specifies a category of an SAP object type (see Figure 9.3
and Table 9.1).
Figure 9.3
Type Category of an SAP Object Type
The possible values of a type category (see Figure 9.3) are
explained in Table 9.1.
Type
Category
Description
Business
Object
Real-world object with business relevance
Technical
Object
Object without business semantics
Analytical
Object
Object with business relevance that is combined
with other data to create new quality of
information
Configuration Object of business configuration or a code list
Object
Dependent
Object
Table 9.1
Object that only exists as part of other business
objects and is shared by these other objects
Type Categories of SAP Object Types
While an SAP object type is a complex aggregate object that
represents all its substructures, an SAP object node type models a
simple substructure of an SAP object type. Figure 9.4 and Figure 9.5
show the SAP object node types SalesOrder and SalesOrderItem,
which both belong to the SAP object type SalesOrder.
Figure 9.4
SAP Object Node Type SalesOrder: A Root Node
Figure 9.5
SAP Object Node Type SalesOrderItem
Every SAP object type has an SAP object node type, usually with the
same name, which represents the header data of the object. This
root node is identified by the Root Node Flag checkbox.
A CDS entity can be assigned to an SAP object node type by
annotation @ObjectModel.sapObjectNodeType.name and hence also to
an SAP object type. Multiple CDS entities with different purposes
could be assigned to the same SAP object node type. The annotated
CDS entities provide rich information for the object node type: data
fields, associations, and further annotations. From the associations
of CDS entities, even the relations between SAP object node types
and object types can be derived.
If it’s not suitable, due to technical reasons, to connect CDS models
by associations, a code or ID field can be annotated with
@ObjectModel.sapObjectNodeTypeReference to point to the SAP object
node type that is identified by that field. The annotation thus defines
a kind of foreign key relation to an SAP object node type. This
approach is, for example, applied by SAP to remote API views
(Section 9.3.3).
9.3
Categories of CDS Entities in the Virtual Data Model
Since the introduction of CDS, the number of CDS entities and views in the VDM has
greatly increased. These entities and views are used for different purposes, and new
views for a specific scenario can easily be developed. SAP object types and object
node types structure the CDS entities from a business point of view. The supported
capabilities, which we’ll introduce in Chapter 14, classify CDS entities by usage
scenarios.
In this section, we explain the layers of CDS entities in the VDM, each having its own
specific tasks, and some special types of views. Figure 9.6 shows the layer structure
with some examples.
Figure 9.6
VDM Layers with Examples
Let’s take a closer look at these views.
9.3.1
Basic Interface Views
Directly above the database sits the basic interface views layer. These views are
identified by annotation @VDM.viewType: #BASIC. Only views from this layer access the
database tables. Basic interface views are supposed to be reused, mainly as data
sources or association targets when defining new views, and are typically released
according to stability contract C1 (see Chapter 14). In the cloud, the basic interface
views are the lowest access level to the data for customers and partner. They hide the
database tables so that the persistence model can be changed without the need to
adapt consumers, except a few basic interface views. All further VDM views are based
on the basic interface views.
Basic interface views transform the classical persistence model of application data to
the VDM as follows:
Business-oriented CDS entities and SAP object types replace the technical
database tables.
Business terminology replaces the cryptic names used for fields in database tables
(Section 9.4).
The semantics of fields are annotated (see Chapter 8, Section 8.3).
Relations between entities are expressed by foreign key associations, text
associations, or compositions (see Chapter 8, Section 8.4, Section 8.5, and
Section 8.6).
Other entities in VDM inherit the transformed properties from the basic interface views
to ensure consistent models and consistent terminology.
Basic interface views can be considered as semantically refined replacements for
database tables within the VDM. Like those, the layer of basic interface views should
be free of redundancies to ensure consistency of the complete VDM. There can be, for
example, only a single basic interface view that represents a customer entity.
Redundancy is meant from a semantical, not a technical perspective. Some tables
contain data of different semantic entities that is represented by their own basic
interface views, for example, the sales order and the sales contract. Such situations of
generalization/specialization can be modeled by specific basic interface views, for
example, I_SalesOrder and I_SalesContract, which are much easier to understand
than the table model. Section 9.5.2 presents the sales order entity as an example in
more detail.
9.3.2
Composite Interface Views
Composite interface views are built from basic interface views and other composite
interface views. They can be projections tailored for specific purposes or combinations
of data from multiple data sources. They are identified by annotation @VDM.viewType:
#COMPOSITE. Analytic cube views, which will be introduced in Chapter 10,
Section 10.2.3, are usually composite interface views.
The CDS views of transactional object models (which we’ll discuss in Chapter 11) also
belong to the layer of composite interface views, although they are characterized by
annotation @VDM.viewType: #TRANSACTIONAL to make their transactional usage explicit.
Composite interface views are meant for reuse, like the basic interface views.
Together, they form the interface views of the VDM, the usable API of the transformed
data model VDM. Not all composite interface views are released for use by customers
and partners, however. Some views of this layer are only meant for SAP internal use.
Analytical cube views are typically released as provider views for custom analytical
queries or as data sources for the definition of custom analytical cubes.
[»] Naming Conventions for VDM Interface Views
Originally, all VDM interface views had prefix I_ by naming convention. Then, a new
convention was introduced to identify VDM interface views reserved for SAP-internal
use. These views are called restricted reuse views and get the prefix R_ (for
restricted). This naming convention was complemented by annotation
@VDM.lifecycle.contract.type: #SAP_INTERNAL_API to indicate that the view is meant
for SAP-internal use only, even if it has prefix I_. Conversely, annotation
@VDM.lifecycle.contract.type: #PUBLIC_LOCAL_API is used for released views or
views with a planned release.
Figure 9.7 shows some examples of interface views. The horizontally attached arrows
represent associations, and the vertical arrows show a data selection of the views.
Figure 9.7
9.3.3
Examples of VDM Interface Views
Consumption Views
Consumption views form the top layer of VDM views. They are built on the basic and
composite interface views, are annotated with @VDM.viewType: #CONSUMPTION, and have
prefix C_ in their names. Consumption views are used in dedicated consumption
scenarios, for example:
As analytical queries in analytic scenarios (see Chapter 10, Section 10.3)
As transactional consumption views in transactional service models (see
Chapter 11, Section 11.5)
For data retrieval in SAP Fiori apps via OData services
Consumption views are usually tailored for an individual consumer of the VDM, that is,
for a concrete transactional or analytical app. They aren’t meant for general reuse but
provide exactly the data and information that is needed by the specific app. When
changing the app, the consumption views are adjusted, often without considering
compatibility with their previous versions. Therefore, consumption views usually aren’t
released for use by customers or partners.
Some consumption views can be used in multiple scenarios, however. For example,
analytical queries can be used to define multiple reports or key performance indicators
(KPIs). In such cases, consumption views also can be released and kept stable.
Consumption views can have special annotations to control the properties of the
related app, including its layout and behavior. For analytical consumption views, this is
described in Chapter 10, Section 10.3, and for transactional consumption views, it’s
explained in Chapter 11, Section 11.9.2.
In the top layer of VDM views, there is another type of view, the remote API view,
which is built on VDM interface views. Remote API views are used to define OData or
web service APIs that enable remote access from other systems. Usually released to
customers and partners, these external APIs are published on SAP Business
Accelerator Hub (https://api.sap.com). SAP keeps remote APIs stable according to
release contract C2 to support stability of the external APIs (see Chapter 14).
Remote API views are a special type of consumption view in that they are exclusively
defined for an API as consumer and must not be reused otherwise. On the other hand,
they are used to make the VDM accessible through remote APIs and therefore often
correspond to basic or composite interface views. To reflect this correspondence, they
are annotated with @VDM.ViewType: #BASIC or @VDM.ViewType: #COMPOSITE but not with
@VDM. ViewType: #CONSUMPTION. Remote API views can also be identified by their
naming convention, prefix A_, or by their annotation @VDM.lifecycle.contract. type
:#PUBLIC_REMOTE_API.
Figure 9.8 shows some examples of consumption and remote API views.
Figure 9.8
9.3.4
Examples of VDM Consumption and Remote API Views
Other Types of VDMs
The VDM also needs some auxiliary views for technical reasons: the private VDM
views (identified by annotation @VDM.private: true and prefix P_). They are only used
by SAP and can change at any time. Private VDM views exist in all layers.
Table 9.2 shows an overview of the most important types of CDS entities in the VDM.
Type
Naming
Identification
Convention
Basic
interface
view
I_*
@VDM.viewType: #BASIC
Composite
interface
view
I_*
@VDM.viewType: #COMPOSITE
Consumption C_*
view
Restricted
reuse view
R_*
@VDM.viewType: #CONSUMPTION
@VDM.lifecycle.contract.type: #SAP_INTERNAL_API
Transactional R_*TP
object view
@VDM.viewType: #TRANSACTIONAL
@VDM.lifecycle.contract.type: #SAP_INTERNAL_API
Transactional I_*TP
interface
view
@VDM.viewType: #TRANSACTIONAL
@VDM.lifecycle.contract.type: #PUBLIC_LOCAL_API
Transactional C_*TP
consumption
view
@VDM.viewType: #CONSUMPTION
@VDM.usage.type: [#TRANSACTIONAL_PROCESSING_SERVICE]
Remote API
view
A_*
@VDM.lifecycle.contract.type : #PUBLIC_REMOTE_API
Private VDM
view
P_*
@VDM.private: true
Extension
include view
E_*
@VDM.viewType: #EXTENSION
VDM view
extension
X_*
@VDM.viewExtension: true
Table 9.2
Types of VDM Entities
9.4
Naming in the Virtual Data Model
The VDM forms the basis for all specific data models of application
data in an SAP S/4HANA system: analytical models, transactional
models, and API models, in particular OData services and data
interfaces. For naming, all these models use the objects, entities,
names, and so on that are assigned in the VDM to ensure uniformity.
Names in VDM focus on business semantics and understandability.
They are based on English terms that are combined according to an
UpperCamelCase schema.
SAP object types form the business nucleus of VDM. Their names
follow strict rules and an approval process. The names of SAP object
node types use the object type name (or an abbreviation) as a prefix.
Names must not be too generic, otherwise collisions with similar but
still different objects occur. Therefore, names are often prefixed with
qualifiers to distinguish, for example, a SalesOrder from a
PurchaseOrder. VDM deliberately doesn’t use a separation by name
spaces but defines unique names for business concepts.
For country-specific or industry-specific objects and some SAP
products, however, standardized prefixes are applied. For countryspecific objects, the country ISO code is used, for example,
TW_TaxCode, and for industries or products, a three-character
abbreviation is used such as OIL for the oil and gas industry, or ACM
for SAP Agricultural Contract Management.
Field names in CDS are limited to a length of 30 characters. This
enforces the use of abbreviations for proper naming of complex
matters. The abbreviations are standardized; that is, every
abbreviation has exactly one expanded term. The abbreviation Qty,
for example, always means Quantity. This allows you to always
expand the full name from an abbreviated one.
According to their type, entities in VDM have specific prefixes and
suffixes as was shown earlier in Table 9.2. Some further frequent
suffixes are given in Table 9.3.
Suffix
Description
<Name>Text
Language-dependent Chapter 8, Section 8.5
text
<Name>Cube
Analytical cube view
Chapter 10,
Section 10.2.3
<Name>Query
Analytical query
Chapter 10,
Section 10.3
<Name>ValueHelp Value help
Table 9.3
Details
Chapter 13,
Section 13.1
Some Frequently Used Suffixes of VDM Entities
The base name of an entity is usually derived from the related SAP
object node type and describes an instance of that entity.
Field names are defined in the same way as the names of object
node types. There are, however, many more data fields with
individual semantics, each of which gets a unique field name.
Uniqueness allows the recognition of relations between different
CDS entities from the field names. This option is particularly helpful
in analytical scenarios when data from different sources are
combined. The uniqueness is technically supported by enforcing
different field names for fields with different data types in the ABAP
Data Dictionary.
Fields not only identify instances of an entity or of an SAP object
node type but also denote quantities, amounts, times or dates, texts,
and so on. These different categories are distinguished by the
representation term of a field name. The naming approach for
different representation terms is presented in the following list:
Identifier field
An identifier field is used for the unique identification of an
instance of a certain entity or of the corresponding SAP object
node type. A sales order number, for example, identifies a
particular sales order. In this case, you choose the name of the
SAP object node type, which is the denomination for an instance
of the entity, for example, SalesOrder. You can optionally use suffix
ID in the name to emphasize the representation term; however,
the suffix is omitted usually.
Universally unique identifier (UUID)
If a universally unique identifier (UUID) exists beside the common
ID, you should use a field name with suffix UUID, for example,
BusinessPartnerUUID. Other alternative identifiers can get a suffix
as well, for example, WBSElementInternalID or PersonExternalID.
Sometimes, the same entity is referenced by multiple fields of a
view, perhaps in different roles. If so, additional qualifiers are
used, for example, SenderCostCenter and ReceiverCostCenter.
Code
A code is a value in a fixed value list that is only changed by
altering the system configuration, for example, a code for
languages (Language) or currencies (Currency). For these field
names, suffix Code can be omitted as well. Semantic subtleties are
expressed by qualifiers, for example, CompanyCurrency or
CountryISOCode.
Indicators
Indicators represent Boolean values. An indicator’s name is a
statement that corresponds to the logical value of the field, for
example, OrderIsReleased or NotificationHasLongText.
Amount and quantity field
The name of an amount field can contain a reference to its
currency, for example, NetAmountInDisplayCurrency. As the
relation to the currency is also expressed by annotations, this
information can be omitted, for example, TaxAmount. Quantity fields
are similar, for example, OrderQuantity or
MinDeliveryQtyInBaseUnit.
Counters and durations
For counters that specify the number of things, prefix NumberOf is
used, for example, NumberOfRecipients. Durations can have a
related unit field, for example, ForecastedDuration with
ForecastedDurationUnit, but it’s not mandatory. In such cases, the
unit should be included in the field name, for example,
TripDurationInDays.
Points in time
For points in time, DateTime is part of the field name, for example,
CreationDateTime. For dates, Date is appended, and, for times,
Time is appended.
Rates and ratios
In field names for rates or ratios, Percentage and Fraction are
forbidden. The following combinations can be used instead:
ConditionRateInPercent, UtilizationPercent, ExchangeRate, and
ProbabilityRatio.
The name of a parameter of a VDM view consists of prefix P_ and a
semantical name, which is formed like a field name and often
actually used as a field name, for example, parameter
P_DisplayCurrency.
An association of a VDM view points to another VDM view. Usually,
the first letter of the target view is omitted, and the remainder is used
as the association name. Association _Customer, for example, often
points to view I_Customer. Alternatively, you can abbreviate the
association name if it remains understandable in its context. The
association from view I_SalesOrder to I_SalesOrderItem is called
_Item, for example. The association from an entity view to its text
view is usually called _Text.
If there are multiple associations with the same target view, qualifiers
are added to the association names, for example, _SenderCostCenter
and _ReceiverCostCenter.
[ ! ] Names Reserved for Customers and Partners
When you define your own SAP object types, object node types,
or CDS entities, use your reserved namespace /<namespace>/ or
the letters ZZ or YY as a prefix. When you extend an SAP standard
view, also use your namespace or prefixes ZZ or YY for your own
custom field names. Otherwise, name clashes are possible with
names of new fields that SAP introduces in a new software
version. These clashes can lead to syntax errors and issues
during the upgrade to a new SAP software version.
9.5
Basic Interface View for the Sales Order
Now, we’ll look at a concrete VDM view in detail. We’ll use basic
interface view I_SalesOrder that represents the header data of a
sales order.
9.5.1
View Annotations
We start with the view annotations of view I_SalesOrder. Listing 9.1
shows the first part of the view definition in SAP S/4HANA 2023, with
all view annotations. There may be deviations in other versions.
@ClientHandling.algorithm: #SESSION_VARIABLE
@EndUserText.label: 'Sales Order'
@VDM: {
viewType: #BASIC,
lifecycle.contract.type: #PUBLIC_LOCAL_API
}
@AccessControl: {
authorizationCheck: #CHECK,
personalData.blocking: #('TRANSACTIONAL_DATA'),
privilegedAssociations: [ '_CreatedByUser', '_LastChangedByUser',
'_BusinessAreaText', '_CostCenterBusinessAreaText',
'_CreditControlAreaText' ]
}
@AbapCatalog: {
preserveKey: true,
sqlViewName: 'ISDSALESORDER',
compiler.compareFilter: true
}
@ObjectModel: {
compositionRoot:
true,
representativeKey: 'SalesOrder',
usageType: {
dataClass:
#TRANSACTIONAL,
serviceQuality: #B,
sizeCategory:
#L
}
supportedCapabilities: [ #ANALYTICAL_DIMENSION,
#CDS_MODELING_ASSOCIATION_TARGET, #SQL_DATA_SOURCE,
#CDS_MODELING_DATA_SOURCE, #EXTRACTION_DATA_SOURCE ],
modelingPattern: [ #ANALYTICAL_DIMENSION ]
}
@Metadata.ignorePropagatedAnnotations: true
@Analytics.dataCategory: #DIMENSION
@Metadata.allowExtensions:true
@ObjectModel.sapObjectNodeType.name: 'SalesOrder'
define view I_SalesOrder
as select from I_SalesDocument as SalesDocument
Listing 9.1
View Annotations of VDM View I_SalesOrder
Annotation @ClientHandling.algorithm: #SESSION_VARIABLE specifies
the method for handling client-dependent data by using session
variable CDS_CLIENT in SAP HANA. All VDM views use this method.
The ABAP database interface sets the session variable to the
current client (SY-MANDT) or to the requested client, and the CDS view
only selects data where the client field equals session variable
CDS_CLIENT. That way, a SQL request from a VDM view returns the
data of exactly one client.
[»] Execute a VDM View in SAP HANA
CDS only supports the execution through the ABAP infrastructure,
not the native execution of the created view in SAP HANA. You
can test your VDM views natively in SAP HANA, for example, from
the SQL Console in SAP HANA, by defining session variable
CDS_CLIENT manually with the following statement:
set 'CDS_CLIENT' = '001';
Annotation @EndUserText.label: 'Sales Order' defines a description
of the view that is translated to all languages. Usually, this is the
semantic name of the CDS view from which the camel case notation
of the view name was composed. This description is displayed in
apps that show and use VDM views, for example, in the View
Browser app that we’ll introduce in Section 9.6.1.
Annotation @VDM.viewType: #BASIC identifies the view as a VDM basic
interface view, and @VDM.lifecycle.contract.type:
#PUBLIC_LOCAL_API indicates that this view is supposed to be
released for system-internal reuse according to stability contract C1
(see Chapter 14). Accordingly, the API state of the view is set to
Released for system-internal use, and the view can be used in cloud
development and in key user apps for custom developments.
Annotation @AccessControl.authorizationCheck: #CHECK indicates
that an access control must be defined for this view. Annotation
@AccessControl. privilegedAssociations: … marks some
associations of the view as privileged so that they can be used for
retrieving additional details from their target views in an exposed
OData service, even if these target views contain sensitive data that
would otherwise not be accessible. More details of these annotations
are explained in Chapter 5, Section 5.2.
Annotation @AbapCatalog.sqlViewName: 'ISDSALESORDER' defines the
SQL view name of the CDS view, and annotation
@AbapCatalog.preserveKey: true ensures that the SQL view gets the
same key as the CDS view. Starting with SAP S/4HANA 2023, this
annotation is set by default and not needed any more. Annotation
@AbapCatalog.compiler.compareFilter: true enables an optimization
of joins generated from the used path notations. It’s generally
recommended to set this annotation if the view implementation uses
association paths to retrieve fields.
Annotation @ObjectModel.compositionRoot: true shows that view
I_SalesOrder is the root view of a hierarchy of views, connected by
composition associations. Such a hierarchy has exactly one root
view. In this example, the composition hierarchy defines the sales
order business object. You can find more information on
compositions in Chapter 8, Section 8.6.
Annotation @ObjectModel.representativeKey: 'SalesOrder' identifies
field SalesOrder, that is, the sales order ID, as the representative key
of the view. More details are given in Chapter 8, Section 8.4.
Every VDM view is categorized by the following three criteria that
enable consumers of the view to estimate its runtime behavior and
its impact on the overall system load:
@ObjectModel.usageType.dataClass: #TRANSACTIONAL
The first characteristic, dataClass, shows the data type of the
view: transactional data, master data, organizational data,
Customizing data, metadata, and mixed data.
@ObjectModel.usageType.serviceQuality: #B
The second characteristic, serviceQuality, indicates the runtime
behavior of the view that results from multiple factors, including
the response time when selecting a single row. Possible values
are A, B, C, D, and X. Category A fulfills the highest standards,
category B can be used in business logic for transactions or
background processing, category C views may still be used for
single object retrieval in transactional scenarios, and category D is
only appropriate for analytic purposes. Views in category X are
intended for special scenarios only.
@ObjectModel.usageType.sizeCategory: #L
The third characteristic, sizeCategory, ranges from S to XXL and
describes the expected data volume in a productive system that is
processed when executing the view.
Annotation @ObjectModel.supportedCapabilities lists the features
that make the view suitable for certain scenarios. View I_SalesOrder
can be used as a data source or association target when defining
new views, it’s possible to select data via SQL from it, I_SalesOrder
can be used in analytics as a dimension view, and it can be used to
extract data to an external system, such as a data warehouse.
Annotation @ObjectModel.modelingPattern states that the view is
modeled according by the pattern of an analytical dimension. These
annotations will be further explained in Chapter 14, Section 14.4.
Annotation @Metadata.ignorePropagatedAnnotations: true prevents
the propagation of annotations from views that are used as data
sources in the definition of I_SalesOrder, for example,
I_SalesDocument (see Chapter 4, Section 4.3.2, for details). This is a
precaution to avoid unwanted changes to the view semantics by its
data sources. Annotation @Metadata.allowExtensions: true enables
a customer to adapt selected annotations of this view by a metadata
extension (see Chapter 4, Section 4.4).
Annotation @Analytics.dataCategory: #DIMENSION identifies the view
as an analytical dimension view. Details on this type of view are
given in Chapter 10, Section 10.2.4.
Annotation @ObjectModel.sapObjectNodeType.name: 'SalesOrder'
specifies the SAP object node type to which the view corresponds
(Section 9.2).
9.5.2
Structure of the Sales Order View
The data of a sales order header isn’t persisted in a single database
table. Moreover, other business objects, such as the sales contract,
store their data in the same tables. As a result, view I_SalesOrder is
defined on intermediate views, as you can see in Figure 9.9.
Figure 9.9
Structure of View I_SalesOrder
You can visualize the structure of a CDS view in Eclipse. Choose
Open With • Dependency Analyzer from the context menu of the
view editor. Note that the presented structure only shows effective
selections from data sources; potential selections via defined
associations aren’t displayed.
A sales order is only one of multiple specializations of a sales
document. Therefore, basic interface view I_SalesOrder isn’t defined
directly on the database tables but as a filtered projection of view
I_SalesDocument. This represents a sales document, that is, the
generalization of a sales order, sales contract, and so on. The filter
defined in view I_SalesOrder selects only the sales orders from all
available sales documents.
The data of the sales order header originates from three different
tables: table VBAK, table VBKD, and table VEDA. Of these database
tables, only the main table VBAK is directly exposed by its own view
I_SalesDocumentBasic. All data is also accessible in view
I_SalesDocument. Still, I_SalesDocumentBasic was introduced
because it covers many use cases and helps to keep the static
complexity low for VDM views using it, which improves the runtime
performance. Tables VBKD and VEDA, which are also used in view
I_SalesDocumentItem, aren’t exposed by their own views.
You may ask why the redundancy is allowed in the layer of basic
interface views that is introduced by the three views I_SalesOrder,
I_SalesDocument, and I_SalesDocumentBasic, which are based on
each other and on the same table VBAK. The redundancy is caused
by two different situations:
The first is the specialization/generalization relationship between
I_SalesOrder and I_SalesDocument. Both views are needed as
independent entities with their own semantics. As a result, they
can and must coexist.
The second is a real redundancy. View I_SalesDocumentBasic is a
simplified variant of I_SalesDocument. This redundancy is accepted
because the simplified version is clearly visible as a restriction and
because it’s mandatory for improving the performance of
important scenarios, for example, the detection and resolution of
sales order fulfillment issues.
Extension include view E_SalesDocumentBasic is provided for custom
extension fields of table VBAK. Association _Extension from the sales
order view to the extension include view (see Listing 9.2) enables
access to these extension fields. Chapter 15 explains this in detail.
9.5.3
Specialization
When specializing the sales document view to the sales order view,
most parts of the sales document model can be simply transferred.
Listing 9.2 shows a part of the view definition of I_SalesOrder.
define view I_SalesOrder
as select from I_SalesDocument as SalesDocument
association [0..*] to I_SalesOrderItem as _Item
on $projection.SalesOrder
= _Item.SalesOrder
association [0..1] to I_SalesOrderType as _SalesOrderType
on $projection.SalesOrderType = _SalesOrderType.SalesOrderType
association [0..1] to E_SalesDocumentBasic as _Extension
on SalesDocument.SalesDocument = _Extension.SalesDocument
{ key cast(SalesDocument as vdm_sales_order preserving type)
as
SalesOrder,
@ObjectModel.foreignKey.association: '_SalesOrderType'
cast(SalesDocumentType as sales_order_type preserving type) as
SalesOrderType,
...
CreatedByUser,
LastChangedByUser,
@Semantics.systemDate.createdAt: true
CreationDate,
@Semantics.systemTime.createdAt: true
CreationTime,
@Semantics.systemDate.lastChangedAt: true
LastChangeDate,
@Semantics.systemDateTime.lastChangedAt: true
LastChangeDateTime,
...
@ObjectModel.association.type: [#TO_COMPOSITION_CHILD]
_Item,
@ObjectModel.association.type: [#TO_COMPOSITION_CHILD]
_Partner,
_SalesOrderType,
_CreatedByUser,
...
}
where SalesDocument.SDDocumentCategory = 'C'
Listing 9.2
View I_SalesOrder as a Specialization of I_SalesDocument
In the specialization of I_SalesDocument, only the relevant fields are
selected. Most field names need no specialization and are kept. The
name of the key field is changed from SalesDocument to SalesOrder.
The related data element is changed as well to adjust the field
labels. CDS language element cast … preserving type ensures an
unchanged technical data type; such a cast has no impact on the
database execution.
Complete view I_SalesOrder has a lot of fields and a high number of
associations. Many of these are used as foreign key associations of
the fields. Most associations are projected from I_SalesDocument, but
only those associations that point to views specific to sales orders
are explicitly defined, as well as the extension association, which can
never be projected.
9.5.4
Element Annotations
Listing 9.2, shown previously, includes some field annotations of
view I_SalesOrder. When you look at complete view I_SalesOrder in
your system, you’ll find many field annotations but only a few
different types of them. You know most of them from Chapter 8
already. New annotations are as follows:
Some @Consumption.valueHelpDefinition annotations for defining
value help for fields (see Chapter 13, Section 13.1)
Some @Consumption.hidden annotations at text associations to
hide them from consumers as these were only defined for
technical reasons
Several @Analytics.internalName : #LOCAL annotations to enforce
local InfoObject names in analytics (see Chapter 10, Section 10.4)
9.6 Tips for Finding Virtual Data Model
Views
In an SAP S/4HANA system, a large number of VDM views exist.
Sometimes, it’s not easy to find a VDM view with specific business
semantics or special technical properties or to get an overview of
existing views. In this section, we’ll walk through various tools that
help you explore and find existing VDM views.
9.6.1
App
SAP Business Accelerator Hub and View Browser
SAP Business Accelerator Hub is accessible for everyone at
https://api.sap.com. Along with various other APIs here, SAP
publishes all released CDS views of SAP S/4HANA Cloud, public
edition. By registering for a free trial of SAP S/4HANA Cloud, you
can navigate further to the View Browser app of your trial and
browse through all CDS views in a live environment.
Start by navigating to SAP Business Accelerator Hub
(https://api.sap.com) where you’ll see an overview screen full of
content. Click on Explore in the menu at the top, and choose the
category CDS Views, as shown in Figure 9.10.
Figure 9.10
Navigate to CDS Views
This leads to an extensive list of CDS views. If you enter a search
term, such as “sales order”, the list is reduced, as shown in
Figure 9.11.
Figure 9.11
Search for CDS Views
Click on a tile to display details of the CDS view and its
documentation, as shown in Figure 9.12.
Figure 9.12
Details of a CDS View at SAP Business Accelerator Hub
By clicking on the Show in View Browser button, you navigate to
the detail view of the View Browser app when you’re logged on to
the free trial of SAP S/4HANA Cloud. Otherwise, you’re prompted to
log on or register. After that, you may have to click the Show in View
Browser button again.
The View Browser app shows comprehensive information on the
selected CDS view in a live environment, as shown in Figure 9.13. It
also offers direct navigation to the details of associated CDS views.
Figure 9.13
Details of a CDS View in the View Browser App
From the detail view, you can’t go directly to the overview screen of
the View Browser app where you could browse all CDS views in your
SAP S/4HANA Cloud free trial. Also on your home page, the View
Browser app isn’t shown. However, you can find the app quickly with
the search function in the header row and start it (see Figure 9.14).
Figure 9.14
Search for the View Browser App
Alternatively, you can edit the URL in your internet browser and
remove the last part to get something like
https://myxxxxxx.s4hana.ondemand.com/ui#CDSView-browse with
your own trial number instead of the placeholder.
The View Browser app displays CDS views with their fields,
annotations, and associations, even if these aren’t released. It offers
various search and filter capabilities. Figure 9.15 shows, for
example, the basic interface views in the VDM that are released for
use in the key user apps.
Figure 9.15
Released Basic Interface Views of the VDM in the View Browser
You can use the View Browser app in SAP S/4HANA as well. Further
information is available in the SAP Fiori apps reference library
(https://fioriappslibrary.hana.ondemand.com/) where the app is
registered with app ID F2170.
9.6.2
Search in ABAP Development Tools
With the Eclipse-based ADT, you can access all CDS views. Its
standard dialog box for opening an ABAP development object also
offers search capabilities. Combining these with the VDM naming
conventions gives you several options.
Start the dialog for opening a development object from the menu by
choosing Navigate • Open ABAP Development Object or by
clicking the
icon. You can now enter a search text. All
development objects are selected that have the search text at the
beginning of their name. By adding a filter of “type:<type>”, you can
limit the result to a certain type of development object. Table 9.4
shows the types that are relevant in the context of CDS.
Type
Description
DDLS CDS data definition
STOB CDS view, table function, or other CDS entity
DCLS CDS access control
DDLA CDS annotation definition
DDLX Metadata extension
NONT SAP object node type
RONT SAP object type
Table 9.4
Types of Development Objects in the Context of CDS
By adding “api:released”, you can restrict your search to released
development objects. Note that, technically, the data definition (DDLS)
of a CDS view is released, not the CDS entity (STOB). Figure 9.16
shows an example.
Figure 9.16
Search for Released CDS Data Definitions
Finally, you can use the wildcards * and ?, as well as the symbol <,
for the end of a name. With these, you can define a search string
such as c_sales*q*ry< type:ddls, which finds analytical query views
that follow the VDM naming conventions.
These are the options of the Open dialog in ADT. Unfortunately, you
can’t use conditions on annotations of the views, which play an
important role in VDM for characterizing views. However, there is
another way to accomplish that, as you’ll see in the next section.
9.6.3
Search Views with Specific Annotations
The following approach for searching CDS views with specific view
annotations isn’t officially supported by SAP, but it’s quite useful in
practice. Let’s walk through the steps:
1. Start the data browser (Transaction SE16) for table DDHEADANNO.
Figure 9.17 shows the selection screen.
2. In the STRUCOBJN field, enter a selection for the views you’re
interested in, for example, search pattern “I_*”.
3. Enter the annotation name in the NAME field, without a leading
“@”, but as a complete path, separated by “.”, for example,
“OBJECTMODEL.USAGETYPE.SERVICEQUALITY”. For arraytype annotations, the name is stored with suffixes $1$, $2$, and
so on for the single-array entries.
4. Enter the annotation value in the VALUE field, for example, “#A”.
5. Start the selection by pressing (F8).
Figure 9.17
Selection of Views with Specific Annotations
The result is a list of all VDM interface views (due to the VDM
naming conventions) with a runtime behavior of category A (refer to
Section 9.5.1).
You can similarly search for element annotations in table
DDFIELDANNO, but you’ll only find annotations that are maintained
directly in the view definition. If you also want to consider propagated
annotations or annotations from metadata extensions, the (internal)
annotation API can help you, that is, the methods of ABAP class
CL_DD_DDL_ANNOTATION_SERVICE.
9.6.4
ABAP Where-Used List
A further option for searching CDS views is the ABAP where-used
list in the ABAP Data Dictionary (Transaction SE11). Enter a
database table, for example, “VBAP” in the Database Table field,
and choose Utilities • Where-Used List from the menu. You can
restrict the result to CDS views by choosing Data Definitions in the
dialog box, as shown in Figure 9.18.
Figure 9.18
Find CDS Models with the ABAP Where-Used List
9.7
Summary
In this chapter, you’ve learned about the VDM approach in SAP
S/4HANA that intends to expose all application data with additional
semantic information as CDS entities. After sections on the structure
of the VDM and the use of names in VDM, you saw the details of a
concrete VDM view, which will help you better understand other
VDM views as well. Finally, we showed several methods for
searching or browsing the CDS views in SAP Business Acceleration
Hub or in your system to find views that fit your needs.
The next chapter will cover the use of CDS views in analytics.
10 Modeling Analytical
Applications
Core data services (CDS) views unlock the comprehensive
data sets of modern business applications. The analytical
CDS model enables reporting and data analysis directly on
your operational data. You can easily add your own
analytical views to cover your specific needs.
With ABAP CDS, you can define complex analytical evaluations and
calculations and use them in multidimensional analytics. In this
chapter, we provide the necessary knowledge and a deeper
understanding of modeling analytical applications. We build on the
foundations laid in the previous chapters and explain in detail how
analytical properties of CDS views are defined.
Section 10.1 is a short introduction to analytics in SAP S/4HANA in
general. Section 10.2 covers analytical views. We start with the
definition of a simple analytical view in CDS and demonstrate its use
in the analytics test environment. Next, we introduce step by step the
essential types of analytical views, cube views and dimension views,
and explain these with examples. We close by looking at some
specifics of the combined analytical model defined by these views.
Section 10.3 presents analytical queries and shows their use in
realizing complex analytics in CDS that you can adapt to the needs
of your end users. In Section 10.4, we give a short overview of the
current analytics infrastructure in SAP S/4HANA. This information is
beneficial for a better understanding of how analytical applications
are executed, in particular, for big data volumes.
By the end of this chapter, you’ll have learned the CDS-based
foundation of analytical applications in SAP S/4HANA in detail, and
you’ll be able to implement analytical CDS views and queries with
powerful functions.
10.1
Analytics in SAP S/4HANA
With SAP S/4HANA, you can access a variety of analytical
evaluations. These are an integral part of the SAP Fiori user
interface (UI). Already on your home screen, you can use the SAP
Fiori launchpad to monitor the most important key performance
indicators (KPIs) on special tiles, as shown in Figure 10.1. By
clicking on these tiles, you can access an analytics app that allows a
detailed analysis of the data.
Figure 10.1
Analytical Tiles on the SAP Fiori Launchpad
To do so, no separate data warehouse is necessary; the data
available in the system is directly evaluated instead. SAP calls this
option embedded analytics. You work with the most current
information as a solid foundation for performing your tasks and
making business decisions.
Besides standard analytics, which is predefined and delivered by
SAP, several options exist to define further analytical content,
tailored to the requirements of an individual customer. To facilitate
analytics definition, SAP delivers not only complete, ready-to-use
analytical applications but also prepared analytical CDS views that
can be leveraged for custom analytics.
A customer’s application experts (key users) are supported in this
process by special SAP Fiori apps, which are the key user tools.
Users can, for example, define their own analytical queries with the
Custom Analytical Queries app, use these in the Create KPI or
Create Report apps, define new tiles with their custom KPIs and
reports, and provide these to other users.
[»] More Information on SAP S/4HANA Embedded Analytics
You can find more information on SAP S/4HANA embedded
analytics, including its customization, use, and integration, in SAP
S/4HANA Embedded Analytics by Jürgen Butsmann, Thomas
Fleckenstein, and Anirban Kundu (SAP PRESS, 2021; www.sappress.com/5226).
In this chapter, however, we’ll focus on the background—the
analytical model—which is the basis for embedded analytics and key
user tools. This chapter targets technical experts and shows how to
define analytical views or complex analyses by creating new CDS
views in the Eclipse-based ABAP development environment. For this
development, you can reuse the CDS view models delivered by SAP.
Your newly defined views can be leveraged by key users in turn.
10.2
Analytical Views
We start our introduction to the CDS modeling of analytical views
with a simple analytical cube view. After showing the view’s definition
and giving a short explanation, we’ll execute and demonstrate it in
the analytics test environment to provide you with a basic
understanding of analytical view functionality. Afterward, we’ll study
the properties and behavior of analytical cube and dimension views
in detail with more examples. At the end of the section, we’ll take a
holistic look at the analytical model and its consistency constraints.
10.2.1
First Analytical Cube View
The simplest way to define an analytical view is to build it on top of a
CDS view of the virtual data model (VDM) delivered by SAP, as
discussed in Chapter 9. Many of these views are prepared for use in
analytics.
To do so, use the ABAP Development Tools (ADT) to create a new
CDS view with data definition language source (DDLS) name
ZB_SALESORDERITEMCUBE01 and the source code from Listing 10.1, as
explained in Chapter 1.
[»] ABAP Data Dictionary-Based CDS Views
In this chapter, we’ll use the ABAP Data Dictionary-based CDS
views instead of the more recent CDS view entities. ABAP Data
Dictionary-based CDS views also run on older versions of SAP’s
ABAP platform. We’ll explain the differences in the analytical
behavior of the two syntax variants when this is relevant.
@AbapCatalog.sqlViewName: 'ZB_SOIC01'
@EndUserText.label: 'Analytical Cube 01 for Sales Order Items'
@Analytics.dataCategory: #CUBE
define view ZB_SalesOrderItemCube01
as select from I_SalesOrderItem
{
SalesOrder,
_SalesOrder,
SalesOrderItem,
CreationDate,
_SalesOrder.SalesOrganization,
_SalesOrder._SalesOrganization,
_SalesOrder.SoldToParty,
_SalesOrder._SoldToParty,
_SalesOrder._SoldToParty.Country as SoldToCountry,
Material,
_Material,
@Aggregation.default: #SUM
OrderQuantity,
OrderQuantityUnit,
_OrderQuantityUnit,
@Aggregation.default: #SUM
NetAmount,
TransactionCurrency,
_TransactionCurrency
}
Listing 10.1
Analytical Cube View ZB_SalesOrderItemCube01
Sample view ZB_SalesOrderItemCube01 selects fields and
associations from CDS view I_SalesOrderItem and from further
views via association paths. It uses two annotations that are specific
and important for analytics:
@Analytics.dataCategory: #CUBE
This view annotation marks the CDS view as an analytical cube
view. Thus, the view is considered in the analytical model and can
serve as the central data source in an analysis. A single cube view
is typically used in multiple analyses.
@Aggregation.default: #SUM
This field annotation characterizes an amount or quantity field as
an analytical measure with standard aggregation behavior
summation. In an analysis, such measures are summed up by
default if no other type of aggregation (e.g., maximum
determination) is requested explicitly in the analysis.
After activation of the new CDS view, the ADT execute additional
consistency checks of the analytical model. Messages from the
analytical consistency checks have the supplement [Analytics].
Analytical errors don’t prevent the activation of the CDS view and its
execution in ABAP SQL, but does prevent its use in analytics.
If no errors are shown after activation (wait a few seconds as the
execution of the analytics checks may take some time), your new
view is ready for use in analytics.
10.2.2
Test Environment for Analytical Views
An analytical view can be executed like any other CDS view with the
Data Preview function in Eclipse. Although this preview ignores the
view’s analytical capabilities, it’s a reasonable first test to ensure that
the analytical view selects the right data.
For verification of the analytical model and for testing the view with
multidimensional analytics, the SAP GUI Transaction
RSRTS_ODP_DIS (Display Transient Provider Preview for
Operational Data Provider) is available as a test environment. Follow
these steps to test your analytical view:
1. After starting Transaction RSRTS_ODP_DIS, choose the ABAP
Core Data Services option from the ODP Context dropdown.
As you’ve defined a CDS view, enter the SQL view name
“ZB_SOIC01” of the CDS view in the ODP Name field for your
first cube view (see Figure 10.2). If you’ve defined a CDS view
entity as an analytical cube, enter the name of the CDS view
entity instead. If the view entity has annotation
@Analytics.technicalName, then enter the technical name
specified by that annotation.
Figure 10.2
Start of the Test Environment for Analytical Views
2. To start the transaction, press (F8).
3. On the next screen, the analytical model derived from the CDS
definition is shown. This presentation is quite technical and not
relevant for your first test. We’ll take a closer look at this later
(Section 10.2.5), as it provides important information if the
analytical view doesn’t work as required. Now, because you only
want to test the cube view, choose Standard Query to start an
analysis (see Figure 10.3).
Figure 10.3
Starting the Analysis for Testing
[»] Enable the Standard Query
Due to changed system settings, the Standard Query button
may not be visible in your system. If this is the case, ask your
system administrator to adjust the settings as follows:
1. Execute report SAP_RSADMIN_MAINTAIN in Transaction
SE38 or Transaction SA38.
2. Enter “DFT_QUERY_CDS_SUPPORTED” in the OBJECT
field and “X” in the VALUE field.
3. Ensure that INSERT is selected, and press (F8).
For more details, see SAP Note 2745251.
This starts a multidimensional analysis based on the analytical
cube, as shown in Figure 10.4.
Figure 10.4
Multidimensional Analysis in the Test Environment
In the main area on the right side, only the two aggregated
measures are shown in columns initially. The measure fields of
all existing sales order items are summed up as specified by
their annotated default aggregation behavior in the CDS view
definition.
4. In the Free Characteristics area at the left, you can see the
fields of the view by which the measures can be grouped. In the
test environment, these fields are called characteristics; in the
context of SAP S/4HANA and CDS, we call them dimensions.
If you click on the
icon next to the free characteristic Material,
the aggregated order quantities and net values are grouped by
the Material field in the sales order items, and the individual
materials are shown in the lines with their respective subtotals in
the Net Value and Order Quantity columns (see Figure 10.5).
Figure 10.5
Analysis Results Grouped by Material
5. In the same way, you can choose the Sales Organization free
characteristic by clicking the second
icon for grouping with
display in the columns (see Figure 10.6).
Figure 10.6
Group by Material and Sales Organization
An analytical SAP Fiori app for multidimensional analysis with this
analytical cube would show the same values, except with a different
graphical presentation and simplified handling (see Figure 10.7). For
demonstrating analytical functions, we restrict ourselves, however, to
the test environment in SAP GUI as this is directly available.
Figure 10.7
SAP Fiori Presentation of the Analysis
The SAP Fiori app also can display the numbers graphically, as
shown in Figure 10.8.
Figure 10.8
Graphical Presentation of the Analysis in SAP Fiori
In this example, you’ve learned the basic behavior of an analysis:
The measures are aggregated according to predefined behavior.
The aggregated values can be split and grouped by freely
selectable dimensions.
In the following sections, we’ll systematically explain the types and
properties of analytical views and their modeling in CDS.
10.2.3
Analytical Cube Views
An analytical cube view is a CDS view or view entity of analytics data
category CUBE. This is defined by view annotation
@Analytics.dataCategory: #CUBE. An example of a cube view is CDS
view ZB_SalesOrderItemCube01 from Listing 10.1, shown earlier.
A cube view plays the role of central data source for an analysis by
providing the numbers that are being aggregated. Therefore, at least
one field of a cube view must be a numeric field, usually an amount
or a quantity, that is marked as a measure.
[+] Names of Cube Views
Use easily understandable names for cube views. These can be
derived from the name of the main data source of the cube view,
for example, sales order items, or from the business semantics of
its measures if these are calculated within the cube by combining
multiple data sources.
A measure is defined by element annotation @Aggregation.default,
for example, @Aggregation.default: #SUM. This annotation also
defines a standard aggregation behavior. Currently, analytical cube
views support aggregation behaviors #SUM, #MIN, and #MAX when
aggregating measures; the sum of their values, their minimum, or
their maximum is determined, respectively. The standard
aggregation can have two special values, #NONE and #NOP. Both
indicate that the field isn’t a measure, and #NONE is equivalent to
having no annotation at all. Numeric fields without an aggregation
annotation are considered not properly modeled. For these fields, an
error is raised, and they can’t be used in analytics. If they shouldn’t
be aggregated, you can annotate them with #NOP, and then they can
be displayed.
[»] Obsolete Annotation
In some places, you may still find annotation @DefaultAggregation.
It has the same semantics as its successor @Aggregation.default
but may not support recent enhancements and shouldn’t be used.
Other fields of a cube view, which aren’t annotated as measures,
generally are dimension fields or dimensions for short. Exceptions
are numeric fields that aren’t measures, fields annotated with #NOP,
and certain special fields.
Dimensions can be used in the following two ways:
A user can enter filter values for dimension fields and thereby
restrict the result of an analysis.
A user can group the result of an analysis by the values of
dimension fields. When doing so, subtotals are calculated per
dimension value according to the aggregation behavior of a
measure.
Dimensions usually represent business concepts or entities, which
enables a flexible multidimensional analysis of business data. This
also explains the term cube, a three-dimensional geometric figure,
hinting at the dimensionality of data. Three dimensions of a real cube
are by far not sufficient: analytical cube views in SAP S/4HANA may
have hundreds of dimensions.
[+] Leverage Dimensions
Many dimension fields in a cube view increase the flexibility of
analytics by offering more options for filtering and grouping.
Therefore, you should enrich your cube views with further
reasonable dimension fields. You can add these per path notation
via associations or by a SQL join. However, be careful with the
cardinality of the association or join: if it’s more than 1, it can
introduce a multiplication of the measure values, leading to wrong
results. In addition, limit the number of data sources for your cube
as this increases the view complexity, which impacts execution
times.
Dimensions are important for an analytical cube in more than one
aspect. They contribute essential information to an analysis. Refer to
the analysis shown in Figure 10.6. There, you can see information
that isn’t contained in the cube view itself: the texts of materials and
sales organizations don’t belong to the result of a plain SQL
selection from the cube view. Furthermore, when setting filters on
dimension fields, a value help is provided, and additional properties
of the entity represented by the dimension field can be displayed. In
the test environment, you can use the Properties function of the
context menu on the dimension field to choose them.
All this information is derived from the cube’s context of dimensions.
Technically, this context is realized by the dimension views, which
will be presented in the next section, and by annotations of the cube
view. All these aspects together form the comprehensive analytical
model.
Your sample view ZB_SalesOrderItemCube01 has a complete context:
the necessary annotations are propagated from I_SalesOrderItem,
the main data source. To see the difference, you can deactivate
annotation propagation by including view annotation
@Metadata.ignorePropagatedAnnotations. In the source code of the
view, this looks like Listing 10.2.
…
@Metadata.ignorePropagatedAnnotations: true
define view ZB_SalesOrderItemCube02
…
Listing 10.2
Deactivate Annotation Propagation
Figure 10.9 shows the sample analysis without a propagated
dimension model.
Figure 10.9
Analysis without a Dimension Model
Texts for sales organizations or materials aren’t available anymore,
so their keys and code, that is, ID, are shown for identification.
To determine which annotations you removed, you can choose Open
With • Active Annotations in the context menu of CDS view
ZB_SalesOrderItemCube01 in Eclipse. You can find the two foreign key
associations, _SalesOrganization and _Material. They are part of the
view definition (refer to Listing 10.1) and have two analytical
dimension views, I_SalesOrganization and I_Material, as the
association target. Without the semantic information that the
associations are foreign key associations, the functionality of the
analytical model is constricted.
After a comprehensive description of dimension views in the next
section, we’ll revisit the cube view and explain the complete sample
view with all the relevant analytical annotations.
10.2.4
Analytical Dimension Views
An analytical dimension view is a CDS view or entity of analytics
data category DIMENSION. This is defined by view annotation
@Analytics.dataCategory: #DIMENSION.
Besides the analytics data category, a dimension view needs the
following:
The proper definition of a unique view key by the CDS key word
key
An identification of the view’s representative key field by view
annotation @ObjectModel.representativeKey
In an analytical scenario, a dimension view provides additional
information for a dimension field of an analytical view. A dimension
view represents the referenced data of a cube view, usually master
data or configuration data.
The connection from a dimension field to its dimension view is
established by a foreign key association at this dimension field
pointing to the dimension view, as shown in Figure 10.10.
Figure 10.10
Foreign Key Association from Cube View to Dimension View
The concrete benefits of a dimension view in an analytical scenario
are as follows:
A dimension field of a cube view usually has a code or ID as a
value, which is often quite technical. By leveraging the dimension
view, you can display a text related to the dimension field in
addition to or instead of the code or ID. Other fields of the
dimension view can also be chosen for display in an analysis.
When defining a filter for a dimension field, the related dimension
view is used for “value help” and provides possible filter values.
Dependencies between different dimension fields of an analytical
view are recognized by the related dimension views and properly
considered when grouping the result of an analysis by any of
these fields.
In the example at the end of this section, we’ll explain the first two
features. The third feature is only relevant for dimension views with
more than one key field. We’ll come back to this in Section 10.2.6.
A dimension view can also be used as the primary data source of an
analysis instead of a cube view. In this case, the analysis of cube
measures is replaced by an analysis of the number of records in the
dimension view. An analysis of measures is also possible if the
measures are defined in the dimension view.
A typical example of a simple dimension view is VDM view
I_SalesOrganization. A simplified version of this view,
ZB_SalesOrganization, reduced to the necessary analytics
components, is shown in Listing 10.3.
@AbapCatalog.sqlViewName: 'ZB_SALESORG'
@EndUserText.label:
'Sales Organization'
@Analytics.dataCategory: #DIMENSION
@ObjectModel.representativeKey: 'SalesOrganization'
define view ZB_SalesOrganization
as select from tvko
association [0..*] to ZB_SalesOrganizationText
as _Text on
$projection.SalesOrganization
= _Text.SalesOrganization
{
@ObjectModel.text.association: '_Text'
key vkorg as SalesOrganization,
waers as SalesOrganizationCurrency,
_Text
}
Listing 10.3
Simplified Dimension View of the Sales Organization
The view is annotated with data category DIMENSION. Field
SalesOrganization uniquely identifies a row, so it can be used
directly for defining the key as representative. Analyses can show
further attributes of the dimension view and the text field from a
related text view at a dimension field. Annotation
@ObjectModel.text.association at the representative key field of the
dimension view identifies the association to the related text view in
the example to view ZB_SalesOrganizationText.
This text view (see Listing 10.4) provides the language-dependent
name of a sales organization. It’s a simplified version of VDM view
I_SalesOrganizationText.
@AbapCatalog.sqlViewName: 'ZB_SALESORGTEXT'
@EndUserText.label:
'Sales Organization Text'
@ObjectModel.dataCategory: #TEXT
@ObjectModel.representativeKey: 'SalesOrganization'
define view ZB_SalesOrganizationText
as select from tvkot
association [0..1] to ZB_SalesOrganization
as _SalesOrganization
on $projection.SalesOrganization =
_SalesOrganization.SalesOrganization
association [0..1] to I_Language
as _Language on $projection.Language =
_Language.Language
{
@ObjectModel.foreignKey.association:
'_SalesOrganization'
key vkorg as SalesOrganization, _SalesOrganization,
@ObjectModel.foreignKey.association: '_Language'
@Semantics.language: true
key spras as Language, _Language,
@Semantics.text: true
vtext as SalesOrganizationName
}
Listing 10.4
Simplified Text View for the Sales Organization
A text view is characterized by view annotation
@ObjectModel.dataCategory: #TEXT.
The first field of the text view that is annotated with @Semantics.text:
true is used as standard text for a sales organization. In the
example, this is field SalesOrganizationName.
A dimension view without its own text field usually has an
association to a text view, that is, a text association. This is identified
by annotation @ObjectModel.text.association at its representative
key. In analytical scenarios, a text view needs the following model
information besides its data category:
The definition of a unique view key by CDS key word key
An identification of the view’s representative key field by view
annotation @ObjectModel.representativeKey
A key field for the language of the text, characterized by field
annotation @Semantics.language: true
A text field characterized by field annotation @Semantics.text:
true
The connections between cube view, dimension view, and text view
are shown in Figure 10.11.
Figure 10.11 Foreign Key Association from Cube to Dimension View and Text
Association from Dimension to Text View
The foreign key association must bind all key fields of the dimension
view in the ON condition, in particular, the foreign key field of the cube
with the representative key field of the dimension view. Exceptions
are the date fields in the key of a date-dependent dimension view;
for these, binding isn’t necessary because data retrieval in analytics
via the association is always performed for a specific date, which is
used to filter the association.
The text association binds in the ON condition all key fields of the
dimension view with the corresponding key fields of the text view, in
particular, the representative key fields with each other. Again, the
date fields in the key of date-dependent views are an exception, and
texts are implicitly selected in the logon language.
A more comprehensive example of a dimension view is VDM view
I_Customer, which represents the master data of a customer. A
simplified version of this view is given in Listing 10.5.
@AbapCatalog.sqlViewName:
'ZB_CUST'
@EndUserText.label:
'Customer'
@Analytics.dataCategory:
#DIMENSION
@ObjectModel.representativeKey: 'Customer'
define view ZB_Customer as select from kna1
{
@ObjectModel.text.element: ['CustomerName']
key cast( kunnr as kunnr preserving type ) as Customer,
@Semantics.text:true
cast( concat_with_space(name1, name2, 1)
as md_customer_name )
as CustomerName,
…
}
Listing 10.5
Simplified Dimension View for Entity “Customer”
For the dimension view of entity Customer, no separate text view is
needed because a customer has a language-independent name.
Instead, annotation @ObjectModel.text.element at the representative
key field identifies a text field of the view that contains the name of
the entity. In the example, this is field CustomerName, which was
specifically defined for that purpose by a string concatenation. An
appropriate data element is assigned to the field by a cast function.
This defines the technical data type of the field as well as field labels
for UIs.
[+] Names of Dimension Views
Dimension fields and dimension views usually represent business
concepts or entities. Use the name (in singular) of these entities as
the name of your own dimension views and fields for better
recognition.
Now start the test environment (Transaction RSRTS_ODP_DIS)
again for view ZB_SalesOrderItemCube01 (enter SQL view name
“ZB_SOIC01”) as in Section 10.2.2 previously, and group the
multidimensional analysis by the Sold-to Party dimension
(characteristic). This field is shown by default as text, that is, the
customer name, from the dimension view (see Figure 10.12).
Figure 10.12
Analysis with Display of the Customer Name
You can change this presentation and choose more attributes for
display. Open the context menu of the dimension, and choose
Properties. This opens the Properties tab (see Figure 10.13) on
which you choose different types of presentation for a dimension
field: you can show only the key, only the text, or both. Moreover,
you can select from further attributes of the dimension view for
display.
Figure 10.13
Selecting the Presentation of a Dimension Field
Choose Key and Text from the Characteristic Presentation
dropdown, and mark the City attribute. Then, click the Apply button
or the
icon below the tab. Figure 10.14 shows the result of your
settings.
Figure 10.14
Analysis with Display of Key, Text, and City
When entering a filter, for example, on the Material dimension field,
the possible values of the related dimension view are offered to
choose from. To enter a filter, click on the
icon beside the
dimension field. This opens the Values tab (see Figure 10.15) on
which you can enter the filter as individual values or by entering
value ranges. The exclusion of values is even possible.
Figure 10.15
Entering a Filter in the Test Environment
You’ve now learned about several usages of dimension views. In the
next section on the combined analytical model, dimension views will
play an important role as well.
10.2.5
Analytical Model in the Test Environment
Analytical cube and dimension views constitute the basic structure of
the analytical model. Text views supplement this structure with
additional information and options. If a dimension view has an
assigned hierarchy view, which will be introduced in Chapter 12,
Section 12.2, this hierarchy view and its related views further extend
the analytical model.
[»] Analytical Fact Views
Besides cube and dimension views, there are CDS views with
another analytics data category—fact views. These views are
characterized by view annotation @Analytics.dataCategory: #FACT.
Fact views mainly provide transactional data—no master data—
and are data sources for cube views. The fact views themselves
aren’t a part of the analytical data model, however.
Now take a closer look at the analytical model of your first cube view.
The complete view definition with explicit annotations that were
inherited in your first sample cube ZB_SalesOrderItemCube01 is shown
in Listing 10.6 as view ZB_SalesOrderItemCube03.
@AbapCatalog.sqlViewName: 'ZB_SOIC03'
@EndUserText.label: 'Analytical Cube 03'
@Analytics.dataCategory: #CUBE
@Metadata.ignorePropagatedAnnotations: true
define view ZB_SalesOrderItemCube03
as select from I_SalesOrderItem
{
@ObjectModel.foreignKey.association: '_SalesOrder'
SalesOrder,
_SalesOrder,
SalesOrderItem,
CreationDate,
@ObjectModel.foreignKey.association: '_SalesOrganization'
_SalesOrder.SalesOrganization,
_SalesOrder._SalesOrganization,
@ObjectModel.foreignKey.association: '_SoldToParty'
_SalesOrder.SoldToParty,
_SalesOrder._SoldToParty,
_SalesOrder._SoldToParty.Country as SoldToCountry,
@ObjectModel.foreignKey.association: '_Material'
Material,
_Material,
@Aggregation.default: #SUM
@Semantics.quantity.unitOfMeasure: 'OrderQuantityUnit'
OrderQuantity,
@ObjectModel.foreignKey.association: '_OrderQuantityUnit'
@Semantics.unitOfMeasure: true
OrderQuantityUnit,
_OrderQuantityUnit,
@Aggregation.default: #SUM
@Semantics.amount.currencyCode: 'TransactionCurrency'
NetAmount,
@ObjectModel.foreignKey.association:'_TransactionCurrency'
@Semantics.currencyCode: true
TransactionCurrency,
_TransactionCurrency
}
Listing 10.6
Complete Example of an Analytical Cube View
You can clearly recognize in Listing 10.6 the annotated foreign key
associations. Now, start the test environment again (Transaction
RSRTS_ODP_DIS), and enter the SQL view name “ZB_SOIC03” as
you did in Section 10.2.2. After execution (press (F8)), the analytical
model is represented as a tree, including information on the
dimension views (see Figure 10.16).
Figure 10.16
Analytical Model in the Test Environment
At the highest level of the tree, the fields of the cube view are
categorized according to their analytical semantics. Measures are
shown in the Key Figures subtree, together with a reference to their
currency or unit. Dimension fields are shown under KEY, DATA, and
UNIT. As our cube view has no keys, the KEY section is empty.
Units and currencies are recognized and shown under UNIT,
according to their role. Fields in the Not Included category can’t be
used in analytics, for example, numeric fields that aren’t annotated
as measures.
The yellow lines show information about the view’s fields. The first
column shows the InfoObject name of the field. This name is used by
the analytics infrastructure to uniquely address the field. You can
identify the cube field by the second column containing the field
name, or by the fifth column, the field label. The green icons at the
end of the yellow rows give a quick hint at the modeling elements
available for the field:
This icon indicates fields with a foreign key association to a
dimension view.
This icon indicates fields that have a related text field (in the
dimension or text view).
This icon shows that a hierarchy exists for this dimension field.
For dimension fields with a dimension view, you can drill further into
the tree if the annotated foreign key association was properly
modeled. In this case, the fields of the dimension view are shown on
the next level.
A useful function of the test environment is a check of metadata, that
is, the analytical model, via Check Metadata in the button bar. It
executes consistency checks and displays their results.
10.2.6
Consistency of the Analytical Model
An individual analytical model consists of a central data source,
usually a cube view, and the dimension views of the data source’s
dimension fields. They form a star schema of CDS views, not of
tables, which is common for data warehouse applications.
You can flexibly control which views are used as dimension views of
a field by defining the foreign key association. It could be necessary,
for example, to use a different dimension view that includes more
fields for display or that excludes some fields that are critical for data
protection reasons.
Dimension views can’t be exchanged arbitrarily, however, as they
have relationships among each other on which the consistency of
the analytical model relies. Consistency comes into play as soon as
a dimension view has more than one key field. To better understand
the situation, we consider VDM view I_Country as representing
countries (see Listing 10.7) and VDM view I_Region for regions (e.g.,
states) in countries (see Listing 10.8).
@Analytics.dataCategory: #DIMENSION
@ObjectModel.representativeKey: 'Country'
define view I_Country as select from t005
{
key Country,
…
}
Listing 10.7
Extract of VDM View I_Country
@Analytics.dataCategory: #DIMENSION
@ObjectModel.representativeKey: 'Region'
define view I_Region as select from t005s
{
@ObjectModel.foreignKey.association: '_Country'
key Country,
key Region,
_Country
}
Listing 10.8
Extract of VDM View I_Region
A region isn’t uniquely defined by key field Region; the country to
which the region belongs is always required (key field Country). View
I_Region has a foreign key relation to view I_Country, expressed by
foreign key association _Country.
A cube view with a region as a dimension field always needs a
dimension field for the country to uniquely identify the region,
typically named Country. The foreign key association from the cube
view to dimension view I_Region is also based on field Country, as
you can see in Listing 10.9.
@Analytics.dataCategory: #CUBE
define view ZB_RegionDemoCube as select from …
association [0..1] to I_Country as _Country
on
$projection.Country =_Country.Country
association [0..1] to I_Region as _Region
on
$projection.Region =_Region.Region
and $projection.Country =_Region.Country
{
Country, _Country,
Region, _Region,
…
}
Listing 10.9
Cube with Dimensions Country and Region
The views are connected by their foreign key associations, as shown
in Figure 10.17.
Figure 10.17
Associations among Cube, Region, and Country
In dimension views with multiple key fields, the foreign key
associations of the superordinate key fields in the dimension view
and the foreign key associations of the corresponding superordinate
fields in the cube view must point to the same dimension view. In the
example, this means that the two foreign key associations, _Country
of the cube view and _Country of the dimension view for the regions,
must point to the same dimension view I_Country.
It’s not allowed to let foreign key association _Country of the cube
point to different view ZB_Country without also changing the target of
association _Region to new view ZB_Region, whose foreign key
association again points to ZB_Country.
[»] Replacement of Dimension Views
If you want to replace a dimension view, for example, the standard
view I_Country, with a different one, it’s not enough to just change
the cube view. You also have to adjust all other dimension views of
this cube that have a key field with a foreign key association to the
dimension view you want to replace, for example, I_Region.
Dimensions with multiple key fields get special treatment in the
multidimensional analysis. If a user groups by regions, for example,
the analytical infrastructure automatically detects that a region is only
unique within a country and groups by the country as well.
That way, the results of the analysis are correctly aggregated from a
business perspective. Without knowing the models of the dimension
views, the analytical infrastructure could not be supported here, and
the analysis would deliver meaningless results.
10.3
Analytical Queries
Using the analytical model of cube and dimension views as a
foundation, you can define and execute diverse analytical
evaluations and calculations as analytical queries to address the
manifold requirements of users. While the analytical model is kept
stable and reused multiple times, many specific queries are possible
to control the analytical access in detail and satisfy specific needs.
In a query, you can define the following initial settings for an
evaluation, as you’ve seen in Section 10.2.2:
Initial layout of the presentation
The rows and columns shown, and the display of texts, subtotals,
and sorting order.
Initial selection of data
Default values for filters and variables, based on the current
context, for example, the current date.
If needed, users can change these defaults and settings
interactively.
The real power of a query, however, lies in its calculation capabilities.
These allow the definition of new measures that aren’t available in
the analytical model itself. You can create evaluations meeting your
business requirements by defining formulas, restricted measures,
and exception aggregations.
After introducing the definition of analytical queries, we’ll discuss
these capabilities in detail.
10.3.1
Definition of an Analytical Query
An analytical query can be defined in CDS with three syntax
variants:
As a classical ABAP Data Dictionary-based CDS view that selects
from an analytical cube or dimension view by the annotation
@Analytics.query: true. We’ll use this variant in the following
examples as it’s also available in previous versions of the ABAP
platform.
As a CDS view entity with the annotation @Analytics.query: true.
We don’t recommend using this variant as we expect future
improvements by SAP only for the next syntax variant.
As a transient CDS projection view with provider contract
analytical_query. This syntax variant is available since 2022 and
recommended by SAP. Our examples will also show the new
syntax where necessary.
In all three variants, an analytical query selects data from an
analytical cube or dimension view. These data sources can be ABAP
Data Dictionary-based views or view entities as it makes no
difference for the analytical behavior of a query.
It’s technically possible to retrieve data by a standard SQL SELECT
statement from an ABAP Data Dictionary-based query or from a
CDS view entity that isn’t defined as transient. In this case, the
specific analytical aspects are ignored, however, and the SQL
request only returns the pure data as a result. To leverage the
analytical capabilities of a query and the underlying analytical model,
the query must be executed by an analytical infrastructure. In
Section 10.4, we’ll briefly explain the analytical infrastructure of SAP
S/4HANA.
A simple analytical query based on your first analytical cube view is
shown in Listing 10.10.
@AbapCatalog.sqlViewName: 'ZB_SOIQ01'
@EndUserText.label: 'Query 01 for sales order items'
@Analytics.query: true
define view ZB_SalesOrderItemQuery01
as select from ZB_SalesOrderItemCube01
{
Material,
SoldToParty,
SoldToCountry,
OrderQuantity,
NetAmount
}
Listing 10.10
Simple Analytical Query
You can test the full analytical capabilities of your query in the query
monitor, the test environment for analytical queries (Transaction
RSRT). Follow these steps:
1. Log on with SAP GUI, and start Transaction RSRT.
2. In the Query field, enter the characters “2C”, directly followed by
the SQL view name of the query view, for example,
“2CZB_SOIQ01”, for your first analytical query (see
Figure 10.18). If you’ve defined the query as a CDS view entity
or as a CDS projection view, enter “2C” and the name of the
CDS view instead. If the view is annotated with
@Analytics.technicalName, then enter “2C” and the technical
name specified by that annotation.
Figure 10.18
Starting the Test Environment for Analytical Queries
3. Press the (Enter) key. The system adds the system-internal
cube name 2CZB_SOIC01 before the system-internal query
name 2CZB_SOIQ01 in the Query field. This is analogously
composed from the SQL view name of the cube, prefixed by the
characters 2C (see Figure 10.19). If the cube is defined as a
CDS view entity, either its name or its annotated technical name
is shown instead, as it has no SQL view name.
Figure 10.19
Displaying the Cube Name/Query Name
4. Keep the default values in the other fields, in particular ABAP
BICS in the Query Display field.
5. Click the Execute button. This starts the multidimensional
analysis defined by the query (see Figure 10.20).
This analysis is almost identical to the analysis in the test
environment of your first cube in Section 10.2.2. The same
infrastructure is used, and the query just selects the cube fields
specified by the projection list of the CDS query view, which reduces
the number of dimensions.
Figure 10.20
Analytical Query in the Test Environment
[+] Use of Business Intelligence Tools
We’re using the ABAP query monitor (Transaction RSRT) to
execute the queries. This is a pure test environment for analytical
queries that shouldn’t be offered to business users.
You can test your CDS-defined analytical queries in any regular
business intelligence UI, for example, in SAP Analysis for
Microsoft Office. Just use the same system-internal query name
as in the test environment. For ABAP Data Dictionary-based
queries, that is 2C followed by its SQL view name. For queries
defined as CDS view entities or as projection views, that is 2C
followed by the name of the view, or by the technical name as
defined by annotation @Analytics.technicalName.
Queries offer the same options to adapt the presentation to your
needs as you saw for cubes (Section 10.2.2 and Section 10.2.4):
Display of text or attributes of dimension fields
Group by dimension fields in rows or columns
Filter by dimension fields
Be sure to make yourself familiar with these options in the test
environment.
Now we consider a different syntax variant. Listing 10.11 shows your
first analytical query in the syntax of a transient CDS projection view.
@EndUserText.label: 'Query 01 für Kundenauftragspositionen'
@AccessControl.authorizationCheck: #NOT_ALLOWED
define transient view entity ZB_SalesOrderItemQuery01A
provider contract analytical_query
as projection on ZB_SalesOrderItemCube01
{
Material,
SoldToParty,
SoldToCountry,
OrderQuantity,
OrderQuantityUnit,
NetAmount,
TransactionCurrency
}
Listing 10.11
Simple Analytical Query Defined as a Transient CDS Projection View
An analytical query is defined as a transient CDS projection view by
syntax elements transient view entity, provider contract
analytical_query, and as projection on. The other bold parts in
Listing 10.11 are mandatory by the CDS syntax. To execute this
query in the test environment, enter
“2CZB_SALESORDERITEMQUERY01A” in the Query field.
10.3.2
Initial Layout of a Query
In an analytical query, you can define the initial layout after the start
of an analysis. The query shown in Listing 10.12 demonstrates
several options for the initial layout. Fields can be selected from the
main data source (e.g., cube) or via path notation from dimension
views or text views. The presentation of fields is controlled by field
annotations that influence the presentation of the annotated query
field.
@AbapCatalog.sqlViewName: 'ZB_SOIQ02'
@EndUserText.label: 'Query 02 for Sales Order Items'
@Analytics.query: true
define view ZB_SalesOrderItemQuery02
as select from ZB_SalesOrderItemCube01
{ @AnalyticsDetails.query.axis: #ROWS
@AnalyticsDetails.query.totals: #SHOW
@AnalyticsDetails.query.display: #KEY_TEXT
Material,
@AnalyticsDetails.query.axis: #COLUMNS
_SalesOrganization._Text.SalesOrganizationName,
@AnalyticsDetails.query.totals: #SHOW
SoldToParty,
@AnalyticsDetails.query.sortDirection: #ASC
_SoldToParty.CustomerName,
_SoldToParty.CityName,
@EndUserText.label: 'Country of Sold-To Party'
SoldToCountry,
@AnalyticsDetails.query.hidden: true
OrderQuantity,
NetAmount
}
Listing 10.12
Define the Initial Layout of a Query
Execution of the query shows the initial layout of Figure 10.21. Rows
are grouped by dimension field Material, which is displayed with the
ID and the material text. The totals of all materials are displayed.
Columns are grouped by dimension field SalesOrganization, but only
the text of the sales organization is shown. As the label for
dimension field SoldToCountry, the text Country of Sold-To Party is
shown. Measure OrderQuantity isn’t displayed.
Figure 10.21
Initial Layout Defined in a Query
Now remove the material dimension from the rows, and group by the
sold-to party instead. Then the layout switches to the one shown in
Figure 10.22. For the dimension field SoldToParty, the ID, name, and
city are listed in ascending order by the name. Again, totals are
calculated.
Figure 10.22
Changed Layouts
Table 10.1 shows the most popular annotations for controlling the
layout of a query.
Annotation
Layout
@AnalyticsDetails.query.axis: #ROWS
resp. #COLUMNS
Distribute measures
and dimensions to rows
or columns (measures
have to be assigned
collectively).
@AnalyticsDetails.query.totals: #SHOW
Display totals or
subtotals for
dimensions.
@AnalyticsDetails.query.display: #KEY
resp. #TEXT resp. #KEY_TEXT
resp. #TEXT_KEY
Display ID and/or text
for ABAP Data
Dictionary-based
queries.
@UI.textArrangement : #TEXT_FIRST
resp. #TEXT_LAST resp. #TEXT_ONLY resp.
#TEXT_SEPARATE
Display ID and/or text
for transient projections
views.
@AnalyticsDetails.query.sortDirection:
#ASC resp. #DESC
Sort.
@EndUserText.label: '<text>'
Define field label.
@AnalyticsDetails.query.hidden: true
Initially hide a measure
that can be inserted by
the user.
Table 10.1
Annotations for Defining the Layout of a Query
The text of a dimension field can also be displayed by following the
association path to the text view and selecting a text field in the text
view. For language-dependent text views, the ADT editor will show a
warning that the text association can modify the cardinality of the
result set. You can ignore this warning for ABAP Data Dictionarybased queries because the analytical infrastructure will always use a
filter on the logon language when reading texts. For transient
projection views, you have to add the option localized as follows:
_SalesOrganization._Text.SalesOrganizationName : localized
Via the foreign key association of a dimension field, you can display
fields of the related dimension view. These are called display
attributes. It’s not possible, however, to group by a display attribute,
define a filter for it, or select further information, except its text. If you
add, for example, the country of the sold-to party via path notation
_SoldToParty.Country to the query definition, a user can neither
group nor filter by it. The analytical infrastructure offers these more
advanced capabilities only for dimension fields of the cube view.
Similarly, it’s not possible to display an attribute of a display attribute,
even if it can be reached via a path notation such as the following:
_SoldToParty._StandardAddress.AddressTimeZone.
If you add such a path expression to your query definition, it can be
activated in Eclipse. The data preview in Eclipse even shows the
address time zone correctly. In analytics, however, the time zone
can’t be displayed by this path expression—you get a message at
activation and the field will be ignored when trying to execute the
query in analytics.
To fully use a field in analytics, you have to add it to the analytical
cube, as we’ve done with fields SoldToCountry and
SalesOrganization in the CDS cube of the example.
[+] Add Further Dimension Fields to Cube Views
All fields that you want to group or filter in an analytical query view,
or for which you want to display additional attributes, have to be
available as dimension fields in your analytical cube view.
10.3.3
Filter, Select Options, Parameters
The next example shows how you can define variables for the
execution of your analytical query and prepopulate them with default
values. An end user can restrict the result set of a query by
specifying variables, similar to select options, or can provide values
for parameters that are mandatory for the execution. Typical
parameters are a key date for an analysis or a display currency into
which all amounts should be converted.
In the following analytical SAP Fiori app, the variables are offered in
a separate area where filters can be selected. Their values can be
adjusted as needed and stored for future use (see Figure 10.23).
Figure 10.23
Selection of Query Variables in SAP Fiori Design
Let’s define a query with variables for this SAP Fiori app. First
extend your cube view with the year and day of the week of the
creation date (field CreationDate), as shown in Listing 10.13. SAP
standard view I_CalendarDate is very convenient for working with
dates, weeks, months, years, and so on.
@AbapCatalog.sqlViewName: 'ZB_SOIC04'
@EndUserText.label: Analytical Cube 04 for Sales Order Items'
@Analytics.dataCategory: #CUBE
define view ZB_SalesOrderItemCube04
as select from I_SalesOrderItem
association [0..1] to I_CalendarDate as _CreationDate
on $projection.CreationDate = _CreationDate.CalendarDate
{ SalesOrder,
_SalesOrder,
SalesOrderItem,
CreationDate,
_CreationDate.CalendarYear as CreationYear,
_CreationDate._CalendarYear as _CalendarYear,
_CreationDate.WeekDay
as CreationWeekDay,
_CreationDate._WeekDay
as _WeekDay,
_SalesOrder.SalesOrganization,
_SalesOrder._SalesOrganization,
_SalesOrder.SoldToParty,
_SalesOrder._SoldToParty,
_SalesOrder._SoldToParty.Country as SoldToCountry,
Material,
_Material,
@Aggregation.default: #SUM
OrderQuantity,
OrderQuantityUnit,
_OrderQuantityUnit,
@Aggregation.default: #SUM
NetAmount,
TransactionCurrency,
_TransactionCurrency
}
Listing 10.13
Analytical Cube View, Complemented with Date Information
Now copy your analytical query view, select from your new cube
view, and add parameters P_Today and P_Country, field CreationYear,
a where condition, and the new highlighted annotations, as shown in
Listing 10.14.
@AbapCatalog.sqlViewName: 'ZB_SOIQ03'
@EndUserText.label: 'Query 03 for Sales Order Items'
@Analytics.query: true
define view ZB_SalesOrderItemQuery03
with parameters
@Consumption.hidden: true
@Environment.systemField: #SYSTEM_DATE
P_Today: abap.dats,
@EndUserText.label: 'Country of Sold-To Party'
@Consumption.valueHelpDefinition.entity:
{ name: 'I_Country', element: 'Country' }
@Consumption.defaultValue: 'DE'
P_Country: land1_gp
as select from ZB_SalesOrderItemCube04
{ @AnalyticsDetails.query.axis: #ROWS
@AnalyticsDetails.query.totals: #SHOW
@AnalyticsDetails.query.display: #KEY_TEXT
@Consumption.filter:
{ selectionType: #RANGE, multipleSelections: true }
Material,
@AnalyticsDetails.query.axis: #COLUMNS
_SalesOrganization._Text.SalesOrganizationName,
@AnalyticsDetails.query.totals: #SHOW
@Consumption.filter:
{ selectionType: #SINGLE, multipleSelections: true }
SoldToParty,
@AnalyticsDetails.query.sortDirection: #ASC
_SoldToParty.CustomerName,
_SoldToParty.CityName,
@EndUserText.label: 'Country of Sold-To Party'
SoldToCountry,
@AnalyticsDetails.query.axis: #ROWS
@Consumption.filter:
{ selectionType: #INTERVAL, multipleSelections: false }
@Consumption.derivation: {
lookupEntity: 'I_CalendarDate',
resultElement: 'CalendarYear',
binding: [ { targetElement: 'CalendarDate',
type: #PARAMETER, value: 'P_Today' } ]
}
CreationYear,
@AnalyticsDetails.query.hidden: true
OrderQuantity,
NetAmount
}
where SoldToCountry = $parameters.P_Country
Listing 10.14
Analytical Query with Variables
When you execute your new query in the test environment, the
system first offers a Variables dialog box for the variable values (see
Figure 10.24).
Figure 10.24
Variable Entry in the Test Environment
The first field, Country of Sold-To Party, is a mandatory field that is
prepopulated with the default value DE. In addition, for the Calendar
Year field, the current year is proposed as a default. You can change
these values and add filters for the material or the sold-to party.
Change the calendar year to a different year if you want to see data
of that past year, and start the evaluation by clicking the
button.
You get an output as shown in Figure 10.25.
Figure 10.25
Analysis Result Based on Defined Variables
Which variables are shown and how they are prepopulated is
controlled by annotations in Listing 10.14:
View parameter P_Today is treated as a variable but isn’t shown
due to its annotation @Consumption.hidden: true.
Annotation @Environment.systemField: #SYSTEM_DATE causes the
analytical infrastructure to assign the current system date to this
parameter.
View parameter P_Country can be entered as a variable; its
Country of Sold-To Party label is defined by annotation
@EndUserText.label.
The value help for P_Country is determined by annotation
@Consumption.valueHelpDefinition. Values for the parameter are
given by field Country of CDS view I_Country.
Annotation @Consumption.defaultValue defines default value DE for
P_Country.
Annotation @Consumption.filter at field Material causes the
infrastructure to provide a filter for this field as a variable. This
filter allows multiple value ranges for selection as well as an
inclusion/exclusion logic, comparable to the semantics of an
ABAP SELECT option or RANGE type.
The same annotation with different settings at field SoldToParty
only allows (multiple) single values as filters.
For field CreationYear, only a single interval is allowed.
Filter values for field variables, such as Material or SoldToParty, are
translated to the where condition of the SQL selection that retrieves
the data of the analysis. Values for the parameter variables, such as
P_Country, can be passed to a data source with parameters to
control its behavior. Alternatively, they can be used directly in the
query definition, for example, in the where condition of
ZB_SalesOrderItemQuery03.
Variables can be defaulted dynamically. A simple mechanism for
parameter variables offers annotation @Environment.systemField.
Table 10.2 shows the options to prepopulate a parameter with a
system field. Pay attention to the appropriate data type of the
parameter.
Annotation Value Parameter Value
#CLIENT
Current client: SY-MANDT
#SYSTEM_LANGUAGE
Logon language: SY-LANGU
#SYSTEM_DATE
System date: SY-DATUM
#SYSTEM_TIME
System time: SY-UZEIT
#USER
Current user: SY-UNAME
#USER_DATE
Date in the current user’s time zone: SY-DATLO
Annotation Value Parameter Value
#USER_TIMEZONE
Table 10.2
Time zone of the current user: SY-ZONLO
System Variables for Parameter Defaulting
Variable derivation is a more complex mechanism for determining
variable values. It’s defined by annotation @Consumption.derivation
and used previously in Listing 10.14 for prepopulating variable
CreationYear.
[»] Infrastructure Support for Derivations
Derivations aren’t just supported by the analytical infrastructure;
the ABAP application infrastructure also evaluates and executes
this annotation.
A derivation uses one input value or multiple input values as
parameters or filters for an auxiliary view, which is also called
derivation function. In the previous example of Listing 10.14,
parameter P_Today is used as an input value to filter field
CalendarDate of view I_CalendarDate, which is used as a derivation
function. The infrastructure executes a SQL SELECT request from the
auxiliary view with these parameters and filter values to determine
the possible values for the result field, which are then used to
populate the variable dynamically. The result field of the example is
CalendarYear. Table 10.3 lists the components of the applied
derivation.
Component
Annotation
Example from
@Consumption.derivation Listing 10.14
Source of the
input value
binding.type,
binding.value
#PARAMETER, P_Today
Component
Annotation
Example from
@Consumption.derivation Listing 10.14
Auxiliary view
(derivation
function)
lookupEntity
I_CalendarDate
Filtered field or
parameter in
the auxiliary
view
binding.targetElement or
binding.targetParameter
CalendarDate
Result field in
the auxiliary
view
resultElement (optional
resultElementHigh)
CalendarYear
Table 10.3
Components of a Derivation
Derivations are executed before the variable screen is presented to
a user. Therefore, it’s not possible to use variable values entered by
a user as input for a derivation. You typically need a system field or
constant as a starting point for a derivation. This is the reason for the
introduction of parameter P_Today of query view
ZB_SalesOrderItemQuery03, which actually isn’t even visible to the
user.
10.3.4
Calculation of Measures
CDS offer several options for defining new measures. When defining
a cube view, you can apply SQL functionality to define joins, unions,
or filters, as well as aggregations, arithmetic operations, and logic
operations.
For defining new measures in analytical queries, set operations join
and union aren’t available, and arithmetic operations have a different
behavior than in SQL. They aren’t applied individually to all rows of
the data source but to the aggregated rows and columns calculated
during query execution. This different type of calculation offers
further flexibility to meet the business needs of analysis. In this
section, we’ll demonstrate the different options with examples.
In the first example, we want to add a calculation to a cube view.
Define a new cube view, ZB_SalesOrderItemCube05, for this purpose;
copy the definition of ZB_SalesOrderItemCube04 into it; add two
calculated fields with their annotations after association
_TransactionCurrency, as shown in Listing 10.15; and add a where
condition. The calculation divides the NetAmount by the
OrderQuantity.
define view ZB_SalesOrderItemCube05
…
_TransactionCurrency,
@Aggregation.default: #MIN
cast( division( NetAmount, OrderQuantity, 2 )
as abap.curr(15,2) ) as AmountPerUnitMin,
@Aggregation.default: #MAX
cast( division( NetAmount, OrderQuantity, 2 )
as abap.curr(15,2) ) as AmountPerUnitMax
}
where OrderQuantity > 0
Listing 10.15
Calculation in an Analytical Cube View
The calculated fields both represent the amount or effective price per
unit for a specific sales order item. In the cube, both have the same
value. Aggregation behavior summation would be senseless for
amounts per unit from a business perspective. The determination of
a minimal or maximal amount per unit across multiple order items is
interesting, however. Therefore, aggregation behavior minimum or
maximum is annotated. This distinguishes the calculated fields from
each other.
[ ! ] Adjust the Data Type after an Amount Calculation
The data type of the result of a CDS function, in this case, function
division, may differ from the source data type. Therefore, always
check the result type of a function, and adjust it with a cast
operation if necessary.
In the concrete example, source field NetAmount has data type
CURR, but the result of the division is of type DEC. In this case, you
must reassign data type CURR to the result; otherwise, the
currency-appropriate processing won’t take place during the
output. For currencies with fewer than two decimal places (e.g.,
Japanese yen), this would output an incorrect value. In addition,
don’t forget to add annotation @Semantics.amount.currencyCode
with a reference to the associated currency field for the result field.
Now define an analytical query to display the calculated fields (see
Listing 10.16).
@AbapCatalog.sqlViewName: 'ZB_SOIQ04'
@EndUserText.label: 'Query 04 for Sales Order Items'
@Analytics.query: true
define view ZB_SalesOrderItemQuery04
as select from ZB_SalesOrderItemCube05
{
@AnalyticsDetails.query.axis: #ROWS
@AnalyticsDetails.query.totals: #SHOW
@AnalyticsDetails.query.display: #KEY_TEXT
Material,
_SalesOrganization._Text.SalesOrganizationName,
@AnalyticsDetails.query.totals: #SHOW
SoldToParty,
@AnalyticsDetails.query.sortDirection: #ASC
_SoldToParty.CustomerName,
OrderQuantity,
OrderQuantityUnit,
NetAmount,
TransactionCurrency,
@EndUserText.label: 'Minimal Amount per Unit'
AmountPerUnitMin,
@EndUserText.label: 'Maximal Amount per Unit'
AmountPerUnitMax
}
Listing 10.16
Display the Measures Calculated in the Cube
When executing the query, you get the result shown in Figure 10.26.
Figure 10.26
Calculated Fields of the Cube
As expected, a range for the prices per unit is displayed. If you group
the rows by the sales organization, you can compare these ranges
between them. Note that a reasonable comparison of the calculated
amounts per unit requires identical currencies and units. For the data
in the example, this is the case; otherwise, the data should be
grouped accordingly, or a currency or unit conversion would also be
needed. Section 10.3.7 will discuss this in more detail.
In a second example, we add a similar calculation to an analytical
query and divide measure NetAmount by OrderQuantity. This division
is executed for the numbers displayed in the multidimensional
analysis, that is, the aggregated values. Copy analytical query
ZB_SalesOrderItemQuery04 to new query ZB_SalesOrderItemQuery05,
and add a new field with its annotations, as shown in Listing 10.17.
define view ZB_SalesOrderItemQuery05
…
AmountPerUnitMax,
@EndUserText.label: 'Average Amount per Unit'
@AnalyticsDetails.query.formula: 'NetAmount / OrderQuantity'
0 as AverageAmountPerUnit
}
Listing 10.17
Calculation in an Analytical Query View
As you can see, the calculation formula in the query is specified by
annotation @AnalyticsDetails.query.formula, while the field for the
result, AverageAmountPerUnit in the example, is defined with a
constant value 0. This notation illustrates that the calculation is
executed with special analytical rules and has results that differ from
the result the same formula would have when executed by a regular
SQL SELECT from the CDS view.
In an annotated formula, you can use other fields that are calculated
in the query as intermediate results by accessing them with notation
$projection.<field>. This isn’t possible for standard calculations in
CDS views but allows the structured definition of rather complex
calculations in queries. When executing the query, you get the result
shown in Figure 10.27.
You can see that the average amount per unit is within the range
calculated in the cube view. It’s always the quotient of the Order
Quantity and Net Value columns. The same formula is also applied
to totals or subtotals. Try grouping by different dimensions
(characteristics) to become familiar with the behavior.
Figure 10.27
Calculated Fields from the Cube and Query
A further specialty in analytics is the display of currency/unit in the
Average Amount per Unit column. The analytical infrastructure
recognizes the currency or unit of the divided numbers and displays
them.
If you’ve defined the analytical query as a transient projection view,
you can express the analytical calculations directly in CDS syntax
and don’t need annotations. The same formula from Listing 10.17 for
a transient projection view is shown in Listing 10.18.
@EndUserText.label: 'Durchschnittlicher Betrag pro Einheit'
@Aggregation.default: #FORMULA
@Semantics.quantity.unitOfMeasure: 'CurrencyPerUnit'
curr_to_decfloat_amount( NetAmount ) / OrderQuantity
as AverageAmountPerUnit,
@EndUserText.label: 'Währung pro Einheit'
virtual CurrencyPerUnit : dd_cds_calculated_unit
Listing 10.18
Calculate a Measure in a Transient Projection View
Annotation @Aggregation.default: #FORMULA indicates the beginning
of an analytical formula. Amounts of data type CURR have to be
prepared according to their currency, similar to calculations in an
analytical cube. Function curr_to_decfloat_amount is used for that.
The result of the calculation again needs a unit, which is
automatically determined by the analytics infrastructure. You have to
include that unit explicitly as a virtual field in the query which you
specify by annotating the formula with
@Semantics.quantity.unitOfMeasure. For the data type of the virtual
field, you can use dd_cds_calculated_unit.
10.3.5
Restricted Measures
Restricted measures are variants of calculations in analytical
queries. For an example, copy query ZB_SalesOrderItemQuery05, and
name it ZB_SalesOrderItemQuery06. Then, replace the three
calculated measures of Section 10.3.4 with the four new calculations
from Listing 10.19.
define view ZB_SalesOrderItemQuery06
as select from ZB_SalesOrderItemCube04
{ …
TransactionCurrency,
@EndUserText.label: 'MO-WE Quantity'
case when CreationWeekDay <= '3' then OrderQuantity
else 0
end as MondayToWednesdayQuantity,
@EndUserText.label: 'TH-SU Quantity'
case when CreationWeekDay >= '4' then OrderQuantity
else 0
end as ThursdayToSundayQuantity,
@EndUserText.label: 'MO-WE Value'
case when CreationWeekDay <= '3' then NetAmount
else 0
end as MondayToWednesdayAmount,
@EndUserText.label: 'TH-SU Value'
case when CreationWeekDay >= '4' then NetAmount
else 0
end as ThursdayToSundayAmount
}
Listing 10.19
Restricted Measures
A restricted measure results from a measure of the cube via a CDS
case function. Only a single when option is allowed, and the else case
is always 0. In the definition of the when condition, no measures are
allowed. That way, the original measure is restricted to rows of the
data source that satisfy the given condition.
In query ZB_SalesOrderItemQuery06, the sales quantities and net
amounts are restricted to Monday through Wednesday and Thursday
through Sunday, respectively. The value for CreationWeekDay, used in
the restriction, is provided by the cube. Figure 10.28 shows the
results of the query.
Figure 10.28
Restricted Measures
[»] Logical Condition in Formulas
In formulas that are defined by annotation
@AnalyticsDetails.query. formula, a logical condition with case is
also possible. They are different from restricted measures in two
aspects, however:
In formulas, only measures or constants are allowed in the
condition.
The condition is evaluated for the aggregated measures, not for
the individual rows of the data source.
Listing 10.20 shows the syntax for restricted measures in transient
projection views.
@EndUserText.label: 'MO-WE Quantity'
@Semantics.quantity.unitOfMeasure: 'OrderQuantityUnit'
case when CreationWeekDay <= '3' then OrderQuantity
else null
end as MondayToWednesdayQuantity,
@EndUserText.label: 'TH-SU Quantity'
@Semantics.quantity.unitOfMeasure: 'OrderQuantityUnit'
case when CreationWeekDay >= '4' then OrderQuantity
else null
end as ThursdayToSundayQuantity,
@EndUserText.label: 'MO-WE Value'
@Semantics.amount.currencyCode: 'TransactionCurrency'
case when CreationWeekDay <= '3' then NetAmount
else null
end as MondayToWednesdayAmount,
@EndUserText.label: 'TH-SU Value'
@Semantics.amount.currencyCode: 'TransactionCurrency'
case when CreationWeekDay >= '4' then NetAmount
else null
end as ThursdayToSundayAmount
Listing 10.20
Syntax for Restricted Measures in a Transient Projection View
Note the mandatory annotation of units and currencies, and the use
of null instead of 0.
10.3.6
Exception Aggregation
With the exception aggregation method, you can combine different
types of aggregations, for example, summation and maximum, to
calculate a measure in an analytical query. This enables the
definition of more complex calculations, in which you can switch
between aggregation and calculation multiple times, while changing
the type of aggregation.
In an exception aggregation step, several types of aggregation are
possible. Table 10.4 gives an overview of supported aggregation
behavior (exceptionAggregationBehavior).
Aggregation Description
Behavior
#SUM
Summation of individual values
#MIN
Minimum of individual values
#MAX
Maximum of individual values
#COUNT
Number of individual values
#AVG
Average of individual values
#STD
Standard deviation of individual values
#FIRST
First individual value if the aggregated rows are
sorted ascending by the fields given in annotation
exceptionAggregationElements
#LAST
Last individual value if the aggregated rows are
sorted ascending by the fields given in annotation
exceptionAggregationElements
Table 10.4
Aggregation Behavior in Exception Aggregation
[+] Basic Principle of Exception Aggregation
Exception aggregation is a powerful tool to implement calculations
with special requirements. Keep the basic principles of exception
aggregation in mind:
1. Add more dimensions to the selection.
2. Perform your calculations.
3. Eliminate the additional dimensions with specific
aggregations.
Then it becomes easier to model concrete calculations with
exception aggregation.
We’ll explain this method with a few examples.
Example: Counter for Distinct Values
The first example defines a counter for distinct materials; that is,
each material is counted only once even if it occurs multiple times.
This calculation can’t be defined by default aggregation annotations
in analytical cubes, but it’s possible with exception aggregation in an
analytical query. Let’s create a new analytical query, as shown in
Listing 10.21.
@AbapCatalog.sqlViewName: 'ZB_SOIQ07'
@EndUserText.label: 'Query 07 for Sales Order Items'
@Analytics.query: true
define view ZB_SalesOrderItemQuery07
as select from ZB_SalesOrderItemCube04
{ @AnalyticsDetails.query.totals: #SHOW
@AnalyticsDetails.query.display: #KEY_TEXT
Material,
_SalesOrganization._Text.SalesOrganizationName,
@AnalyticsDetails.query.totals: #SHOW
@AnalyticsDetails.query.axis: #ROWS
SoldToParty,
@AnalyticsDetails.query.sortDirection: #ASC
_SoldToParty.CustomerName,
OrderQuantity, OrderQuantityUnit,
NetAmount, TransactionCurrency,
@EndUserText.label: 'Distinct Materials'
@AnalyticsDetails: {
exceptionAggregationSteps: [{
exceptionAggregationBehavior: #COUNT,
exceptionAggregationElements: [ 'Material' ]
}]
}
0 as NumberOfDistinctMaterials
}
Listing 10.21
Define a Distinct Counter with Exception Aggregation
Execute the query to get the results shown in Figure 10.29. As you
can see, the number of distinct materials in sales orders of a
customer is shown. You can verify the numbers by drilling into
dimension Material.
Figure 10.29
Counter for Distinct Materials
Definition of an Exception Aggregation
The definition of an exception aggregation has multiple parts. First,
you need a measure for the result of the exception aggregation. In
the example shown in Listing 10.21, the expression 0 as
NumberOfDistinctMaterials defines a new field for that purpose. In
annotation @AnalyticsDetails. exceptionAggregationSteps, the steps
of the exception aggregation are defined. The example only has a
single step.
Annotation clause exceptionAggregationElements: ['Material']
plays a decisive role as it changes the SQL SELECT request that
retrieves the data: further dimension fields are selected, and the
result is grouped by them. In the example, the SQL SELECT is
extended with dimension Material. Without exception aggregation,
the data would be retrieved by the following SQL SELECT request:
select SoldToParty, sum(OrderQuantity) …
group by SoldToParty
With exception aggregation, an extended select is executed instead:
select SoldToParty, Material, sum(OrderQuantity),
0 as NumberOfDistinctMaterials …
group by SoldToParty, Material
The new measure is added here as well.
This intermediate result is further aggregated, thus removing
additional dimension Material from the query result in the example.
For this additional aggregation, the aggregation behavior defined by
annotation clause exceptionAggregationBehavior is applied. In the
example, this is #COUNT, so the pre-aggregated rows are counted. As
the SQL SELECT was grouped by dimension Material, this counts just
the distinct materials.
If multiple exceptionAggregationSteps are defined, the initial SQL
SELECT retrieves the exceptionAggregationElements of all steps and
groups by them. Then, the aggregations of the steps are executed
one by one with the aggregation behavior specified in the step’s
exceptionAggregationBehavior clause.
In transient projection views, an exception aggregation is defined in
the same way. You must take care, however, to define the new field
as a constant with the correct data type. Listing 10.22 shows the
syntax for the first example.
@EndUserText.label: 'Distinct Materials'
@Aggregation.default: #FORMULA
@AnalyticsDetails: {
exceptionAggregationSteps: [{
exceptionAggregationBehavior: #COUNT,
exceptionAggregationElements: [ 'Material' ]
}]
}
abap.int4'0' as NumberOfDistinctMaterials
Listing 10.22
Exception Aggregation in a Transient Projection View
The second example demonstrates a calculation based on exception
aggregation. Copy cube view ZB_SalesOrderItemCube01 to new cube
ZB_SalesOrderItemCube06, and add the order’s total net amount, as
shown in Listing 10.23.
define view ZB_SalesOrderItemCube06
as select from I_SalesOrderItem
{ …
_TransactionCurrency,
@Aggregation.default: #MAX
_SalesOrder.TotalNetAmount
}
Listing 10.23
Cube View with the Order Net Value
The order’s total net amount is needed in the example because the
total net amounts of orders with certain properties will be added
together. The properties of order items will be considered as well,
especially the material of an item. As these properties can occur at
multiple items of an order, it’s not possible to just sum up the total
net amounts of all items—only the amounts of distinct sales orders
will be aggregated. This is ensured by the exception aggregation
implemented in the query shown in Listing 10.24.
@AbapCatalog.sqlViewName: 'ZB_SOIQ08'
@EndUserText.label: 'Query 08 for Sales Order Items'
@Analytics.query: true
define view ZB_SalesOrderItemQuery08
as select from ZB_SalesOrderItemCube06
{ @AnalyticsDetails.query.axis: #ROWS
@AnalyticsDetails.query.totals: #SHOW
@AnalyticsDetails.query.display: #KEY_TEXT
Material,
_SalesOrganization._Text.SalesOrganizationName,
@AnalyticsDetails.query.totals: #SHOW
SoldToParty,
@AnalyticsDetails.query.sortDirection: #ASC
_SoldToParty.CustomerName,
@AnalyticsDetails.query.totals: #SHOW
SalesOrder,
OrderQuantity,
NetAmount,
@EndUserText.label: 'Maximal Order Net Value'
@AnalyticsDetails.query.hidden: true
TotalNetAmount,
@EndUserText.label: 'Total Order Net Value'
@AnalyticsDetails: {
exceptionAggregationSteps: [{
exceptionAggregationBehavior: #SUM,
exceptionAggregationElements: [ 'SalesOrder' ]
}]
}
@AnalyticsDetails.query.formula: 'TotalNetAmount'
0 as DistinctTotalNetAmount
}
Listing 10.24
Summation for Distinct Orders Only
Figure 10.30 shows the result of the query.
Figure 10.30
Summation for Distinct Orders Only
The exception aggregation is similar to the first example but has a
few specialties. The measure for the result is again defined with
constant value 0, but annotation formula defines cube field
TotalNetAmount as the source for the value of
DistinctTotalNetAmount. Therefore, the SQL SELECT request also
retrieves measure TotalNetAmount. A summation of this measure
would lead to wrong results, so we would like to have no aggregation
at all. But without an annotated default aggregation, the field would
not be recognized as a measure; therefore, we defined its
aggregation behavior in the cube as #AVG. The executed SQL SELECT
request looks as follows:
select Material, SalesOrder,
avg(TotalNetAmount) as DistinctTotalNetAmount …
group by Material, SalesOrder
Due to grouping by SalesOrder, the calculated measure now has the
total net value of order SalesOrder, independent from the number of
sales order items that are aggregated. In the exception aggregation
step, amounts DistinctTotalNetAmount are summed to the required
result. As the rows were grouped by orders, each order total net
value is summed at most once.
If you group the rows by the Sales Order dimension in a
multidimensional analysis, you can reproduce the exception
aggregation (see Figure 10.31).
Figure 10.31
Steps of the Exception Aggregation
Because exception aggregation dimension SalesOrder (of annotation
clause exceptionAggregationElements) is requested as a standard
column, it becomes part of the grouping, and the exception
aggregation step for the regular result rows (light gray rows)
becomes superfluous and is omitted. The Total Order Net Value
column shows the total net value of the sales order. For the lines
with totals and subtotals, however, the exception aggregation step is
executed. This helps you find errors in the definition of an exception
aggregation by checking the subtotal and total values.
If measures are aggregated, not only counted, the syntax of a
transient projection view requires the specification of a currency or
unit of the result (see Listing 10.25).
@EndUserText.label: 'Gesamter Auftragsnettowert'
@Semantics.amount.currencyCode: 'TransactionCurrency'
@Aggregation.default: #FORMULA
@AnalyticsDetails: {
exceptionAggregationSteps: [{
exceptionAggregationBehavior: #SUM,
exceptionAggregationElements: [ 'SalesOrder' ]
}]
}
$projection.totalnetamount as DistinctTotalNetAmount
Listing 10.25
Exception Aggregation of Measures in a Transient Projection View
The calculation for the resulting measure of the exception
aggregation is defined directly in CDS instead of an annotation. Yet
no fields from the data source, the cube view, can be used in the
calculation—only fields that are defined in the query.
10.3.7
Currencies and Conversion
In the previous examples, you’ve only seen amounts in one
currency, that is, euros. Of course, amounts in other currencies are
also possible, which brings with it some additional aspects.
Figure 10.32 shows the result of query ZB_SalesOrderItemQuery05
from Section 10.3.4, if amounts in multiple currencies exist. Note that
this screen is drilled down by Document Currency to show the
different currencies in detail.
Figure 10.32
Drilldown and Aggregation for Multiple Currencies
As you can see, the calculations take the currency into account and
display the calculated fields accordingly, including the field showing
the average amount per unit. The line with the Overall Result shows
a special feature: amounts with different currencies can’t simply be
aggregated into a single value. Therefore, an asterisk (*) is displayed
instead of the sum or aggregate value. If you like to see comparable,
effective totals, the amounts have to be converted into a common
currency for display.
For that purpose, we need two new parameters in our analytical
query, one for the display currency and the other for the type of
exchange rate to use. The currency conversion is executed by CDS
function currency_conversion. Listing 10.26 shows a suitable query
with the same output fields as query ZB_SalesOrderItemQuery05.
@AbapCatalog.sqlViewName: 'ZB_SOIQ09'
@EndUserText.label: 'Query 09 for Sales Order Items'
@Analytics.query: true
define view ZB_SalesOrderItemQuery09
with parameters
@Consumption.defaultValue: 'M'
P_ExchangeRateType : kurst,
@Consumption.defaultValue: 'EUR'
P_DisplayCurrency : vdm_v_display_currency
as select from ZB_SalesOrderItemCube05
{
@AnalyticsDetails.query.axis: #ROWS
@AnalyticsDetails.query.totals: #SHOW
@AnalyticsDetails.query.display: #KEY_TEXT
Material,
_SalesOrganization._Text.SalesOrganizationName,
@AnalyticsDetails.query.totals: #SHOW
SoldToParty,
@AnalyticsDetails.query.sortDirection: #ASC
_SoldToParty.CustomerName,
OrderQuantity,
OrderQuantityUnit,
@Semantics.currencyCode: true
cast( :P_DisplayCurrency as vdm_v_display_currency )
as DisplayCurrency,
@EndUserText.label: 'Net Value in Display Currency'
@Aggregation.default: #FORMULA
@Semantics.amount.currencyCode: 'DisplayCurrency'
currency_conversion (
amount
=> NetAmount,
source_currency
=> TransactionCurrency,
target_currency
=> :P_DisplayCurrency,
exchange_rate_date => CreationDate,
exchange_rate_type => :P_ExchangeRateType
) as NetAmountInDisplayCurrency,
@EndUserText.label: 'Minimal Amount per Unit'
@Aggregation.default: #FORMULA
@Semantics.amount.currencyCode: 'DisplayCurrency'
currency_conversion (
amount
=> AmountPerUnitMin,
source_currency
=> TransactionCurrency,
target_currency
=> :P_DisplayCurrency,
exchange_rate_date => CreationDate,
exchange_rate_type => :P_ExchangeRateType
) as AmountPerUnitMinInDC,
@EndUserText.label: 'Maximal Amount per Unit'
@Aggregation.default: #FORMULA
@Semantics.amount.currencyCode: 'DisplayCurrency'
currency_conversion (
amount
=> AmountPerUnitMax,
source_currency
=> TransactionCurrency,
target_currency
=> :P_DisplayCurrency,
exchange_rate_date => CreationDate,
exchange_rate_type => :P_ExchangeRateType
) as AmountPerUnitMaxInDC,
@EndUserText.label: 'Average Amount per Unit'
@AnalyticsDetails.query.formula:
'$projection.NetAmountInDisplayCurrency / OrderQuantity'
0 as AverageAmountPerUnit
}
Listing 10.26
Query with Currency Conversion
After starting the query, you can directly accept or adjust the
suggested default values (see Figure 10.33).
Figure 10.33
Enter the Query Parameters
Afterward, you get the query result in the chosen display currency
(see Figure 10.34).
Figure 10.34
Analysis with Converted Currencies
The switch from an ABAP Data Dictionary-based analytical query
with currency conversion and subsequent average calculation to a
transient projection view requires several syntax adjustments.
Listing 10.27 shows the slightly simplified result.
@EndUserText.label: 'Query 09 für Sales Order Items'
@AccessControl.authorizationCheck: #NOT_ALLOWED
define transient view entity ZB_SalesOrderItemQuery09A
provider contract analytical_query
with parameters
@Consumption.defaultValue: 'M'
P_ExchangeRateType : kurst,
@Consumption.defaultValue: 'EUR'
P_DisplayCurrency : vdm_v_display_currency
as projection on ZB_SalesOrderItemCube05
{
@AnalyticsDetails.query.axis: #ROWS
@AnalyticsDetails.query.totals: #SHOW
@UI.textArrangement: #TEXT_LAST
Material,
_SalesOrganization._Text.SalesOrganizationName : localized,
@AnalyticsDetails.query.totals: #SHOW
SoldToParty,
@AnalyticsDetails.query.sortDirection: #ASC
_SoldToParty.CustomerName,
OrderQuantity,
OrderQuantityUnit,
cast($parameters.P_DisplayCurrency as vdm_v_display_currency) as
DisplayCurrency,
@EndUserText.label: 'Net Amount in Display Currency'
@Aggregation.default: #FORMULA
@Semantics.amount.currencyCode: 'DisplayCurrency'
currency_conversion (
amount
=> curr_to_decfloat_amount( NetAmount ),
source_currency
=> TransactionCurrency,
target_currency
=> $parameters.P_DisplayCurrency,
exchange_rate_date => CreationDate,
exchange_rate_type => $parameters.P_ExchangeRateType
) as NetAmountInDisplayCurrency,
@EndUserText.label: 'Average Amount per Unit'
@Aggregation.default: #FORMULA
@Semantics.quantity.unitOfMeasure: 'CurrencyPerUnit'
$projection.NetAmountInDisplayCurrency / OrderQuantity
as AverageAmountPerUnit,
@EndUserText.label: 'Currency per Unit'
virtual CurrencyPerUnit : dd_cds_calculated_unit
}
Listing 10.27
Calculation
Transient Projection View with Currency Conversion and Average
Note that the division with operator “/” used for calculating the
average expects arguments of data type DECFLOAT34, but function
curr_to_decfloat_amount can’t be applied to calculated fields, that is,
those with $projection. Therefore, the data type of the original
amount is first changed, then the currency conversion is executed,
and finally the average is calculated.
As an alternative to the procedure described here, you can
implement a currency conversion already in the analytical cube used
in the query and pass the parameters of the query to the parameters
of the cube. Typically, however, a query with currency conversion
runs faster than a query that uses a cube with currency conversion.
The faster execution is achieved by first aggregating the rows of the
data source with the same currency and the same conversion date,
and then performing currency conversion for the aggregate. As a
result, the relatively expensive conversion function must be applied
to fewer pre-aggregated data rows.
10.3.8
Views
Analytical Query Selecting from Dimension
Analytical query views can be defined not only on cube views but
also on dimension views. When doing so, you can apply the
methods for designing the query presented in the previous sections
as well. Listing 10.28 shows a query on customer data as an
example.
@AbapCatalog.sqlViewName: 'ZB_CUSTQ01'
@EndUserText.label: 'Query 01 for Customer Data'
@Analytics.query: true
define view ZB_CustomerQuery01
as select from I_Customer
{
Customer,
Country,
@AnalyticsDetails.query.axis: #ROWS
CityName,
@EndUserText.label: 'Number of Customers'
@Aggregation.default: #SUM
1 as NumberOfCustomers
}
Listing 10.28
Query Selecting from a Dimension View
A special feature of query selecting from dimension views is the
definition of a counter by a calculated field with value 1 and
aggregation behavior #SUM. This is possible because field Customer is
the key of the dimension view. An exception aggregation isn’t
necessary in this case; this can improve the runtime of the query.
Note that in older releases, it might be necessary to use the
deprecated form of aggregation annotation @DefaultAggregation:
#SUM. The output of the query is shown in Figure 10.35.
Figure 10.35
Query on a Dimension View with Counter
In a transient projection view, a more precise syntax is mandatory.
When specifying the literal 1 for a counter, its data type must be
explicitly given, for example, as abap.int4'1'.
[»] Queries on Cube Views or Dimension Views
Analytical queries are mostly defined on cube views. Cubes often
combine different entities, contain many attributes from multiple
sources, and enable varied analyses. If an attribute is missing, you
can copy the cube or define a new cube on top of it, add the
missing attribute, and define your query on the new one. Cubes
offer various options.
Dimension views instead represent a single entity and have
attributes that are directly related to this entity from a business
perspective. Indirectly related attributes, for example, the country
symbol of a customer’s country, aren’t included in the dimension
view. Copying a dimension view or defining a new one for the
same entity isn’t straightforward, as dimension views are
interconnected with others by foreign key associations.
Cube views, therefore, are the preferred data source for analytical
query views.
10.4
Analytical Infrastructure
In the previous section, you’ve seen that analytical views and
queries offer many additional options and functions that aren’t
available when executing a plain CDS view, for example, in the
Eclipse data preview. These additional capabilities are realized by an
analytical infrastructure.
The definition of analytical views and queries is supposed to be
independent from the concrete analytical infrastructure and its
properties. This allows the usage of analytical CDS views on
platforms with different technology. In the previous sections, we
could have introduced CDS-based analytics without detailing the
infrastructure; only a test environment was necessary.
Now, let’s introduce the analytical infrastructure of SAP S/4HANA.
Figure 10.36 shows an overview.
The central analytical component is the analytic processor in the
ABAP server, also known as the OLAP processor (online analytic
processing) or analytic engine. This is also used in the classical SAP
Business Warehouse (SAP BW) solution and is now leveraged for
CDS-based SAP S/4HANA embedded analytics.
Figure 10.36
Analytical Infrastructure of SAP S/4HANA
The analytic processor uses the ABAP application infrastructure and
CDS infrastructure for two purposes:
Read metadata of CDS views, including annotations to build its
analytical model.
Select data via ABAP SQL.
The test environment you’ve used for the examples is connected to
the analytic processor in ABAP.
Starting with SAP S/4HANA release 1909, the analytic processor
uses an additional direct channel for selecting data from SAP HANA,
which is also used in SAP BW/4HANA. This channel offers
performance advantages compared to data selection via CDS for
complex calculations in analytical queries or for exception
aggregation.
Analytical apps or other analytical UIs can be connected to the
analytic processor via two analytical interfaces:
The proprietary Information Access (InA) protocol for
communication with SAP’s analytical components, including SAP
Analytics Cloud
The globally standardized Open Data Protocol (OData) as an
open communication interface, which is used by many analytical
SAP Fiori apps in SAP S/4HANA
Via these interfaces, the CDS-defined analytical views and queries
can be used by diverse analytics tools, for example:
SAP Analysis for Microsoft Office
SAP Lumira, designer edition, formerly known as SAP
BusinessObjects Design Studio
SAP Analytics Cloud
The analytical interface based on OData exposes entities and their
properties with the same names as the analytical CDS views that
define the underlying analytical model and their fields.
This is different for the analytical interface based on the InA protocol.
It uses the internal technical names of the analytic processor, the
InfoObject names. For CDS views, the InfoObject name can be
composed in several ways:
For an ABAP Data Dictionary-based CDS view, the InfoObject
name is created by concatenating prefix 2C with the name of the
SQL view belonging to the CDS view. Your first cube view
ZB_SalesOrderItemCube01 with SQL view ZB_SOIC01, for example,
has the InfoObject name 2CZB_SOIC01.
For CDS view entities or projection views, the name of the view
with prefix 2C is taken as the InfoObject name.
The InfoObject name can also be defined by an annotation, which
overrides the other options. The annotation
@Analytics.technicalName specifies a technical name that,
together with the prefix 2C, forms the InfoObject name.
Besides the analytical views, their fields also have InfoObject names
by which they are exposed in the InA protocol. Some examples were
shown previously in the test environment of Figure 10.16. The
InfoObject name of a measure field is composed of the InfoObject
name of the analytical view and its field name. If the concatenated
string is short enough, up to 30 characters, the string is taken as the
InfoObject name; otherwise, a hash value of that string is used after
the 2C prefix. This approach for naming an InfoObject name is called
local naming as the context of the view isn’t considered.
The InfoObject name of a dimension field, in contrast, is the
InfoObject name of the analytical dimension view to which the
foreign key association of the field points, by default. If this rule
would lead to duplicate names, only the first InfoObject name is
taken from the foreign key association’s target, and the other ones
are determined locally like measure names. It’s possible to enforce
the local naming logic also for dimension fields via annotation
@Analytics.internalName : #LOCAL, either by annotating a field or the
complete analytical cube or dimension view.
Analytics tools such as SAP Analytics Cloud often build their own
configurations on top of the analytical queries or model exposed by
the analytic processor. If they use the InA channel as an interface,
they rely on the InfoObject names and their stability.
[+] Enforce Local InfoObject Names
We recommend that you enforce local InfoObject names by
annotating your analytical cube or dimension views with
@Analytics.internalName : #LOCAL. Then, it’s possible to change
the target of a foreign key association or rearrange fields in an
analytical cube or dimension without affecting the InfoObject
names and consequently the stability of the analytical interface
InA.
The analytic processor applies several methods to optimize the data
access of a request. It usually retrieves the aggregated measures in
the requested grouping by a SQL SELECT to SAP HANA or in an
optimized form for complex calculations. This minimizes the amount
of data transferred from the database to the application servers.
Then, the required texts and other attributes from dimension views
are added for display. Application server buffers are used to
minimize database access in this step.
[+] Reasonable Selection of Data
Try to avoid the selection of big, only slightly aggregated data
volumes as transport from the database to the application server
and processing in ABAP can take some time. Define appropriate
variables in your analytical queries to filter data in a reasonable
way.
10.5
Summary
In this chapter, you learned how to define and execute analytical
models in CDS. The main steps were the identification of measure
and dimension fields, of cube and dimension views, and their
connection by foreign key associations. Based on this model, we
defined analytical queries and their layout, and we applied advanced
methods for calculating measures by formulas and exception
aggregation. We executed several examples in the test environment.
At the end, we gave a short overview of the analytical infrastructure.
The next chapter will cover CDS models for transactional
applications.
11 Modeling Transactional
Applications
This chapter explains how to use core data services (CDS)
data models for developing transactional applications, that is,
applications that not only read data but also change it based
on the ABAP RESTful application programming model.
In addition to modeling and executing data read accesses, CDS also
provides the basis for modeling transactional aspects, thereby
defining further artifacts and the behavior of applications that change
data. In general, data can be created, changed, or deleted by direct
user input via a user interface (UI) or machine interfaces, for
example, in application-to-application (A2A) communication. Along
with standard create, read, update, delete (CRUD) operations,
actions and functions also can be defined and exposed. Changes
explicitly requested by the consumer of one of these interfaces can
result in further implicit changes referred to as side effects in the
context of this chapter.
In this chapter, you’ll learn how to define a transactional application.
We’ll cover what additional features need to be considered for
transactional applications in Section 11.1. In Section 11.2, you’ll
learn about the transactional infrastructure in SAP S/4HANA, which
is based on the ABAP RESTful application programming model, the
programming model of ABAP Cloud.
In Section 11.3 and Section 11.4, we describe how you can define
transactional object models and the related behavior definition with
its static feature set and how you can implement the related
business logic. The business logic includes the implementation of
drawing numbers; setting enqueue locks, authorization checks, data
derivations, and data checks, actions, and functions; and the
dynamic control of operations and properties as well es events. The
draft concept is also introduced and described in detail.
You’ll then use this sample application in various consumptionspecific scenarios using transactional projection models
(Section 11.5), interface behavior definitions (Section 11.6), and
projection behavior definitions (Section 11.7). In Section 11.8, we
give an overview of the runtime orchestration.
Finally, we explain the concrete usage of our application and
implementation based on an example of a Web API and an SAP
Fiori elements application with draft support in Section 11.9 as well
as the usage of the defined events within the local system or via
SAP Event Mesh in Section 11.10.
11.1
Transactional Applications
In the previous chapters, we’ve shown how CDS can be used to
model various query and read accesses based on the capabilities of
SQL. The question that arises is whether and how this semantically
rich model can now also be used for supporting modifying accesses.
Especially if you use these models via REST-based interfaces on the
web, it becomes obvious that read and write operations should
ideally be based on the same model and resources. In this section,
we discuss the basics and specific aspects of transactional
applications.
Business objects serve as the logical union of several entities. As an
example of a business object, you’ve already learned about the
sales order, which, in our simplified example, consists of header
data, item data, and schedule lines. These have a compositional
relationship among themselves and are therefore existentially
dependent on the respective parent entity. While business objects
play a rather subordinate role in querying and reading, they form a
context that is very important in modifying access. Usually, several
entities of a single business object are processed and modified
together. Other referenced or associated entities are often only read
or have their own implementation for modifying accesses.
In addition to dedicated authorization controls, data modifications
require exclusive locks to ensure the transactional consistency of the
data. In most cases, both authorization control and locking behavior
are implemented at the business object level as a whole and not on
the individual child entity level of a business object. But there are
also some use cases where a child entity requires a more restrictive
authorization control than the root entity. In addition, some business
objects support locking on the child entity level.
When modifying data entered by a user or transmitted via a machine
interface, the data usually isn’t simply taken over and persisted.
Instead, the data entered is checked, other data is determined and
calculated that depends on changed data or the business
configuration, and so on. This is generally referred to in this book as
business logic. Not only is data of the directly addressed business
object changed, but, in many cases, other business objects or entire
business processes or subsequent processes are also triggered or
processed.
You can change data and business objects using elementary
operations such as create, change, or delete (supplemented by
reading, also known as CRUD operations). In REST-based
consumptions such as OData, these operations also correspond to
the related HTTP method on a resource, that is, HTTP GET, POST,
PUT/PATCH/MERGE, and DELETE. However, the business logic addressed
is also very often exposed in more comprehensive operations
containing several fine granular changes that ensure consistent
processing of operations. We call this type of operation in the
following actions. Actions help to ensure that the business logic or
consistency of multiple changes isn’t left to the end user or
consumer. Instead, they offer better convenience regarding recurring
changes. If required, actions can also offer explicit extended
authorization control.
Functions are enhancements supported by the ABAP RESTful
application programming model and OData compared to pure SQL
query and read access. By definition, functions aren’t related to
modifying access and don’t modify data in the system. These also
can be provided and are part of the business object model and
business logic. In addition, functions could require further dedicated
authorizations that are different from pure read authorization.
There are some additional challenges in the context of ABAP CDS
and the features and functions it provides. First, ABAP CDS currently
only offers data definitions such as CDS views and CDS table
functions for data modeling. These entities only support defining
query and read accesses. Thus, from a purely technical point of
view, the corresponding SQL operations for changing data must take
place directly via database tables or on the underlying database
tables. The infrastructure can only delegate modifying accesses to
the CDS models automatically if the CDS models represent a simple
projection of these database tables. When using more complex SQL
logic within the CDS models, tasks such as view building, writing, or
finding what to write to which database table are no longer easy or
are sometimes not possible at all. In fact, in the SAP S/4HANA
virtual data model (VDM), which is exposed as a semantically rich
model, the data model is fundamentally simplified in many places
regarding the existing database tables and persistency model so that
unions, joins, and other SQL functions are also used to define the
basic VDM CDS models.
11.2 Transactional Infrastructure in SAP
S/4HANA
At this point, you may wonder at what level the business logic should
be implemented and integrated. In the ABAP environment, the
answer is relatively clear that the business logic (as we understand it
here) runs on the ABAP application server and is implemented in
ABAP. Reasons for this include the scalability of the application
servers and the extensive existing business logic in SAP S/4HANA,
which should of course be accessible and reused. This approach
isn’t contradictory to the goal of shifting the data-intensive logic to
the database. Optimally, SAP HANA is used, of course, for all read
operations, and transformations are mapped as much as possible
using SQL when reading data. This also applies especially to
authorization checks leveraging the CDS access control language to
avoid transferring data to the application server to just remove it
again. This pushdown approach can also be used in a reasonable
way in transactional logic.
[»] Leveraging the SAP HANA Database
Consider the following example: In sales order processing, before
the sales order can be confirmed, the credit limit of the customer
should be checked based on the current order and the open items.
The open items are calculated using aggregation (total of open
items) in the SAP HANA database. Only the result of the
calculation is transferred to the application server. In the
application server in the corresponding check, this value is added
to the current order value and compared with the customer’s credit
limit. If this limit is exceeded, a corresponding message is
generated, and, if necessary, a corresponding status is set in the
document.
The ABAP platform enables the use of CDS models as the basis for
modeling transactional applications. In this context, all the
requirements defined in the previous section for transactional
applications or for a corresponding infrastructure must be
considered. For this purpose, the ABAP RESTful application
programming model has been introduced as the logical successor of
the ABAP programming model for SAP Fiori.
The ABAP RESTful application programming model is the common
foundation that the programming model of SAP S/4HANA is based
on. It fosters the application architecture to clearly separate the
database model and database layer, the application logic (business
logic), and the UI. While the separation of the UI (and related user
experience [UX]) is technically ensured by following the SAP Fiori
technology approach leveraging OData as the protocol of choice, the
ABAP RESTful application programming model allows you to further
clearly separate the model and implementation of the different layers
—the common general base model and application logic (business
logic) on one side, and the service-specific model and application
logic on the other side—while leaving the protocol implementation to
the infrastructure. Thus, the ABAP RESTful application programming
model ensures that the complete application implementation is
protocol agnostic and can also be reused when switching to a
different protocol or protocol version.
As for CDS entities, all the ABAP RESTful application programming
model–specific artifacts needed for implementing transactional
applications can be created and edited via ABAP Development Tools
(ADT; aka ABAP in Eclipse).
The ABAP RESTful application programming model supports both
greenfield and brownfield implementations, which is a major
enhancement compared to the ABAP programming model for SAP
Fiori. The consumption of the ABAP RESTful application
programming model implementation is completely integrated into the
ABAP language, and the type of implementation is hidden from the
consumers of the application, whether it’s within ABAP or via
different channels such as OData.
We’ll focus on consuming the application via REST and OData as a
web standard because the ABAP platform already offers end-to-end
support for these standards. The infrastructure takes over the
complete protocol handling and automatically forwards all
operations, that is, read and write requests, to the database or the
provider implementation. All non-transactional read and analytical
requests are delegated directly to the SAP HANA database via the
ABAP SQL interface by leveraging the related CDS data model.
Transactional requests for reading or writing data are passed to the
transactional runtime and the transactional buffer in the application
server. This combines efficient processing of all persistent data by
the SAP HANA database with the advantages of the transactional
runtime being executed on the ABAP application server and the
possible reuse of the existing business logic.
The Service Adaptation Definition Language (SADL) of the ABAP
platform is used to translate OData GET requests to SQL queries.
SADL is an infrastructure for model-based reading and processing of
data.
[»] Additional Resources
For more information about SADL, visit the SAP Help Portal, and
search for “Consuming Business Entities with SADL” (http://s-
prs.co/v529412).
For a schematic representation of the ABAP infrastructure for
transactional applications with the most important runtime
components, see Figure 11.1.
Figure 11.1
ABAP Infrastructure: Transactional Applications
11.3
Transactional Object Models
This section introduces the transactional object models. You’ll learn
how to define and implement a business object and its business
logic. First, you’ll define the data model and introduce an object
structure. In the transactional world, we consequently make use of
the newer CDS entity types (aka CDS V2 models). You’ll then
enhance the business object defined in this way to support
transactional changes.
11.3.1
Object Models
As already mentioned, business objects are defined as tree or
compositional structures of entities. To describe business objects in
the network of CDS entities and associations, the root of the
composition tree is defined as such, and the relevant associations
are defined as compositions. You’ve already learned this modeling in
Chapter 3, Section 3.3.
[+] Transactional View Layer
We recommend that you define dedicated CDS views for your
transactional application that are based on your basic interface
views. This makes the model and its use clearer while leaving the
model open for further developments and new features.
We further recommend that you add the suffix »TP« to the
underlying CDS view names as a naming convention for this kind
of CDS view.
In spoken language, no distinction is usually made between the
business object (in our example, the sales order) and its root entity
(in our example, the sales order header), and the name of the
business object is used synonymously for both of them. In the
following example, we retain the correct definitions and terms to
avoid conceptual confusion, making it clear when the business
object or the root entity of the business object is being referred to.
Let’s define the model of the business object for the sales order.
Because you base the transactional CDS views on the basic
interface views already defined, known and available annotations of
the underlying fields, such as their semantics, are automatically
available using the annotation propagation logic. In addition, the field
names already have a semantic and readable alias.
[»] Inheriting Annotations
In part of our example, we still add some annotations to the CDS
model that may also be inherited to increase the readability and
understandability of the examples.
Define the sales order header as shown in Listing 11.1. It already
contains a child association to the sales order item, which is defined
in Listing 11.2.
@AccessControl.authorizationCheck: #MANDATORY
@EndUserText.label: 'Sales Order'
define root view entity ZR_SalesOrderTP
as select from ZI_SalesOrder
composition [0..*] of ZR_SalesOrderItemTP as _Item
association [0..1] to ZI_Customer as _SoldToParty
on $projection.SoldToParty = _SoldToParty.Customer
association [1]
to I_User as _CreatedByUser
on $projection.CreatedByUser = _CreatedByUser.UserID
association [1]
to I_User as _LastChangedByUser
on $projection.LastChangedByUser = _LastChangedByUser.UserID
{
key ZI_SalesOrder.SalesOrder,
ZI_SalesOrder.SalesOrderType,
ZI_SalesOrder.SalesOrganization,
ZI_SalesOrder.SoldToParty,
ZI_SalesOrder.DistributionChannel,
ZI_SalesOrder.OrganizationDivision,
@Semantics.amount.currencyCode: 'TransactionCurrency'
ZI_SalesOrder.NetAmount,
ZI_SalesOrder.TransactionCurrency,
ZI_SalesOrder.DeliveryStatus,
@Semantics.booleanIndicator: true
ZI_SalesOrder.DeletionIndicator,
@Semantics.user.createdBy: true
ZI_SalesOrder.CreatedByUser,
@Semantics.systemDateTime.createdAt: true
ZI_SalesOrder.CreationDateTime,
@Semantics.user.lastChangedBy: true
ZI_SalesOrder.LastChangedByUser,
@Semantics.systemDateTime.lastChangedAt: true
ZI_SalesOrder.LastChangeDateTime,
_Item,
_SoldToParty,
_CreatedByUser,
_LastChangedByUser
}
Listing 11.1
CDS View Entity: Sales Order Header
Listing 11.2 shows the sales order item that has associations to both
the sales order header and the schedule lines defined in Listing 11.3.
@AccessControl.authorizationCheck: #MANDATORY
@EndUserText.label: 'Sales Order Item'
define view entity ZR_SalesOrderItemTP
as select from ZI_SalesOrderItem
association
to parent ZR_SalesOrderTP as _SalesOrder
on $projection.SalesOrder = _SalesOrder.SalesOrder
composition [0..*] of ZR_SalesOrderScheduleLineTP
as _ScheduleLine
association [0..1] to ZI_Product as _Product
on $projection.Product = _Product.Product
association [1]
to I_User as _CreatedByUser
on $projection.CreatedByUser = _CreatedByUser.UserID
association [1]
to I_User as _LastChangedByUser
on $projection.LastChangedByUser = _LastChangedByUser.UserID
{
@ObjectModel.foreignKey.association: '_SalesOrder'
key ZI_SalesOrderItem.SalesOrder,
key ZI_SalesOrderItem.SalesOrderItem,
ZI_SalesOrderItem.Product,
@Semantics.quantity.unitOfMeasure: 'OrderQuantityUnit'
ZI_SalesOrderItem.OrderQuantity,
ZI_SalesOrderItem.OrderQuantityUnit,
@Semantics.amount.currencyCode: 'TransactionCurrency'
ZI_SalesOrderItem.NetAmount,
ZI_SalesOrderItem.TransactionCurrency,
@Semantics.user.createdBy: true
ZI_SalesOrderItem.CreatedByUser,
@Semantics.systemDateTime.createdAt: true
ZI_SalesOrderItem.CreationDateTime,
@Semantics.user.lastChangedBy: true
ZI_SalesOrderItem.LastChangedByUser,
@Semantics.systemDateTime.lastChangedAt: true
ZI_SalesOrderItem.LastChangeDateTime,
_SalesOrder,
_ScheduleLine,
_Product,
_CreatedByUser,
_LastChangedByUser
}
Listing 11.2
CDS View Entity: Sales Order Item
Listing 11.3 shows the sales order schedule line that has
associations to the sales order header as root entity and to the sales
order items as parent entity.
@AccessControl.authorizationCheck: #MANDATORY
@EndUserText.label: 'Sales Order Schedule Line'
define view entity ZR_SalesOrderScheduleLineTP
as select from ZI_SalesOrderScheduleLine
association [1..1] to ZR_SalesOrderTP as _SalesOrder
on $projection.SalesOrder = _SalesOrder.SalesOrder
association
to parent ZR_SalesOrderItemTP
as _SalesOrderItem
on $projection.SalesOrder
= _SalesOrderItem.SalesOrder and
$projection.SalesOrderItem = _SalesOrderItem.SalesOrderItem
association [1]
to I_User as _CreatedByUser
on $projection.CreatedByUser = _CreatedByUser.UserID
association [1]
to I_User as _LastChangedByUser
on $projection.LastChangedByUser = _LastChangedByUser.UserID
{
key ZI_SalesOrderScheduleLine.SalesOrder,
key ZI_SalesOrderScheduleLine.SalesOrderItem,
key ZI_SalesOrderScheduleLine.SalesOrderScheduleLine,
ZI_SalesOrderScheduleLine.DeliveryDate,
@Semantics.quantity.unitOfMeasure: 'OrderQuantityUnit'
ZI_SalesOrderScheduleLine.OrderQuantity,
ZI_SalesOrderScheduleLine.OrderQuantityUnit,
ZI_SalesOrderScheduleLine.SalesOrderScheduleLineType,
@Semantics.user.createdBy: true
ZI_SalesOrderScheduleLine.CreatedByUser,
@Semantics.systemDateTime.createdAt: true
ZI_SalesOrderScheduleLine.CreationDateTime,
@Semantics.user.lastChangedBy: true
ZI_SalesOrderScheduleLine.LastChangedByUser,
@Semantics.systemDateTime.lastChangedAt: true
ZI_SalesOrderScheduleLine.LastChangeDateTime,
_SalesOrder,
_SalesOrderItem,
_CreatedByUser,
_LastChangedByUser
}
Listing 11.3
CDS View Entity: Sales Order Schedule Line
[+] CDS View Activation
To avoid issues with CDS view activation, we recommend that you
first define and activate the CDS views without associations and
add the associations in the second step. The definition of the
associations to parent entities needs to be done up front to the
definition of the related compositions.
11.3.2
Access Controls
Read access is controlled via the well-known access control (see
Chapter 5). As we define the transactional layer on top of our basic
layer, we can simply inherit the access control from the underlying
CDS entities. Listing 11.4 shows the inherited access control of the
sales order header.
@EndUserText.label: 'Sales Order'
@MappingRole: true
define role ZR_SalesOrderTP {
grant
select
on
ZR_SalesOrderTP
where
inheriting conditions from entity
ZI_SalesOrder;
}
Listing 11.4
Access Control: Sales Order Header
The access control for the sales order item is shown in Listing 11.5.
@EndUserText.label: 'Sales Order Item'
@MappingRole: true
define role ZR_SalesOrderItemTP {
grant
select
on
ZR_SalesOrderItemTP
where
inheriting conditions from entity
ZI_SalesOrderItem;
}
Listing 11.5
Access Control: Sales Order Item
The access control of the sales order schedule line is shown in
Listing 11.6.
@EndUserText.label: 'Sales Order Schedule Line'
@MappingRole: true
define role ZR_SalesOrderScheduleLineTP {
grant
select
on
ZR_SalesOrderScheduleLineTP
where
inheriting conditions from entity
ZI_SalesOrderScheduleLine;
}
Listing 11.6
Access Control: Sales Order Schedule Line
11.4
Behavior Definitions
In this section, you’ll learn how to add transactional behavior to your
business object. First, you’ll define the provided operations and
features. Then, you’ll complete your application with advanced
features such as numbering, exclusive locks, authorization control,
business logic, actions, and functions. Gradually, a complete
application will be created by implementing the necessary handlers.
11.4.1
Create Behavior Definition
To create a behavior definition, right-click on the CDS root view
entity, and choose New Behavior Definition. The Name can’t be
changed as it’s identical to the CDS root view entity; you can change
the Description and choose the implementation type.
Implementation type Managed is for greenfield applications that
implement or integrate their business logic natively into the ABAP
RESTful application programming model. When choosing this
implementation type, by default, the complete CRUD operations and
the buffer handling are taken over by the ABAP RESTful application
programming model infrastructure.
Implementation type Unmanaged is for brownfield applications that
want to integrate their existing business logic into the ABAP RESTful
application programming model. CRUD operations and buffer
handling are implemented by the application.
We’ll discuss both options in the following sections.
MANAGED Implementation Type
Keep the Implementation Type set to Managed for the first
example, click Next, and then click Finish (see Figure 11.2).
Figure 11.2
ADT: Create Behavior Definition
While creating the behavior definition, a template source is already
provided based on the related CDS entities of the compositional
hierarchy. The source code of the behavior definition is shown in
Listing 11.7. It already contains certain syntax elements to ensure
the current state is free of syntax errors and can be activated. We’ll
explain the syntax elements in the next sections.
managed implementation in class zbp_r_salesordertp unique;
strict ( 2 );
define behavior for ZR_SalesOrderTP alias SalesOrder
persistent table zsalesorder
lock master
authorization master ( instance )
//etag master <field_name>
{
create;
update;
delete;
field ( readonly : update ) SalesOrder;
association _Item { create; }
}
define behavior for ZR_SalesOrderItemTP alias SalesOrderItem
persistent table zsalesorderitem
lock dependent by _SalesOrder
authorization dependent by _SalesOrder
//etag master <field_name>
{
update;
delete;
field ( readonly ) SalesOrder;
field ( readonly : update ) SalesOrderItem;
association _SalesOrder;
association _ScheduleLine { create; }
}
define behavior for ZR_SalesOrderScheduleLineTP alias SalesOrderScheduleLine
persistent table zsalesordersline
lock dependent by _SalesOrder
authorization dependent by _SalesOrder
//etag master <field_name>
{
update;
delete;
field ( readonly ) SalesOrder;
field ( readonly ) SalesOrderItem;
field ( readonly : update ) SalesOrderScheduleLine;
association _SalesOrder;
association _SalesOrderItem;
}
Listing 11.7
Behavior Definition: Sales Order
The first line defines the provider type (in our case, MANAGED) and a
proposed class name for the implementation. The addition unique is
mandatory in strict mode and ensures that each operation is only
implemented once. We’ll dig more into the implementation class in
Section 11.4.2.
[+] Strict Mode
The behavior definition should always be defined with the key
word strict(2), or rather the highest available strict mode, to
ensure that the most recent syntax is used and that deprecated
syntax features and functions aren’t available.
The key word DEFINE BEHAVIOR indicates that for this entity, a
behavior model and implementation is defined. Here, for each entity,
you should give an ALIAS that is then also present in the runtime
structures to make your code more readable. In addition, element
PERSISTENT TABLE names the related database table of the entity. This
information is needed for a MANAGED implementation in which the
buffering of transactional changes as well as all database operations
are taken over by the infrastructure by default.
Sometimes, you’re not starting completely greenfield but still want to
leverage the full infrastructure functionality regarding buffer handling
and application implementation while keeping your existing database
tables, or you may even run the new implementation side by side to
the existing one until feature parity is reached. The ABAP RESTful
application programming model also offers out-of-the-box features
for such an approach (sometimes called the bluefield approach). By
default, the defined persistent table needs to have the same
schema, that is, the same field names, as the CDS entity. If the entity
itself still follows the database table but just with different aliases, the
full infrastructure functionality can be used, and the aliasing can also
be done in the write accesses to the database by defining a related
mapping in the behavior definition. Such a mapping (not required in
our use case) could look like Listing 11.8.
mapping for zsalesorder corresponding
{
SalesOrder = salesorder;
}
Listing 11.8
Behavior Definition: Mapping
Here, the optional addition corresponding allows all fields with the
same name to be moved and allows a further exception list of fields
via syntax element except. We recommend defining an explicit
mapping in any case and adding corresponding to any defined
mapping to ensure future enhancements of the entities work out of
the box.
If the database schema looks completely different, for example,
because the new model combines multiple database tables into one
CDS entity (via JOIN or UNION relations), or you don’t even have
relational or CDS-supported storage, the database updates can also
be implemented completely by the application. This is done by
adding the WITH UNMANAGED SAVE syntax to the entity definition instead
of the PERSISTENT TABLE. By doing so, you’ll get a saver handler into
your implementation class where all changes are handed over during
saving, allowing you to execute your own direct database updates or
call your update task function module. Note that even in these cases,
you can leverage the mapping definitions declared in the behavior
definition in your own coding because the mapping is also supported
natively in the ABAP CORRESPONDING operator.
Finally, there are also use cases where you’ll want to write additional
data to the database. This can also be achieved with the
implementation type MANAGED. A good example for such a use case is
writing change documents. To achieve this, you can enhance the
framework logic with your own implementation part instead of taking
over the complete save implementation. This is achieved by adding
WITH ADDITIONAL SAVE to the behavior entity definition. In this case,
you’ll get the same handler method. Your handler implementation will
run additionally after the standard saver handler.
Further syntax elements LOCK MASTER and LOCK DEPENDENT are
described in Section 11.4.6, AUTHORIZATION MASTER and AUTHORIZATION
DEPENDENT are described in Section 11.4.7, and FIELD is described in
Section 11.4.4.
Inside the behavior clause, you can now define the supported and
implemented operations and transactional-enabled associations. By
default, only our compositions and to-parent associations are listed
in the behavior definition. These are needed in any case and need to
be transactional enabled. Further we’ve enabled all standard
operations, that is, CREATE, UPDATE, and DELETE for each entity. Note
that a direct CREATE operation is only possible on the root entity, and
the creation of instances for child entities is only possible via a
CREATE BY ASSOCIATION operation. If an operation isn’t allowed and
implemented at all, then it won’t be declared. If it can be executed by
your own implementation only (and not by external consumers), it
will be declared as INTERNAL. In our example, we’ve defined the
DELETE operation on the sales order header level accordingly.
A syntax warning indicates that the proposed implementation class
isn’t yet available, but a quick fix is offered, as you can see in
Figure 11.3, to not only create the class but also to add all to-beimplemented handler classes and methods after processing the wellknown class creation and transport request dialog. For more
information on using the quick fix, refer to http://s-prs.co/v564203.
Figure 11.3
ADT: Create Behavior Implementation Class via Quick Fix
Because we’ve chosen the MANAGED implementation scenario,
currently there is no handler yet to be implemented as the complete
handling of the CRUD operations is managed by the infrastructure.
You can ignore the handler for global authorization for now as we’ll
look into that later in Section 11.4.7; same for the saver handler if
you added statement WITH UNMANAGED SAVE or WITH ADDITIONAL SAVE for
one of the entities. So, if you like you can jump to Section 11.4.3 and
consume this implementation programmatically or to Section 11.7 to
define the projection layer, add some UI annotations, and run the
SAP Fiori elements preview (Section 11.9.2). In any case, you’ll find
a working application not only supporting read access to the
persisted data but also supporting full modifying access.
At this stage, you may enter or change data not intended to be
entered/changed by the end user; there is no (modify) authorization
checks in place and no application logic, for example, to at least
validate the entered data. This is what we’re going to look at now in
the further sections to make the application complete and explain the
concepts and syntax at the same time.
UNMANAGED Implementation Type
As most of the concepts and syntax elements apply both for
implementation types MANAGED and UNMANAGED, in this section, we’ll
just briefly introduce a second data model and behavior definition of
type UNMANAGED to explain the differences on the implementation side
and the further features coming up in the next sections of this
chapter.
[»] Implementation Type
The implementation type is an implementation detail; that is, it’s
hidden from the consumers of the behavior definition independent
of their access whether programmatically or via OData services for
Web APIs or UIs. Thus, the syntax for everything defining the
consumer API within the behavior definition, such as available
operations, is the same for either implementation type.
To keep it simple and comparable, just copy the three CDS entities
of the data model into a new data model with suffix »_2«, create a
behavior definition as described previously, and this time choose
implementation type Unmanaged. Don’t forget to also adjust the
association targets, and keep in mind that usually we would use
such a suffix only for new versions because only one implementation
should be developed for every application in most cases. We just
added an example for the sales order root in Listing 11.9.
@AccessControl.authorizationCheck: #MANDATORY
@EndUserText.label: 'Sales Order'
define root view entity ZR_SalesOrderTP_2
as select from ZI_SalesOrder
composition [0..*] of ZR_SalesOrderItemTP_2 as _Item
association [0..1] to ZI_Customer
as
_SoldToParty
on $projection.SoldToParty =
_SoldToParty.Customer
association [1]
to I_User
as
_CreatedByUser
on $projection.CreatedByUser =
_CreatedByUser.UserID
association [1]
to I_User
as
_LastChangedByUser on $projection.LastChangedByUser =
_LastChangedByUser.UserID
{
key ZI_SalesOrder.SalesOrder,
ZI_SalesOrder.SalesOrderType,
ZI_SalesOrder.SalesOrganization,
ZI_SalesOrder.SoldToParty,
ZI_SalesOrder.DistributionChannel,
ZI_SalesOrder.OrganizationDivision,
@Semantics.amount.currencyCode: 'TransactionCurrency'
ZI_SalesOrder.NetAmount,
ZI_SalesOrder.TransactionCurrency,
ZI_SalesOrder.DeliveryStatus,
@Semantics.booleanIndicator: true
ZI_SalesOrder.DeletionIndicator,
@Semantics.user.createdBy: true
ZI_SalesOrder.CreatedByUser,
@Semantics.systemDateTime.createdAt: true
ZI_SalesOrder.CreationDateTime,
@Semantics.user.lastChangedBy: true
ZI_SalesOrder.LastChangedByUser,
@Semantics.systemDateTime.lastChangedAt: true
ZI_SalesOrder.LastChangeDateTime,
_Item,
_SoldToParty,
_CreatedByUser,
_LastChangedByUser
}
Listing 11.9
CDS View Entity: Sales Order (UNMANAGED)
The resulting behavior definition of type UNMANAGED then only differs in
the implementation type key word and the absence of the syntax
element PERSISTENT TABLE that is exclusive to the MANAGED
implementation type (see Listing 11.10).
unmanaged implementation in class zbp_r_salesordertp_2 unique;
strict ( 2 );
define behavior for ZR_SalesOrderTP_2 alias SalesOrder
//late numbering
lock master
authorization master ( instance )
//etag master <field_name>
{
create;
update;
delete;
field ( readonly : update ) SalesOrder;
association _Item { create; }
}
define behavior for ZR_SalesOrderItemTP_2 alias SalesOrderItem
//late numbering
lock dependent by _SalesOrder
authorization dependent by _SalesOrder
//etag master <field_name>
{
update;
delete;
field ( readonly ) SalesOrder;
field ( readonly : update ) SalesOrderItem;
association _SalesOrder;
association _ScheduleLine { create; }
}
define behavior for ZR_SalesOrderScheduleLineTP_2 alias SalesOrderScheduleLine
//late numbering
lock dependent by _SalesOrder
authorization dependent by _SalesOrder
//etag master <field_name>
{
update;
delete;
field ( readonly ) SalesOrder;
field ( readonly ) SalesOrderItem;
field ( readonly : update ) SalesOrderScheduleLine;
association _SalesOrder;
association _SalesOrderItem;
}
Listing 11.10
Behavior Definition: Sales Order (UNMANAGED)
Although from a consumption point of view, now both behavior
definitions offer the same features and operations, on the
implementation side, it looks completely different. Now, creating the
implementation class for our new behavior definition via the quick fix
reveals a couple of handlers to be implemented. The resulting local
class definition for the behavior handler of the sales order should
look like Listing 11.11. The related local class definition for the saver
handler should look like Listing 11.12.
CLASS lhc_salesorder DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR salesorder RESULT result.
METHODS create FOR MODIFY
IMPORTING entities FOR CREATE salesorder.
METHODS update FOR MODIFY
IMPORTING entities FOR UPDATE salesorder.
METHODS delete FOR MODIFY
IMPORTING keys FOR DELETE salesorder.
METHODS read FOR READ
IMPORTING keys FOR READ salesorder RESULT result.
METHODS lock FOR LOCK
IMPORTING keys FOR LOCK salesorder.
METHODS rba_item FOR READ
IMPORTING keys_rba FOR READ salesorder\_item FULL result_requested RESULT
result LINK association_links.
METHODS cba_item FOR MODIFY
IMPORTING entities_cba FOR CREATE salesorder\_item.
ENDCLASS.
Listing 11.11
Behavior Implementation: Sales Order Handler (UNMANAGED)
Along with the handler method for the global authorization (as
discussed already in the MANAGED case) and the lock handler method
that will be described later, you can see that now all CRUD
operations need to be implemented accordingly, that is, read as well
as the read by association for defined associations, and create,
update, delete for defined entities, and create by association for
create-enabled associations. Note that the delete operation is listed
here although it was defined as internal, that is, defined on the
provider side, but can only be consumed by the provider itself.
CLASS lsc_zr_salesordertp_2 DEFINITION INHERITING FROM cl_abap_behavior_saver.
PROTECTED SECTION.
METHODS finalize REDEFINITION.
METHODS check_before_save REDEFINITION.
METHODS save REDEFINITION.
METHODS cleanup REDEFINITION.
METHODS cleanup_finalize REDEFINITION.
ENDCLASS.
Listing 11.12
Behavior Implementation: Sales Order Saver (UNMANAGED)
The saver handler, which you might know already from the MANAGED
scenario with a different handler method for statement WITH
UNMANAGED SAVE or WITH ADDITIONAL SAVE, consists of multiple handler
methods reflecting the ABAP RESTful application programming
model save phase, as we’ll describe in Section 11.4.5.
[»] UNMANAGED Implementation
We won’t provide an implementation for the UNMANAGED scenario as
it would blow up the current scope of this book. So, the example
implementation will focus on the MANAGED implementation and
greenfield approach. As stated, most of the things are agnostic to
the implementation type, but when a topic is specific to one or the
other scenario, we’ll mention it in the related section.
11.4.2
Behavior Pool and Handler Implementation
As discussed in the previous section, the ABAP RESTful application
programming model provides dedicated handler methods that offer a
typed signature which then needs to be implemented by the
application. We usually call this the provider implementation.
The implementation can be split across multiple global behavior
pools, multiple local handler classes, and multiple handler methods,
which we’ll explain now.
Global Behavior Classes (Behavior Pool)
In our example implementation, we’ll only work with one global
implementation class (behavior pool) while the behavior handlers
themselves must be implemented in local classes. This global class
usually implements at least the saver handler because it isn’t specific
to an entity. You can also specify implementation classes on the
entity level, for example, for every entity similarly (see Listing 11.13).
define behavior for ZR_SalesOrderTP alias SalesOrder
implementation in class zbp_r_salesorderroottp_2 unique
…
Listing 11.13
Behavior Definition: Behavior Implementation Pool on the Entity Level
Implementations can be distributed even further into separate
behavior pools by the group concept (see Listing 11.14). The group
defines a bracket around the to-be-implemented operations or
handlers.
group <group_name> implementation in class <class_name> unique
{
…
}
Listing 11.14
Behavior Definition: Behavior Implementation Pool on the Group Level
With this concept, you can distribute your implementation into
separate global behavior pools, for example, to distribute the
implementation work or reduce the size of the handler classes.
Local Handler Classes and Handler Methods
In addition, within one behavior pool, you can choose which local
class and method implements the operation. The operation
implementation itself is defined by the importing (and related
exporting) parameters of the method. So, you might implement every
single operation in a separate method and define for every method a
dedicated local class; or on the other extreme, you might define one
single local class and one single method for all operations of the
same type (i.e., all MODIFY or all READ operations). We recommend not
combining multiple operations into the same method if it isn’t
necessary. However, if, for example, the subsequent called internal
legacy API can handle multiple entities in one call, it makes sense to
put these operations into the same method to leverage this
combined processing.
By default, the infrastructure proposes one local handler class per
entity and, within the local handler class, one method for each
operation to be implemented. Depending on how the implementation
looks, it might be better to define the classes and methods
differently. Especially if existing functionality (classes or function
modules) are reused and the behavior implementation mainly
delegates to these, the capabilities of these APIs predefine the
reasonable combination of handlers. Here, everything is possible,
that is, putting everything into one class as well as putting operations
of the same type into the same method if it makes sense.
[»] Saver Handlers
Besides the various behavior handlers, there is also always
exactly one saver handler. The saver handler needs to be
implemented in the behavior pool defined on the behavior
definition level.
To control the invocation of the handler implementations at runtime if
multiple operations are invoked in one request within a behavior
pool, the implementations for different local handler classes can be
enhanced with syntax element BEFORE or AFTER another handler class
(see Listing 11.15). This defines a default sequence to optimize
processing, especially for the modify operations in ABAP RESTful
application programming model UNMANAGED or for determinations
(Section 11.4.12) in ABAP RESTful application programming model
MANAGED. In the latter case, however, this should not have any
functional impact as any validation and determination
(Section 11.4.12) is called if the trigger condition is met (independent
of whether it was called before already or not).
CLASS lhc_SalesOrderItem DEFINITION INHERITING FROM
cl_abap_behavior_handler
AFTER lhc_SalesOrder.
…
ENDCLASS.
Listing 11.15
Behavior Implementation: Before and After Syntax
Handler Signature
The signature of the different handlers is defined based on the
handler type and offers a dedicated and typed input and output that
need to be declared in the handler method definition. Some special
output parameters can’t be declared explicitly and are automatically
present in related handlers.
FAILED is used to indicate the failure of any operation whether it’s
because the instances aren’t found, the user isn’t authorized, or the
requested change isn’t allowed. This return parameter consists of a
structure with all entities and their keys, and it provides a control
structure to indicate the failed operation and a FAIL CAUSE to
distinguish the different causes. Based on the key, the failure can be
assigned to the related incoming key. In the OData exposure,
different fail causes usually are also mapped to different HTTP status
codes. FAILED is available in any handler.
REPORTED is used to provide additional messages, mainly for failures,
but warning, information, or success messages also can be added.
The parameter consists of a structure of all entities and their keys to
allow messages to be assigned to concrete instances. In addition,
the messages can also be assigned to dedicated operations or
fields. If messages can’t be assigned to an instance of an entity, this
has to be indicated via indicator %global. Additionally, if messages
can’t even be assigned to an entity, general messages can be
assigned to element %others. A message itself is defined as a T100
exception class and needs to implement interface
IF_ABAP_BEHV_MESSAGE.
Messages as such belong to the current request, but often we want
to assign messages to the state of the instances and keep them
persistent or buffered. This can be achieved by filling element
%state_area for a message, but this is only possible for a message
bound to an instance. In this case, the infrastructure will buffer these
messages until the same state area is invalidated again or provides
new messages. Thus, it’s important to always invalidate the relevant
state area the handler is responsible for within a handler
implementation by adding an empty message with this state area
into REPORTED. Note that REPORTED is available in any handler, but
state messages are not supported in all of them.
For our examples, we created an ABAP message class via NEW •
Other ABAP Repository Object • Message Class and added the
message we need for our application (see Figure 11.4). In addition,
we created an ABAP exception class that implements interface
IF_ABAP_BEHV_MESSAGE via NEW • ABAP Class and added the
respective messages and variables. The code snippets are provided
in the download area.
Figure 11.4
Message Class
MAPPED is used to inform the consumer about the creation of new
instances that have been created based on the consumer operation.
Every %cid (i.e., content ID) that is provided in the incoming request
needs to have a result in either MAPPED or FAILED. MAPPED contains
both field %cid and field %tky (consists of temporary or transient
instance key). MAPPED is technically available in any modify handler
but may only be used for create operations or factory actions
(Section 11.4.10).
11.4.3
Consumption via EML
The runtime access in the ABAP RESTful application programing
model to the modeled entities and business objects is done via the
Entity Manipulation Language (EML). EML is an enhancement of the
ABAP language to natively consume ABAP RESTful application
programming model–defined implementations. This allows a fully
typed consumption of any ABAP RESTful application programming
model–based implementation (independent of the provider internal
implementation type). It’s used to access the operations of an
implementation both internally, that is, within the handler (or
provider) implementations, as well as from the consumer
implementation. Via EML, all available operations can be accessed,
such as create, update, delete, any action, any function, lock
invocation, data retrieval, and retrieving feature control and
authorization information.
The access within the handler implementation on the provider side
usually is done via addition IN LOCAL MODE because, in this case,
certain things shouldn’t be checked, such as authorizations
(Section 11.4.7) or dynamic feature control (Section 11.4.13). This
also applies to static feature control, as you need to be able to also
set static read-only fields from within your own behavior
implementation (Section 11.4.4).
It’s the provider’s task to implement this local mode correctly. This
means that in particular the read and possibly also the write
operations in a self-implemented handler (implementation type
UNMANAGED) must use the runtime context and react accordingly, for
example, not checking the authorization. The context can be
determined as follows:
cl_abap_behv_aux=>get_current_context(
IMPORTING in_local_mode = data(in_local_mode)
).
Any of the read or modifying operations can also be bundled within
one call, for example, to invoke multiple actions, to create a
complete document with one request, or to read a complete
document with one request. As usual, in ABAP, EML offers a static
as well as a dynamic syntax variant. The following operations are
supported:
READ
To access data from the (transactional) buffer directly via
association or via function.
MODIFY
To modify data in the transactional buffer via create, update,
delete, or actions.
SET LOCKS
To set an enqueue lock.
GET PERMISSION
To access authorization and feature control information, merged
for convenient consumer access, allowing consumers to specify
which information will be included.
[»] Further Information
For detailed syntax and further information, check the official
ABAP help documentation: http://s-prs.co/v564204.
Based on the various example implementations, when enhancing the
model and its implementation in the next sections, you’ll understand
how the EML is working and see what the concrete syntax looks like.
11.4.4
Static Field Control
In the previous section, you’ve seen how to define operations, how
to expose them to consumers, and how to keep them internal. This is
what we call static operation control. Even if the creation or update of
instances is allowed, not all fields can be changed by the consumer,
and certain fields also might be mandatory for the consistency of an
instance. The corresponding restrictions are referred to as static field
control.
You’ve seen statement field ( readonly ), which is enforced by the
behavior definition syntax for some of the fields. You’ll learn in the
next section why the key fields of the related parent entity are set to
read only. In addition, we don’t want to allow our system
administrative fields to be set from the outside, so we also set these
to read only, as follows:
field ( readonly ) CreatedByUser;
field ( readonly ) CreationDateTime;
field ( readonly ) LastChangedByUser;
field ( readonly ) LastChangeDateTime;
You can also write multiple elements with the same settings into one
line/statement, but we recommend the use of settings per individual
field to increase readability and simplicity if the setting of one field is
changed or enhanced afterwards. The combined syntax would look
as follows:
field ( readonly ) CreatedByUser, CreationDateTime, LastChangedByUser,
LastChangeDateTime;
If a field value may only be supplied during a create operation and
then is fixed, this can be indicated with statement field (
readonly:update ). This restriction and setting apply implicitly for any
key field, but should be stated explicitly, nonetheless.
We can also define whether fields are mandatory. There are two
different flavors of this setting. The field ( mandatory ) syntax
indicates that a field value needs to be provided for this field at the
time of saving; that is, it relates to the consistency of the instance.
The same should be accompanied by a validation which checks that
such fields are supplied (discussed in Section 11.4.12). The quite
similar syntax field ( mandatory:create ), on the other hand, defines
that a field must be provided during the (technical) create operation;
that is, it’s not a validation of the data but an operation check. This
should usually only be used for key fields of an entity with early
external numbering (see the next section) because, by definition, key
fields can’t be changed afterward. While no infrastructure support is
available as of now for the mandatory validation, the operation check
for mandatorily provided fields is integrated into the infrastructure.
Thus, if the related fields aren’t provided during a create operation,
this will lead to a syntax error if it can be detected during
implementation time (static syntax check). Otherwise, it will result in
a runtime error. As in our sales order example, a sales order without
a customer and without products and their quantity to be ordered
doesn’t make any sense, so we define these fields as mandatory, for
example, for the sales order header as depicted here:
field ( mandatory ) SalesOrderType;
field ( mandatory ) SalesOrganization;
field ( mandatory ) SoldToParty;
We’ll also look at the other option of the mandatory setting when
explaining the numbering topic in the next section.
11.4.5
Numbering
For each entity within a behavior definition, the numbering defines at
which point in time the (final) key of an instance is determined and
who can define the key and key values. Currently, we haven’t
imposed any restrictions for setting the key fields of our entities,
which indicates that these can also be set from the consumer side
during a create operation, which is called external numbering. At the
same time, we haven’t defined any means to adapt the key value;
that is, it’s fixed during the create operation and can’t be changed
anymore afterwards, which is called early numbering.
This reflects the two aspects of numbering: on the one hand, who
defines the number, and on the other hand, at which point in time the
number is final. For the first aspect, the number could be given from
the consumer side (external numbering) or from the provider side
(internal numbering). This aspect isn’t “either or” but can also be
combined; that is, if no number is given for the create operation from
consumer side, the provider generates one. For the second aspect,
the number could be final already with the create operation (early
numbering) or only if the data is saved to the database the first time
(late numbering). “Late” refers to the fact that the number isn’t final
during the operation in which the related instance is created, but only
after first saving the instance to the database. We’ll discuss the
details and examples further in the following sections.
Early Numbering
Because we haven’t defined any further static field control, key field
SalesOrder of our sales order header entity can be supplied during
the create operation but doesn’t necessarily need to be supplied.
This means that if no value is supplied externally, the number needs
to be provided internally—usually from a number range. With this,
we see that a combination of external and internal numbering for the
same entity is also possible. For the UNMANAGED implementation, this
is completely fine because the create handler is implemented by the
application, and if no number is given, it’s provided and set as part of
this handler implementation. In the MANAGED case, this isn’t possible
because the create handler isn’t implemented by the application.
Depending on the use case, there are two ways to solve this issue.
If the scenario is pure external numbering, then we can indicate the
need to provide this field with static field control setting field (
mandatory:create ). We might complement this setting with field (
readonly:update ), but this would only be a documentary addition
because key fields by definition can never be changed. The same is
valid similarly in the UNMANAGED implementation.
If the scenario is a mixture of external and internal numbering or
pure internal numbering indicated by the already known field (
readonly ), we need an exit to provide the number in the MANAGED
case. This can be achieved by adding EARLY NUMBERING into the
behavior definition of the entity.
In our example implementation, we define the sales order header to
be provided externally, whereas for the sales order item and
schedule line, we choose internal numbering. To do this, we define
the key field of the sales order as follows:
field ( mandatory:create, readonly:update ) SalesOrder;
This states that the sales order header number needs to be given
externally during the create operation and can’t be changed
afterward. For the other two entity definitions, we enhance the
definition with EARLY NUMBERING.
When doing so, the behavior definition reveals a warning Early
Numbering for create by association _ITEM is not implemented.
A quick fix helps to add the missing implementations to the behavior
implementation class, and here we can now provide a proper
implementation, as depicted in Listing 11.16. As input of the
corresponding method, the complete create request is provided.
Thus, if the numbering depends on the incoming data (e.g., as key
fields are partially taken over externally if provided or as a different
number range interval is used based on some incoming data), this
can be achieved. For the schedule line, the same warning and a
similar implementation method and implementation are provided.
CLASS lhc_salesorder DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS earlynumbering_cba_item FOR NUMBERING
IMPORTING entities FOR CREATE salesorder\_item.
ENDCLASS.
CLASS lhc_salesorder IMPLEMENTATION.
METHOD earlynumbering_cba_Item.
READ ENTITIES OF zr_salesordertp IN LOCAL MODE
ENTITY salesorder BY \_item
FIELDS ( salesorderitem )
WITH CORRESPONDING #( entities )
RESULT DATA(sales_order_items)
FAILED failed.
LOOP AT entities ASSIGNING FIELD-SYMBOL(<sales_order>).
" get highest item from sales order items of a sales order
DATA(max_item_id) = REDUCE #( INIT max = CONV posnr( '000000' )
FOR sales_order_item IN sales_order_items
USING KEY entity
WHERE ( salesorder = <sales_order>-salesorder )
NEXT max = COND posnr(
WHEN sales_order_item-salesorderitem > max
THEN sales_order_item-salesorderitem
ELSE max )
).
"assign sales order item id
LOOP AT <sales_order>-%target ASSIGNING
FIELD-SYMBOL(<sales_order_item>).
APPEND CORRESPONDING #( <sales_order_item> ) TO
mapped-salesorderitem
ASSIGNING FIELD-SYMBOL(<mapped_sales_order_item>).
IF <sales_order_item>-salesorderitem IS INITIAL.
max_item_id += 1.
<mapped_sales_order_item>-salesorderitem = max_item_id.
ENDIF.
ENDLOOP.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
Listing 11.16
Behavior Implementation: Sales Order Item Early Numbering
A special feature of the managed implementation is managed
numbering with a globally unique identifier (GUID) known also from
Business Object Processing Framework (BOPF). Here, the
numbering is also taken over by the infrastructure if the key is a
GUID, and no dedicated exit implementation is needed. To indicate
that a number should be provided by the infrastructure, this can be
defined via further field information:
field ( numbering:managed ) SalesOrderUUID;
The syntax check verifies that the related field is a GUID. Usually, in
this case, the field is also read only, but the combination with
external numbering is supported even here.
Late Numbering
Often, in business applications, numbers are taken from a number
range rather late during the save phase to avoid gaps in used
numbers. The same applies with external numbering if, for example,
the number should be editable in a UI unless the document is saved
for the first time. The feature of late numbering is declared in the
behavior definition of an entity with syntax element LATE NUMBERING.
Adding it in our example of the unmanaged behavior definition on the
sales order header brings up some warnings that the other entities
need to declare the same as well. This occurs because, in our
example, the key is hierarchical; that is, the SalesOrder is also part of
the key of the two child entities. Therefore, these also need to be late
numbering as the full key is only given during save.
With this enhancement in the behavior definition, a new method to
draw the numbers is also included in the save phase, and we need
to add a redefinition into our local saver handler class:
METHODS adjust_numbers REDEFINITION.
This completes the steps and methods of the save phase.
As the number isn’t drawn during create at runtime, we need to
distinguish two created sales orders in the same transaction. To
enable this for late numbering, the runtime types for the entities are
extended by a field named %PID (preliminary ID). This GUID of type
RAW 16 acts as a handle in all runtime accesses and buffered
instances and is given back during create operations instead of the
final key. In the adjust numbers implementation during save, this
preliminary ID is mapped to the final key returned via parameter
MAPPED.
Similarly, the child entities get a field called %PID_PARENT to map the
complete temporary key (%tky) of the parent entity. Other
associations that have an entity with later numbering of the target
entity also require a corresponding additional field. This is also
automatically added with name %pid_<association_name> or, if an
abbreviation has been defined for the association via abbreviation
<association_abbreviation> (which is recommended), the name
%pid_<association_abbreviation>.
First, we define a new number range object by creating a new object.
To do so, press (Ctrl)+(N) and search for “number range”. Select
the found entry and click Next. In the following dialog, enter a name
and description to create the number range object (see Figure 11.5)
and confirm by clicking Finish.
Figure 11.5
Number Range: Create Dialog
To complete the number range definition in the source, enter the
Number Length Domain as “VBELN”, and activate the number
range object (see Figure 11.6).
Figure 11.6
Number Range
In addition, you need to set up a number range interval for the newly
created number range object. This can be done via Transaction
SNUM where we’ve set up number range interval “01” for our
example.
Now, we can add an implementation to draw the number late in the
saver handler. An example of the implementation is shown in
Listing 11.17. Here, it’s assumed that the transactional buffer of the
sales order header is kept in table MT_SALESORDER.
METHOD adjust_numbers.
LOOP AT mt_salesorder ASSIGNING FIELD-SYMBOL(<ls_salesorder>)
WHERE %pid IS NOT INITIAL.
CALL FUNCTION 'NUMBER_GET_NEXT'
EXPORTING
nr_range_nr
= '01'
object
= 'ZSALESORD'
IMPORTING
number
= <ls_salesorder>-SalesOrder
EXCEPTIONS
interval_not_found
= 1
number_range_not_intern = 2
object_not_found
= 3
quantity_is_0
= 4
quantity_is_not_1
= 5
interval_overflow
= 6
buffer_overflow
= 7
OTHERS
= 8.
IF sy-subrc = 0.
APPEND VALUE #( %pid
= <ls_salesorder>-%pid
SalesOrder = <ls_salesorder>-SalesOrder
) TO mapped-salesorder.
ELSE.
"error handling
ENDIF.
ENDLOOP.
ENDMETHOD.
Listing 11.17
11.4.6
Behavior Implementation: Sales Order Header Late Numbering
Exclusive Locks
Next, we add an enqueue lock to our implementation. In MANAGED
implementations, there is an enqueue functionality available out of
the box, so no implementation or definition of a custom enqueue
object is needed. For the UNMANAGED implementation, this is a
mandatory handler to be implemented. If required, the same can
also be done similarly for a MANAGED implementation by adding
UNMANAGED to the LOCK MASTER:
lock master unmanaged
Enhancing the syntax reveals a warning message that the lock
operation isn’t yet implemented. With the help of the quick fix, the
necessary method can be added automatically to the behavior
handler class.
[+] Using an Existing Enqueue Object
If you’re converting an existing application with an existing SAP
enqueue object, you can and should reuse this enqueue object to
ensure interoperability as well as data integrity between the new
and old application implementation.
To implement the enqueue functionality, we first create a new lock
object. To do so, open the context menu of your package, choose
New • Other ABAP Repository Object, and search for “Lock
Object” (see Figure 11.7). Choose Lock Object, and click Next >.
Figure 11.7
Lock Object: Create Dialog
In the creation dialog, as shown in Figure 11.8, enter
“EZSALESORDER” in the Name field and a reasonable description
in the Description field. Enter “ZSALESORDER” in the Primary
Table field to represent the database table of the sales order header
entity. Complete the creation by clicking Next >.
Figure 11.8
Lock Object: Create
In the detail screen, in the Lock Mode field, choose Write Lock, and
then activate the lock object by clicking the Activate button. On
activation, the related enqueue function modules are generated (see
Figure 11.9).
Figure 11.9
Lock Object
Now we can add the lock implementation into the related handler, as
shown in Listing 11.18. Note that our example lock implementation
doesn’t perform an existence check as we only need the key field for
the lock arguments that is already provided by the input signature.
METHODS lock FOR LOCK
IMPORTING keys FOR LOCK SalesOrder.
…
METHOD lock.
TRY.
DATA(lock) = cl_abap_lock_object_factory=>get_instance(
iv_name = 'EZSALESORDER'
).
LOOP AT keys ASSIGNING FIELD-SYMBOL(<salesorder>).
TRY.
lock->enqueue(
it_parameter = VALUE #( ( name = 'SALESORDER'
value =
REF #( <salesorder>-SalesOrder ) ) )
).
CATCH cx_abap_foreign_lock INTO DATA(lx_foreign_lock).
SELECT SINGLE FROM I_User WITH PRIVILEGED ACCESS
FIELDS UserDescription
WHERE UserID = @sy-msgv1
INTO @DATA(lv_user).
APPEND VALUE #( %key = <salesorder>
%fail-cause = if_abap_behv=>cause-locked
) TO failed-salesorder.
APPEND VALUE #( %key = <salesorder>
%msg = NEW zcm_salesordertp(
textid
= zcm_salesordertp=>locked
salesorder = <salesorder>-SalesOrder
user
= SWITCH #( lv_user WHEN space
THEN sy-msgv1 ELSE lv_user )
severity
= if_abap_behv_message=>
severity-error
)
) TO reported-salesorder.
ENDTRY.
ENDLOOP.
CATCH cx_abap_lock_failure INTO DATA(lx_exp).
RAISE SHORTDUMP lx_exp.
ENDTRY.
ENDMETHOD.
Listing 11.18
Behavior Implementation: Sales Order Header Lock
The lock handler is invoked for any modifying instance-related
operation and expected to either set a lock or return proper fail
cause LOCKED and an appropriate error message. As seen in the
behavior definition syntax, the LOCK MASTER is stated only on the root
entity, but obviously also an update of the sales order item needs to
ensure a proper lock; that is, in this case, it’s expected that the sales
order is locked. The same is supported out of the box by the
infrastructure with the following syntax:
lock dependent by _SalesOrder
This statement relates to an association from your own entity to the
entity that acts as the lock master. The ABAP infrastructure uses this
association to, in case of a modifying operation on the sales order
item, navigate from the sales order item to the sales order header
and invokes its lock handler. If the target key of the lock master entity
is mapped by the ON condition of the association, the (in this case,
unnecessary) navigation is omitted for performance reasons. In this
case, the lock handler is called by the ABAP infrastructure using the
source information. If the lock fails, the related FAILED information is
also mapped automatically by the infrastructure back to the incoming
operations.
[»] Lock Master on the Subentity Level
Currently, locks can only be modeled, implemented, and set at the
root entity level, that is, for the entire business object. In most
cases, this is sufficient. To avoid lock conflicts, however, it’s
sometimes useful to set locks on child entities of the business
object if the application logic and processing of a business object
allow this. Such a locking mechanism can currently be achieved
only by defining different root entities for the lockable entities.
11.4.7
Authorization Checks
While exclusive locks prevent data from being edited and overwritten
simultaneously, authorization checks control access to the data and
operations. The different operations on the underlying documents,
such as creating, reading, changing, and deleting, as well as each
specifically defined action or function, can be authorized separately.
Because authorizations are always application specific and depend
on specific application data (organizational units, order types, etc.),
you must define the required authorization object yourself.
As you’ve already learned in Chapter 5, the read permission is
modeled with CDS access controls (data control language [DCL])
when accessing the data via ABAP SQL. These access controls are
applied automatically when data is selected using the CDS views.
Such a locking mechanism is also applied during transactional read
access for a MANAGED implementation. Because an UNMANAGED
implementation handles read accesses itself, the read authorizations
need to be implemented explicitly in the read handlers of the
UNMANAGED implementation. Usually, an UNMANAGED implementation
doesn’t access the data via CDS views here, but via the existing
buffer and direct access to the underlying (legacy) database tables.
In such cases, explicit authority checks may need to be added to the
ABAP action.
For the modifying access, you must always add an appropriate
implementation of the authorization check because the granted
authorizations to users are different as not everybody that is allowed
to read data is automatically allowed to modify data or execute a
specific action or function. For the implementation of authorization
checks for modifying operations, there are dedicated handlers
integrated into the ABAP RESTful application programming model
runtime orchestration. We recommend using these handlers also if
there is an UNMANAGED implementation, although theoretically the
authorization checks could also be done in the modify handlers
(similar as for the read authorization in read handlers).
[»] Authorization for Read Operations
The defined authorization handlers are only available for modifying
operations. For read operations, the respective authority checks
are either expected to be invoked based on the data access via
CDS and the related access control or by implementing the same
in the respective read handlers. The same also applies for
functions (Section 11.4.11) where you need to perform the
authorization check in the function implementation.
The already-implemented sample behavior definition specifies the
global authorization, that is, a non-instance-related authorization
check. This is available for all modifying operations, especially noninstance-related operations such as create or static actions
(introduced later in Section 11.4.10). But global authorization can
also be implemented for any instance-related operation such as
update or delete. The global authorization is invoked before any
instance-related operation in the runtime orchestration, such as
locking, thus it can reject requests early. But it’s also useful in use
cases where the authorization check, even for these operations, isn’t
instance specific.
The syntax in the behavior definition to define global authorization is
as follows:
authorization master ( global )
With this in the behavior implementation class, we get a handler to
be implemented. In the signature, all available modifying operations
are listed for which the authorization can be requested that should
return a negative result if the authorization isn’t granted. In addition,
a corresponding message should be returned if an operation isn’t
permitted.
In the example, we reuse authorization object V_VBAK_AAT, which is
part of the SAP standard functionality, for the sales order processing.
Listing 11.19 shows an example implementation of the global
authorization check.
METHODS get_global_authorizations FOR GLOBAL AUTHORIZATION
IMPORTING REQUEST requested_authorizations FOR SalesOrder
RESULT result.
…
METHOD get_global_authorizations.
IF requested_authorizations-%create = if_abap_behv=>mk-on.
AUTHORITY-CHECK OBJECT 'V_VBAK_AAT'
ID 'ACTVT' FIELD '01'
ID 'AUART' DUMMY.
IF sy-subrc <> 0.
result-%create = if_abap_behv=>auth-unauthorized.
APPEND VALUE #( %global = if_abap_behv=>mk-on
%msg = NEW zcm_salesordertp(
textid
= zcm_salesordertp=>
no_auth_create
severity = if_abap_behv_message=>
severity-error
)
) TO reported-salesorder.
ENDIF.
ENDIF.
IF requested_authorizations-%update = if_abap_behv=>mk-on.
AUTHORITY-CHECK OBJECT 'V_VBAK_AAT'
ID 'ACTVT' FIELD '02'
ID 'AUART' DUMMY.
IF sy-subrc <> 0.
result-%update = if_abap_behv=>auth-unauthorized.
APPEND VALUE #( %global = if_abap_behv=>mk-on
%msg = NEW zcm_salesordertp(
textid
= zcm_salesordertp=>
no_auth_update
severity = if_abap_behv_message=>
severity-error
)
) TO reported-salesorder.
ENDIF.
ENDIF.
ENDMETHOD.
Listing 11.19
Behavior Implementation: Sales Order Header Global Authorization
In most cases, the authorization is controlled not only on the global
level but also on the instance level, for example, the sales area or
order type in our example. This is achieved by defining the
authorization as follows:
authorization master ( instance )
Again, now the related handler is missing, and the provided quick fix
adds an implementation method to our handler class. The signature
of the method is similar to the global authorization handler but only
contains the instance-related operations and adds the keys for which
the authorizations will be checked.
Listing 11.20 shows an example implementation of the instance
authorization check. Note that for the sake of simplicity, the example
isn’t optimized regarding executing the authority check only once for
every order type but is implemented straight forward, which adds
some redundancy.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR
SalesOrder RESULT result.
…
METHOD get_instance_authorizations.
READ ENTITIES OF zr_salesordertp IN LOCAL MODE
ENTITY salesorder
FIELDS ( salesordertype )
WITH CORRESPONDING #( keys )
RESULT DATA(salesorders)
FAILED failed.
LOOP AT salesorders ASSIGNING FIELD-SYMBOL(<salesorder>).
IF requested_authorizations-%update = if_abap_behv=>mk-on.
AUTHORITY-CHECK OBJECT 'V_VBAK_AAT'
ID 'ACTVT' FIELD '02'
ID 'AUART' FIELD <salesorder>-salesordertype.
IF sy-subrc <> 0.
APPEND VALUE #( %tky
= <salesorder>-%tky
%update = if_abap_behv=>auth-unauthorized
) TO result.
APPEND VALUE #( %tky = <salesorder>-%tky
%msg = NEW zcm_salesordertp(
textid
= zcm_salesordertp=>
no_auth_update
salesorder = <salesorder>-salesorder
severity
= if_abap_behv_message=>
severity-error
)
) TO reported-salesorder.
ENDIF.
ENDIF.
ENDLOOP.
ENDMETHOD.
Listing 11.20
Behavior Implementation: Sales Order Instance Authorization
You might also use both global and instance combined.
In general, it’s possible to define each entity as an AUTHORIZATION
MASTER. In this case, you get an authorization handler for each of the
standard create, update, and delete operations and the
corresponding operations into the handler signature. In most cases,
however, the authorization check is performed only on the root entity,
and all modifying operations on child entities are mapped to the
operation update. This can be modeled using the association to the
corresponding root entity:
authorization dependent by <AssoziationsName>
In this case, there is no authorization handler on this entity or for
these operations. The corresponding requests are delegated by the
ABAP infrastructure to the respective AUTHORIZATION MASTER.
As you might have recognized in the signature and implementation,
the delete operation of the sales order header isn’t present. This is
because we’ve declared this operation as internal, and thus it can
only be called inside the provider with IN LOCAL MODE where no
authorization checks or feature control checks (Section 11.4.13) are
part of the runtime orchestration (Section 11.8).
[»] Privileged Access from the Business Logic
The authorization checks just defined and implemented are
executed via standard EML access. Programmatic access to the
data via EML IN LOCAL MODE is always privileged. The background
to this is that the corresponding operations must also be executed
successfully in the internal business logic, for example, to write
static read-only fields. Another example is the duplicate check for
external numbering, which must be carried out without
authorization restrictions to find possible duplicates.
11.4.8
Authorization Contexts and Privileged Access
As already mentioned, internal accesses (IN LOCAL MODE) are always
privileged, among other things in terms of permissions, but you may
also want to offer your consumers a similar mode. For this, we first
define an authorization context. This authorization context specifies
which authorization objects are actively checked in the
implementation and is defined as follows:
define own authorization context { … }
In our examples we’ve used two authorization objects in our DCL
that we add to our own authorization context:
define own authorization context { 'V_VBAK_AAT'; 'V_VBAK_VKO'; }
To offer privileged access, we also first need to define a respective
authorization context with a readable name, for example:
define authorization context NoCheckWhenPrivileged {
}
With this authorization context, we can then offer the functionality for
a privileged access with this addition to the behavior definition:
with privileged mode disabling NoCheckWhenPrivileged;
A consumer who now wants to call operations privileged (and of
course must take care of the corresponding custom permission
checks) now has the key word PRIVILEGED available in EML (similar
to SQL with the corresponding syntax WITH PRIVILEGED ACCESS).
Privileged access defined in this way must then be implemented by
the provider. To do this, the implementation must determine the
runtime context (similar as described for the local mode):
cl_abap_behv_aux=>get_current_context(
IMPORTING privileged = data(privileged)
).
This information can then be used to exclude authorization checks,
which can be quite difficult depending on the existing code called.
Fortunately, this can all be done much more simply as well.
Authorization objects can also be assigned to the privileged
authorization context. In our example, we want to include both
authorization objects:
define authorization context NoCheckWhenPrivileged
{ 'V_VBAK_AAT'; 'V_VBAK_VKO'; }
The authorization context used for privileged mode must by definition
always be a subset of its own authorization context. In the best case,
they are identical, but there are also reasons never to privilege some
authorization objects and to check them always. At runtime for
privileged access, the listed authorization objects are now
automatically deactivated by the ABAP infrastructure. That is, the
AUTHORITY-CHECK statement always returns “0” for these authorization
objects.
Because the authorization objects of our own authorization context
are repeated in the definition of the authorization context for
privileged access, this results in some redundancy, as we can
already see in our simple example. To avoid this, we can include the
already defined authorization objects in the definition of our own
authorization context and add more if necessary:
define own authorization context by privileged mode and { … }
In our simple example, we don’t need the enhancement because
both authorization contexts are identical.
As we’ve seen, the definition of authorization contexts is generally
possible and only their use, for example, for privileged access,
defines their semantics. Another use case for authorization contexts
is the disabling of (unwanted) authorization checks that occur, for
example, through calls to other applications or APIs that don’t offer
privileged access (yet). For these, too, there is native support from
the ABAP RESTful application programming model because, just as
with the privileged authorization context, the listed authorization
objects can be automatically disabled in respective handlers. For the
handlers of read or modify operations, this is defined as follows:
define authorization context NoCheckWhenReadingOrModifying
for disable ( modify, read ) ##WARN_OK { … }
The deactivation only has an effect in handlers of type READ or
MODIFY, so a warning is given for implementation type MANAGED
because only the handlers for actions and functions are implemented
here by the application. We can hide or confirm the warning with
pragma ##WARN_OK. In the UNMANANGED implementation type, the early
save phase is also supported for disabling as the related handlers of
the early save phase are implemented by the application. The late
save phase is supported for disabling in all implementation types.
This can be done as follows:
define authorization context NoCheckWhenSaving
for disable ( save:early, save:late ) { … }
If automatic deactivation isn’t sufficient in special cases, this can also
be done in the implementation itself as a last option. However, this
syntax statement is only allowed within a behavior pool, and an open
context must also be closed again in the same programming unit:
AUTHORITY-CHECK DISABLE BEGIN CONTEXT <NameOfContext>.
…
AUTHORITY-CHECK DISABLE END.
11.4.9
Associations
Associations of type composition are basic and define the
composition tree of an object. In CDS, other associations can be
defined, such as to master data as foreign keys or similar. In our
concrete example, we’ve associated the user master for the
administrative data as well as the business partner and the product.
These associations are available via SQL, but not yet via EML; i.e.,
only for the persisted data, but not for the data changed within the
same transaction. To create the possibility to also access the
business partner transactionally via the association, for example, or
even to create a business partner in the same transaction, each
association where the target key of the entity is bound in the on
condition can also be defined as transactional. Here, the association
must be listed in the behavior definition in the same way as the
compositions. This is only possible if the target of the association is
also part of a behavior definition.
For our example, we restrict ourselves to new associations within our
business object and behavior definition and define a specialization
for each type of a schedule line. Here, we extend our CDS model as
shown in Listing 11.21.
define view entity ZR_SalesOrderItemTP
…
association [0..*] to ZR_SalesOrderScheduleLineTP as
_RequestedScheduleLine
on $projection.SalesOrder
=
_RequestedScheduleLine.SalesOrder
and $projection.SalesOrderItem =
_RequestedScheduleLine.SalesOrderItem
and 'R'
=
_RequestedScheduleLine.SalesOrderScheduleLineType
association [0..*] to ZR_SalesOrderScheduleLineTP as
_ConfirmedScheduleLine
on $projection.SalesOrder
=
_ConfirmedScheduleLine.SalesOrder
and $projection.SalesOrderItem =
_ConfirmedScheduleLine.SalesOrderItem
and 'C'
=
_ConfirmedScheduleLine.SalesOrderScheduleLineType
…
{
…
_RequestedScheduleLine,
_ConfirmedScheduleLine,
…
}
Listing 11.21
CDS Model: Define Association
As we want to support these associations for transactional usage,
we also add them to the behavior definition, as shown in
Listing 11.22.
define behavior for ZR_SalesOrderItemTP alias SalesOrderItem
…
{
…
association _RequestedScheduleLine;
association _ConfirmedScheduleLine;
…
}
Listing 11.22
Behavior Definition: Define Association
Besides read access via EML, we now also want to be able to create
these types of schedule lines via these associations. Therefore, we
add operation create to the associations. In doing so, you’ll discover
that adding operation create will lead to a syntax error of Noncomposition
'ZR_SALESORDERITEMTP'\'_REQUESTEDSCHEDULELINE'
cannot be create-enabled in a behavior definition with
implementation type MANAGED. The root cause is that this operation
isn’t yet supported by the MANAGED provider implementation. Thus, we
need to implement the association within our own handler, the same
as in UNMANAGED, which can be expressed via unmanaged association,
as shown in Listing 11.23.
define behavior for ZR_SalesOrderItemTP alias SalesOrderItem
…
{
…
unmanaged association _RequestedScheduleLine { create; }
unmanaged association _ConfirmedScheduleLine { create; }
…
}
Listing 11.23
Behavior Definition: Unmanaged Association
After adding these new operations, we’re now asked to implement
them. In doing so, we notice the now expected handlers for the
authorizations. Because we don’t want to implement these
specifically, since the standard update authorization is sufficient in
this case same as for the composition, we simply delegate this to the
AUTHORIZATION MASTER using authorization:update (see
Listing 11.24).
unmanaged association _RequestedScheduleLine
{ create ( authorization : update ); }
unmanaged association _ConfirmedScheduleLine
{ create ( authorization : update ); }
Listing 11.24
Behavior Definition: Delegate Authority Check
For previously existing composition associations, this is implicitly
defined and therefore this addition isn’t necessary.
In the implementation of this association, we simply delegate to the
composition and filter the appropriate instances in the result when
reading or set the value of the appropriate type field when creating.
Listing 11.25 shows the sample implementation for the requested
schedule lines.
METHOD rba_Requestedscheduleline.
cl_abap_behv_aux=>get_current_context( IMPORTING in_local_mode =
DATA(in_local_mode) ).
IF in_local_mode = abap_true.
READ ENTITY IN LOCAL MODE ZR_SalesOrderItemTP
BY \_scheduleline
FROM CORRESPONDING #( keys_rba )
RESULT DATA(salesorderschedulelines)
LINK DATA(salesorderschedulelinelinks)
FAILED failed
REPORTED reported.
ELSE.
READ ENTITY ZR_SalesOrderItemTP
BY \_scheduleline ##NO_LOCAL_MODE
FROM CORRESPONDING #( keys_rba )
RESULT salesorderschedulelines
LINK salesorderschedulelinelinks
FAILED failed
REPORTED reported.
ENDIF.
LOOP AT salesorderschedulelines ASSIGNING FIELD-SYMBOL(<salesorderscheduleline>)
WHERE SalesOrderScheduleLineType <> 'R'.
DELETE salesorderschedulelinelinks WHERE target-%tky =
<salesorderscheduleline>-%tky.
DELETE salesorderschedulelines WHERE %tky = <salesorderscheduleline>-%tky.
ENDLOOP.
result = CORRESPONDING #( salesorderschedulelines ).
association_links = CORRESPONDING #( DEEP salesorderschedulelinelinks ).
ENDMETHOD.
METHOD cba_Requestedscheduleline.
DATA create TYPE TABLE FOR CREATE
zr_salesordertp\\salesorderitem\_scheduleline.
create = CORRESPONDING #( DEEP entities_cba ).
LOOP AT create ASSIGNING FIELD-SYMBOL(<create>).
LOOP AT <create>-%target ASSIGNING FIELD-SYMBOL(<target>).
<target>-SalesOrderScheduleLineType = 'R'.
<target>-%control-SalesOrderScheduleLineType =
if_abap_behv=>mk-on.
ENDLOOP.
ENDLOOP.
MODIFY ENTITY IN LOCAL MODE ZR_SalesOrderItemTP
CREATE BY \_ScheduleLine FROM create
MAPPED DATA(mapped_local)
FAILED failed
REPORTED reported.
mapped = CORRESPONDING #( DEEP mapped_local ).
ENDMETHOD.
Listing 11.25
Behavior Implementation: Unmanaged Association
[»] Alternative Modeling
Because the read operation is already supported by default for this
simple example, it’s better to define an internal equivalent
association that contains only the read operation. You can then
delegate to it 1:1 without reading too much data and filtering it out
afterwards.
11.4.10
Actions
In addition to the standard operations, such as create, change, and
delete, we also want to provide higher-level operations in our
applications that offer more convenient functionalities and
consistently bundle several basic operations. These operations are
referred to in the ABAP RESTful application programming model as
actions. Simply said, in UI-based consumptions, actions correspond
to the buttons in the application’s UI. Like the other operations,
actions can be exposed to the consumers or can be defined as
internal if they are intended to be consumed only within your own
business object and behavior implementation.
Actions can be defined with instance-related actions or as static
actions (i.e., not instance related). Instance-related actions are
invoked on instances (similar to the instance-related operations:
create by association, update, or delete). Thus, instance-related
actions automatically get a key parameter inferred to the action
handler signature, whereas static actions only get a handle into the
signature (%CID) to correlate the result, failures, or messages with the
request.
We’ll explore actions further in the following sections, including
locking, authorization, parameters, and results, as well as take a look
at a special type of actions.
Action Locking and Authorization
As you’ve seen, the ABAP infrastructure takes care to lock invoked
instances for the standard operations. The same is also done for
(instance-related) actions automatically; that is, if an action is
invoked on an instance, this instance is locked up front by calling the
lock handler. As static actions have no instance relation, the lock
handler isn’t called up front. If within the actions, further modifying
statements are invoked (in your own application or a different one),
related instances are locked accordingly.
If locking isn’t wanted up front—for example, for an action Copy, you
don’t want to exclusively lock the copy template—this can be stated
via the following:
action ( lock: none ) <ActionName> …;
Like locking, authorization checks are also integrated into the ABAP
RESTful application programming model runtime orchestration.
Thus, when adding new actions, the signature of the authorization
handlers requested_authoriziatons is also enhanced with the related
action in parameter %action. Static actions only appear in the
signature of the global authorization handler because they have no
instance relationship, whereas other actions appear in both the
global and the instance authorization handler.
In many cases, actions have no checks of their own or specific
authorization checks, but just check the standard update
authorization. While this behavior can be easily achieved in the
authorization handler implementation for actions defined in the
authorization master entity, this always requires your own
implementation for actions in the authorization-dependent entities. If
the action doesn’t require special authorization, this can also be
indicated via the following:
action ( authorization: update ) <ActionName> …;
In this case, an action call invokes the standard update authorization
check similar to the standard operations on the authorization
dependent entities.
In the rather unusual case that an action doesn’t require any
authorization, this can be expressed via the following:
action ( authorization: none ) <ActionName> …;
In this case, the authorization handler isn’t invoked at all if an action
is called (and the action is also not added to the signature of the
authorization handlers).
External Action Name
The action name as an ABAP-related artifact can have up to 30
characters, which sometimes requires abbreviations to be used for
longer expressive action names. Thus, the behavior definition also
allows you to define an external action name that can contain up to
128 characters:
action <ActionName> external '<ExternalActionName>' …;
This external action name is only used in the OData exposure
(Section 11.9) where the external name is used as the action name
in the OData metadata. We recommend using the external action
name to expand potential abbreviations in the action name and omit
the same if it’s not needed.
Action Parameters
Unlike standard operations, actions can also be further
parameterized. This allows consumers (e.g., the end user) to
influence the behavior of an action within a predefined frame. In a UI,
this corresponds to a user dialog box after clicking the corresponding
button in which allowed values for the defined parameters can be
entered. An action parameter is defined as an ABAP Data Dictionary
structure that can contain multiple elements of different types or as a
CDS abstract entity to define the parameter data type.
Actions optionally also can provide a result with a defined result
cardinality. The result can be either an entity indicated by key word
entity (with special notation $self for the entity the action is defined
and bound at) or any result data type of your choice. In the latter
case, an action result is defined as an ABAP Data Dictionary type or
as a CDS abstract entity to define the result data type.
[»] Action Result Cardinality and Mass Enablement
Cardinality should not be confused with mass enablement, that is,
invoking an action on multiple instances within one request. The
ABAP RESTful application programming model is mass-enabled
by design; that is, every operation can be invoked on multiple
instances, and multiple operations can be bundled within one EML
statement (Section 11.4.3). If, for example, a copy action provides
a numeric parameter NumberOfCopies, and the same is invoked on
2 instances—A and B—with a parameter value of “5”, then the
result of a successful action invocation will be 10 additional
instances, that is, 5 copies of instance A and 5 copies of instance
B.
CDS-Based Typing
We’ll focus on the CDS approach to define action parameters and
results, that is, abstract entities, that are used for pure typing here,
as then we can leverage the CDS annotation feature set. An abstract
entity is defined via key words define abstract entity (refer to
Chapter 2, Section 2.1) and defines the single fields and their typing
via a built-in data type or data element. With this, we can define
simple structures for the parameter and the result (while the result
can also have multiple lines for one input based on the defined
cardinality).
As known from the ABAP Data Dictionary, we may not only have
simple data types, but the action parameter or result may also
contain structure or table types. The same can also be achieved with
CDS abstract entities with the help of associations. A deep type is
defined by adding an association, and the defined cardinality [1..1]
or [0..*] indicates whether the associated abstract entity is
representing a structure or table type. Usually, we define the
associated data types as part of this usage and use the composition
association, but it’s also possible to reuse such a data type by using
the key word association. An example with the different options is
shown in Listing 11.26. We recommend following the naming
convention and start such elements with an underscore (“_”) here as
well. Note that these abstract entities defined for data types shall not
specify key fields. The data types themselves can again be deep and
contain structures and table types (i.e., associations).
@VDM.usage.type: [#ACTION_PARAMETER_STRUCTURE]
define root abstract entity <NameOfDataType>
{
…
Field
: <NameOfDataElement or Built-In Type>
_Structure
: composition [1] of <NameOfUsedDataType1>;
_Table
: composition [0..*] of <NameOfUsedDataType2>;
_ReuseStructure : association [1] of <NameOfReusedDataType1>;
_ReuseTable
: association [0..*] of <NameOfReusedDataType2>;
}
Listing 11.26
CDS Abstract Entity: Structured Data Type
To use such a deep type in the ABAP RESTful application
programming model, we also need to define a behavior definition for
the data type. While all further data types included as composition
are defined in that same behavior definition, the reuse data types
defined as root abstract entities via association need to provide their
own behavior definition accordingly. The behavior definition itself
again is abstract and indicates the definition of a type with the key
word with hierarchy;. An example related to the previously defined
CDS abstract entity is shown in Listing 11.27.
abstract;
strict;
with hierarchy;
define behavior for <NameOfDataType>
{
association _Structure;
association _Table;
}
define behavior for <NameOfUsedDataType1>
{
…
}
define behavior for <NameOfUsedDataType2>
{
…
}
Listing 11.27
Behavior Definition: Structured Data Type
In addition, for multiple or even deep parameters, the application
might want to define certain parameters as optional or might need to
know if a parameter has been provided or not. If the initial value is
valid, this requires certain control fields that are already widespread
in the ABAP RESTful application programming model and known via
the %control structure. The same can be defined on the entity level
for each abstract entity; that is, it can also be achieved in deep
types. An example related to the previously defined CDS abstract
entity is shown in Listing 11.28.
abstract;
strict;
with hierarchy;
define behavior for <NameOfDataType> with control
{
…
}
Listing 11.28
Behavior Definition: Structured Data Type with Control
With the given set, we can realize any kind of deep structures as an
action result, but still the option to define a result of type data
element via CDS is missing. Here we require special behavior syntax
scalar entity. The definition of the CDS root abstract entity is the
same and usually should contain exactly one field (see
Listing 11.29). In fact, it doesn’t matter if there are further elements
defined in the CDS entity because in the related behavior definition,
the relevant fields need to be listed explicitly (see Listing 11.30).
@VDM.usage.type: [#ACTION_RESULT_STRUCTURE]
define root abstract entity <NameOfDataType>
{
Field : <NameOfDataElement or Built-In Type>
}
Listing 11.29
CDS Abstract Entity: Scalar Data Type
abstract;
strict;
with hierarchy;
scalar entity <NameOfDataType> field Field;
Listing 11.30
Behavior Definition: Scalar Data Type
[»] Parameter and Result Data Types
While parameter and result data types can refer to any ABAP Data
Dictionary data type, we recommend only using the CDS-based
typing via abstract entities and the provided deep types options via
behavior definition. If dictionary types are used, at least avoid
using data references or boxed components as these can’t be
exposed via OData out of the box. Further general restrictions
apply for OData V2 based on the available typing according to the
standard OData specification.
Example: Delete Action
Now let’s add an action to our example. We haven’t yet allowed
direct deletion of sales orders in the model. For enabling the
deletion, we can now implement an action that, depending on the
delivery status of the sales order, carries out a physical deletion or
logical deletion by setting a deletion indicator. To do this, define an
action in the behavior definition. The action has no parameter and no
result (as the instance will be deleted as a result of the successful
action execution, and the success or failure of the action is sufficient
information). In the behavior definition, add the following action:
action Delete;
After saving and activating the behavior definition, the syntax
warning reveals that the action implementation is missing. We use
the quick fix to create the method definition automatically. Further,
we can see that the authorization handlers now have a new field in
requested_authoriziatons: %action-delete. Thus, we enhance our
authorization handler implementations accordingly to check the
delete activity (see Listing 11.31 and Listing 11.32). As the delete
operation should be mapped to a separate activity (in our case, the
standard activity “06”), we call the authorization check in case the
delete operation is requested with activity “06”. The implementation
of the global authorization handler is optional as this is an instancerelated operation.
…
IF requested_authorizations-%action-Delete = if_abap_behv=>mk-on.
AUTHORITY-CHECK OBJECT 'V_VBAK_AAT'
ID 'ACTVT' FIELD '06'
ID 'AUART' DUMMY.
IF sy-subrc <> 0.
result-%action-Delete = if_abap_behv=>auth-unauthorized.
APPEND VALUE #( %global = if_abap_behv=>mk-on
%msg = NEW zcm_salesordertp(
textid
= zcm_salesordertp=>
no_auth_delete
severity = if_abap_behv_message=>
severity-error
)
) TO reported-salesorder.
ENDIF.
ENDIF.
…
Listing 11.31
Behavior Implementation: Global Authority Check for Action Delete
…
LOOP AT salesorders ASSIGNING FIELD-SYMBOL(<salesorder>).
…
IF requested_authorizations-%action-Delete = if_abap_behv=>mk-on.
AUTHORITY-CHECK OBJECT 'V_VBAK_AAT'
ID 'ACTVT' FIELD '06'
ID 'AUART' FIELD <salesorder>-SalesOrderType.
IF sy-subrc <> 0.
APPEND VALUE #( %tky
= <salesorder>-%tky
%action-Delete = if_abap_behv=>auth-unauthorized
) TO result.
APPEND VALUE #( %tky = <salesorder>-%tky
%msg = NEW zcm_salesordertp(
textid
= zcm_salesordertp=>
no_auth_delete
salesorder = <salesorder>-salesorder
severity
= if_abap_behv_message=>
severity-error
)
) TO reported-salesorder.
ENDIF.
ENDIF.
ENDLOOP.
…
Listing 11.32
Behavior Implementation: Instance Authority Check for Action Delete
The method definition for the action implementation looks like the
following:
METHODS delete FOR MODIFY
IMPORTING keys FOR ACTION salesorder~delete.
In the method implementation (see Listing 11.33), we now read the
sales orders handed over. IN LOCAL MODE is used because we’re
inside our implementation, and authorization checks have already
been done for the action. Based on the status of the sales order, we
decide to update the sales order deletion indicator or physically
delete the instance. As we haven’t defined a result, the only result
information is whether the operation failed or whether there are error
or success messages via REPORTED.
METHOD delete.
READ ENTITY IN LOCAL MODE ZR_SalesOrderTP
FIELDS ( DeliveryStatus DeletionIndicator )
WITH CORRESPONDING #( keys )
RESULT DATA(salesorders)
FAILED DATA(failed_not_found).
DATA update TYPE TABLE FOR UPDATE zr_salesordertp\\salesorder.
DATA delete TYPE TABLE FOR DELETE zr_salesordertp\\salesorder.
LOOP AT salesorders ASSIGNING FIELD-SYMBOL(<salesorder>).
IF <salesorder>-DeliveryStatus = space OR <salesorder>-DeliveryStatus = 'A'.
"physically delete sales orders with delivery status space or A
APPEND VALUE #( %tky = <salesorder>-%tky ) TO delete.
ELSEIF <salesorder>-DeletionIndicator = abap_false.
"logically delete sales orders with delivery status B or C
APPEND VALUE #( %tky
= <salesorder>-%tky
DeletionIndicator
= abap_true
%control-DeletionIndicator = if_abap_behv=>
mk-on ) TO update.
ENDIF.
ENDLOOP.
MODIFY ENTITY IN LOCAL MODE ZR_SalesOrderTP
UPDATE FROM update
DELETE FROM delete
FAILED failed
REPORTED reported.
INSERT LINES OF failed_not_found-salesorder
INTO TABLE failed-salesorder.
ENDMETHOD.
Listing 11.33
Behavior Implementation: Sales Order Delete Action
Example: Copy Action
In our second example, we’ll now look at an action with a simple
parameter and specific result. We want to define a copy action on
sales order item level that creates new items as a copy from an
existing item. As this is just a convenience operation similar to
creating an item and maintaining all the data, we also leverage the
feature to not define specific authorization checks for this action and
just refer to the update authorization. Within the behavior definition of
the sales order item, we add the snippet shown in Listing 11.34.
action ( authorization : update ) Copy
parameter ZD_SalesOrderItemCopyParameter
result [1..*] $self;
Listing 11.34
Behavior Definition: Sales Order Item Copy Action
We add a parameter here to define how many items should be
created as copy, so the resulting cardinality of the action is [1..*]. In
addition, we use the predefined entity result $self to indicate that the
action will return instances of the entity the action is defined on.
The parameter itself is defined as an abstract entity with one simple
integer field, as shown in Listing 11.35.
@VDM.usage.type: [#ACTION_PARAMETER_STRUCTURE]
define abstract entity ZD_SalesOrderItemCopyParameter
{
@EndUserText.label: 'Number of Copies'
NumberOfCopies : abap.int1;
}
Listing 11.35
Action
CDS Abstract Entity: Parameter Data Type of Sales Order Item Copy
In the implementation of the copy action (again created via quick fix
into the local handler class of the sales order item), we read the
incoming item(s) and create the requested number of copied items.
As we’ve defined the result as $self, the signature contains the
complete item structure that needs to be returned as a result (see
Figure 11.10).
Figure 11.10
Handler Signature: Action Result $self
This requires us to read the created items from the transactional
buffer via EML. We could have defined a dedicated result structure
that only contains the item keys, which would have allowed us to
provide the result information already based on the returned data in
MAPPED of the create operation. A code example is provided in
Listing 11.36.
METHOD Copy.
READ ENTITY IN LOCAL MODE ZR_SalesOrderItemTP
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(salesorderitems)
FAILED failed.
DATA create TYPE TABLE FOR CREATE zr_salesordertp\\salesorder\
_item.
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
READ TABLE salesorderitems
ASSIGNING FIELD-SYMBOL(<salesorderitem>)
WITH KEY id COMPONENTS %tky = <key>-%tky.
CHECK sy-subrc = 0.
READ TABLE create ASSIGNING FIELD-SYMBOL(<create>)
WITH KEY cid COMPONENTS %cid_ref = <key>-%cid_ref
SalesOrder = <key>-SalesOrder.
IF sy-subrc <> 0.
APPEND VALUE #( %cid_ref
= <key>-%cid_ref
SalesOrder = <key>-SalesOrder ) TO
create ASSIGNING <create>.
ENDIF.
DO <key>-%param-numberofcopies TIMES.
APPEND VALUE #( product
= <salesorderitem>-product
orderquantity
= <salesorderitem>-orderquantity
orderquantityunit
= <salesorderitem>-orderquantityunit
netamount
= <salesorderitem>-netamount
transactioncurrency = <salesorderitem>-transactioncurrency
) TO <create>-%target.
ENDDO.
MODIFY ENTITY IN LOCAL MODE zr_salesordertp
CREATE BY \_item
FIELDS ( product orderquantity orderquantityunit netamount
transactioncurrency )
AUTO FILL CID WITH create
MAPPED DATA(mapped_local)
FAILED failed
REPORTED reported.
READ ENTITY IN LOCAL MODE ZR_SalesOrderItemTP
FROM CORRESPONDING #( mapped_local-salesorderitem )
RESULT DATA(new_salesorderitems).
LOOP AT new_salesorderitems ASSIGNING
FIELD-SYMBOL(<new_salesorderitem>).
APPEND VALUE #( %cid_ref = <key>-%cid_ref
%tky
= <key>-%tky
%param
= CORRESPONDING #( <new_salesorderitem> ) ) TO
result.
ENDLOOP.
ENDLOOP.
ENDMETHOD.
Listing 11.36
Behavior Implementation: Sales Order Item Copy Action
New instances are created for the copy operation of sales order
items. A much better modeling would be to express this also directly
in the syntax and get result parameters similar to the create
operation. In the next section, you’ll learn how to achieve the same
with factory actions based on a different example.
An example for deep parameters and results will be provided in the
function examples (Section 11.4.11).
Factory Actions
A special type of actions are factory actions, that is, actions that
create new instances. Different from any action that also is capable
of creating new instances, factory actions are special because they
can be used to invoke further operations on the resulting instance
already within the same request via content ID references (see EML
consumption in Section 11.4.3).
Factory actions are restricted to the result $self (which therefore is
omitted) and have a restricted result cardinality of [1]. Other than
these restrictions, everything else is similar to normal actions or
static actions; that is, factory actions also can be static or instancebound and have any kind of parameters. On the implementation
side, the result parameter of the action isn’t RESULT (as for any other
actions) but MAPPED (similar to the create operations) to enable the
mapping from the incoming content IDs to the provided instance
keys and allow content ID reference operations. Consequently, on
the consumer side, factory actions have a %CID in their request
signature.
Let’s add a static factory action for the creation of a sales order
based on a predecessor document, for example, a sales quote, to
our model. To define the action, enhance the behavior definition by
adding the following to the sales order header entity:
static factory action CreateFromSalesQuote deep parameter
ZD_SalesOrderCreateFromQuoteP [1];
The referenced sales quotes are provided via the deep parameter
structure. Depending on the use case, multiple sales quotes could
be combined into one sales order, or single sales quote items can be
taken over and thus split into several sales orders. We restrict
ourselves here to a simple example to take over multiple sales
quotes into one sales order. To achieve a simple table-like
parameter, we need to define a root abstract entity that contains a
composition with cardinality [0..*] and a substructure with one
single field (in our case, the key of the sales quote SalesQuote) (see
Listing 11.37 and Listing 11.38). As we only define an association
technically, we require a dummy field as well as a parent association.
@VDM.usage.type: [#ACTION_PARAMETER_STRUCTURE]
define root abstract entity ZD_SalesOrderCreateFromQuoteP
{
key Dummy:abap.int1;
_SalesQuotes: composition [0..*] of ZD_SalesOrderCreateFromQuotesP;
}
Listing 11.37
CDS Abstract Entity: Deep Type
@VDM.usage.type: [#ACTION_PARAMETER_STRUCTURE]
define abstract entity ZD_SalesOrderCreateFromQuotesP
{
SalesQuote : vbeln;
_DummyAssociation : association to parent
ZD_SalesOrderCreateFromQuoteP;
}
Listing 11.38
CDS Abstract Entity: Deep Type Sub-Entity
Using this parameter definition in the behavior definition with addition
deep requires a related behavior definition of the type where we
define the entities (see Listing 11.39).
abstract;
strict ( 2 );
with hierarchy;
define behavior for ZD_SalesOrderCreateFromQuoteP
{
association _SalesQuotes;
field (suppress) Dummy;
}
scalar entity ZD_SalesOrderCreateFromQuoteSP field SalesQuote;
Listing 11.39
Behavior Definition: Deep Parameter Type
To ensure the dummy field used only for technical reasons isn’t
present in the runtime structures and handler signatures, the field is
then suppressed in the related behavior definition via statement
field ( suppress ). An implementation example isn’t provided here
because this is beyond the scope of this book and adds no further
insights.
Repeatable Actions
Both static and factory actions can be used several times in one call.
This applies in particular to instance-bound factory actions, for
example, to copy the same sales order more than once. Other
instance-bound actions must not be called more than once on the
same instance in one call. The reason for this is that otherwise, the
result of the action, depending on the modeling RESULT but in
particular FAILED and REPORTED, can no longer be correlated to the
individual request, and the consumer therefore can’t derive, for
example, which or whether all related requests for the instance
failed.
However, there are also use cases for actions that can be called
multiple times on the same instance and that aren’t factory actions
(or can’t be modeled as factory actions with the current restrictions).
To allow such calls, the behavior can be modeled accordingly via the
following:
repeatable action <ActionName> ...;
This addition extends the signature of the action with a content ID
(%CID) similar to static or factory actions, which must be supplied by
the consumer. The results of the action then also receive the content
ID, so that the concrete result can be correlated with the request in
any case.
We won’t provide an implementation example as it does not add
further insights.
11.4.11
Functions
Like actions, functions also can be defined in the behavior definition
via key word function or static function. Functions are like readonly actions in that you can’t invoke modifying operations. Thus,
other than actions, functions always need a result, or they won’t
make any sense. Parameters and results can be defined similar to
actions. In addition, the features of external names and the control
fields are available for functions. For functions, the infrastructure
doesn’t provide an exit (yet) for authorization checks or feature
control, so it’s the task of the function implementation to invoke the
necessary authorization checks and to check if a function is
applicable or not in the current state or the given instances.
Static functions again aren’t bound to instances and thus provide
only a handle %CID as a request structure similar to static actions.
The key word repeatable is also available for instance-bound
functions to be able to call them multiple times for an instance as
well.
Because the complete syntax and its derived signatures are identical
to actions, we’ll omit further syntax descriptions here and refer to the
previous section.
For the sake of simplicity, we add two functions, which, from a
functional perspective, would not be needed, just to provide you with
an example for deep result data types. In the behavior definition, we
define the two functions as provided in Listing 11.40.
function GetNumberOfItems deep result [1] ZD_SalesOrderGetNumberofItemsR;
function GetSalesOrder deep result [1] ZD_SalesOrderGetResult;
Listing 11.40
Behavior Definition: Define Functions
The first function just returns the number of items of a sales order.
Here we would like to provide a simple integer type result (see
Listing 11.41) that also requires a result data type definition defined
via behavior definition (see Listing 11.42). The behavior definition is
needed to make the result a scalar type; otherwise, the result would
be a structure with one field: NumberOfItems.
@VDM.usage.type: [#ACTION_RESULT_STRUCTURE]
define root abstract entity ZD_SalesOrderGetNumberOfItemsR
{
NumberOfItems : abap.int8;
}
Listing 11.41
CDS Abstract Entity: Scalar Type
abstract;
strict ( 2 );
with hierarchy;
scalar entity ZD_SalesOrderGetNumberOfItemsR field NumberOfItems;
Listing 11.42
Behavior Definition: Scalar Type
After activation and creation of the related implementation method
(via quick fix), the result signature contains just the SalesOrder key
(as it’s a bound and not static function) and %param typed accordingly
to the parameter definition (see Figure 11.11).
Figure 11.11
Handler Signature: Function Result with Scalar Type
Based on this, we simply implemented the function by reading the
data via read by association. By not using the local mode, the read
authorization is checked automatically, which is sufficient for this
case. To hide the syntax warning that indicates an access within the
behavior pool should use IN LOCAL MODE, we add pragma
##NO_LOCAL_MODE (see Listing 11.43).
METHODS GetNumberOfItems FOR READ
IMPORTING keys FOR FUNCTION SalesOrder~GetNumberOfItems
RESULT result.
…
METHOD GetNumberOfItems.
READ ENTITY ZR_SalesOrderTP ##NO_LOCAL_MODE
BY \_Item
FROM CORRESPONDING #( keys )
RESULT DATA(salesorderitems)
FAILED failed.
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
READ TABLE failed-salesorder TRANSPORTING NO FIELDS
WITH KEY id COMPONENTS %tky = <key>-%tky.
CHECK sy-subrc <> 0.
APPEND VALUE #( %tky
= <key>-%tky
%param = REDUCE #( INIT count = 0
FOR salesorderitem IN salesorderitems
WHERE ( %tky = <key>-%tky )
##PRIMKEY
NEXT count += count )
) TO result.
ENDLOOP.
ENDMETHOD.
Listing 11.43
Behavior Implementation: Function with Scalar Type
In the second example, we return the complete sales order as a
deep structure. To achieve this, we need to define a result structure
that has a table within a table showing that there are no restrictions
regarding the result types. To cover all three main entities, we also
need three abstract entities to define the complete type that can be
compared to the compositional entity structure. The main difference
is that in the child entities, we omit the inherited key fields as these
aren’t needed here and are somehow redundant due to the deep
structure. Listing 11.44 shows the main result structure containing
some fields of the sales order header and a table for the sales order
items (represented as composition with cardinality [0..*]).
@VDM.usage.type: [#ACTION_RESULT_STRUCTURE]
@EndUserText.label: 'Sales Order Get Function Result'
define root abstract entity ZD_SalesOrderGetResult
{
SalesOrder
: vbeln;
SalesOrderType
: auart;
SoldToParty
: kunag;
SalesOrganization
: vkorg;
DistributionChannel : vtweg;
OrganizationDivision : spart;
DeliveryStatus
: lfstk;
_Items
: composition [0..*] of
ZD_SalesOrderGetItemResult;
}
Listing 11.44
CDS Abstract Entity: Sales Order Header Deep Type
Listing 11.45 shows the child structure containing some fields of the
sales order item and a table for the related sales order schedule
lines (represented as a composition with cardinality “to many”).
@VDM.usage.type: [#ACTION_RESULT_STRUCTURE]
@EndUserText.label: 'Sales Order Get Item Function Result'
define abstract entity ZD_SalesOrderGetItemResult
{
SalesOrderItem
: posnr;
Product
: matnr;
@Semantics.quantity.unitOfMeasure : 'OrderQuantityUnit'
OrderQuantity
: kwmeng;
OrderQuantityUnit
: vrkme;
@Semantics.amount.currencyCode : 'TransactionCurrency'
NetAmount
: netwr_ap;
TransactionCurrency : waerk;
_DummyAssociation
: association to parent
ZD_SalesOrderGetResult;
_ScheduleLines
: composition [0..*] of
ZD_SalesOrderGetScheduleLineR;
}
Listing 11.45
CDS Abstract Entity: Sales Order Item Deep Type
Finally, Listing 11.46 shows the child structure containing some fields
of the sales order schedule line.
@VDM.usage.type: [#ACTION_RESULT_STRUCTURE]
@EndUserText.label: 'Sales Order Get SLine Function Result'
define abstract entity ZD_SalesOrderGetScheduleLineR
{
SalesOrderScheduleLine : etenr;
@Semantics.quantity.unitOfMeasure : 'OrderQuantityunit'
OrderQuantity
: wmeng;
OrderQuantityUnit
: vrkme;
DeliveryDate
: edatu;
_DummyAssociation
: association to parent
ZD_SalesOrderGetItemResult;
}
Listing 11.46
CDS Abstract Entity: Sales Order Schedule Line Deep Type
The behavior definition in Listing 11.47 defines the behavior for the
different abstract entities without further specifics.
abstract;
strict ( 2 );
with hierarchy;
define behavior for ZD_SalesOrderGetResult
{
association _Items;
}
define behavior for ZD_SalesOrderGetItemResult
{
association _ScheduleLines;
}
define behavior for ZD_SalesOrderGetScheduleLineR
{
}
Listing 11.47
Behavior Definition: Deep Type
After activation and creation of the related implementation method
(via quick fix), the result signature contains just the SalesOrder key
(as it’s a bound and not static function) and %param typed with the
defined deep result structure (see Figure 11.12).
Figure 11.12
Handler Signature: Function Result with Deep Type
In the implementation, we just read the sales order headers, items,
and schedule lines for the incoming keys and fill the result structure
accordingly. In this case, we read in local mode and invoke the
authorization check on our own in the code (see Listing 11.48).
METHODS GetSalesOrder FOR READ
IMPORTING keys FOR FUNCTION SalesOrder~GetSalesOrder RESULT result.
…
METHOD GetSalesOrder.
READ ENTITY IN LOCAL MODE ZR_SalesOrderTP
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(salesorders)
FAILED failed.
READ ENTITY IN LOCAL MODE ZR_SalesOrderTP
BY \_Item
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(salesorderitems).
READ ENTITY IN LOCAL MODE ZR_SalesOrderItemTP
BY \_ScheduleLine
ALL FIELDS
WITH CORRESPONDING #( salesorderitems )
RESULT DATA(salesorderslines).
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
READ TABLE salesorders ASSIGNING FIELD-SYMBOL(<salesorder>)
WITH KEY id COMPONENTS %tky = <key>-%tky.
IF sy-subrc = 0.
"authority-check
AUTHORITY-CHECK OBJECT 'V_VBAK_AAT'
ID 'ACTVT' FIELD '03'
ID 'AUART' FIELD <salesorder>-SalesOrderType.
IF sy-subrc <> 0.
APPEND VALUE #( %tky = <salesorder>-%tky
%fail-cause = if_abap_behv=>
cause-unauthorized
) TO failed-salesorder.
CONTINUE.
ENDIF.
APPEND VALUE #( SalesOrder = <salesorder>-SalesOrder ) TO result
ASSIGNING FIELD-SYMBOL(<result>).
<result>-%param = CORRESPONDING #( <salesorder> ).
LOOP AT salesorderitems ASSIGNING FIELD-SYMBOL(<salesorderitem>)
USING KEY entity WHERE SalesOrder = <salesorder>-SalesOrder.
APPEND CORRESPONDING #( <salesorderitem> ) TO <result>-%param-_items
ASSIGNING FIELD-SYMBOL(<result_item>).
LOOP AT salesorderslines ASSIGNING FIELD-SYMBOL(<salesordersline>)
USING KEY entity WHERE SalesOrder
= <salesorderitem>-SalesOrder
AND SalesOrderItem = <salesorderitem>-SalesOrderItem.
APPEND CORRESPONDING #( <salesordersline> ) TO
<result_item>-_schedulelines.
ENDLOOP.
ENDLOOP.
ENDIF.
ENDLOOP.
ENDMETHOD.
Listing 11.48
11.4.12
Behavior Implementation: Function with Deep Type
Data Determinations and Validations
In transactional applications, data is often changed, which triggers a
change of other data, change of feature control, or results in
messages about the state of a business object during or after
processing. These side effects come in three types:
Changing dependent data
Checking data with corresponding (error) messages
Changing (dynamic) feature control information
In the following sections, we’ll deal with the first two types. Dynamic
feature control will be introduced in Section 11.4.13.
The implementation of the application logic is the major difference
between a MANAGED and UNMANAGED implementation. In the latter case,
we’ve seen that the modify handler and the saver handler are
implemented by the application completely. The implemented logic
not only needs to take over the modifications into the transactional
buffer and save them to the database later but also to invoke
necessary additional application logic. The additional application
logic can be executed immediately in the modify handler based on
the changes or during the save phase, that is, within FINALIZE and
CHECK_BEFORE_SAVE. In a MANAGED implementation, these handlers—
buffer handling and saving the data—are provided by the ABAP
infrastructure, so the application needs additional hooks and
handlers to implement the additional application logic. This is done
via determinations and validations, a concept probably also known
already from BOPF. Changes to dependent data in the ABAP
RESTful application programming model are implemented in
determinations. Data checks in the ABAP RESTful application
programming model are implemented via validations.
Implement Data Determinations
Let’s consider some simple examples of changing dependent data,
meaning that changes to some data cause other changes to become
necessary. For example, a postal code is entered, and the
corresponding city is determined and placed in the data field
provided for this purpose. As a second example, a shopping basket
is filled, and the total amount is determined accordingly based on the
ordered quantity and price. Of course, further changes can be
triggered by changed data with the business logic, that is, side
effects based on changes occurring out of side effects. For example,
in the shopping basket, the taxes and shipping costs are
recalculated by the changed total amount.
In the ABAP RESTful application programming model, these
determinations are modeled to a certain extent. You define both the
determination itself and the changes that trigger the determination.
This is referred to in the ABAP RESTful application programming
model as the trigger condition. The framework only executes the side
effect and calls the implementing handler method if a corresponding
change has been made that is relevant for executing the
determination. The triggering changes are defined on the related
instance, as in the example of the postal code and city or the
example of the shopping basket and items, where the header data is
changed based on a side effect. The determination also can change
data on other related instances or even other applications. You can
define the type of change to which you want to react. The ABAP
RESTful application programming model provides field triggers and
operation triggers. Field triggers are evaluated if a field is changed,
that is, within a change operation, but also if a field is set directly in a
create operation. Operation triggers are create, update, or delete,
which apply if an instance is created, changed, or deleted.
There are two execution times available for determinations that
define when these are to be invoked and run. The following
execution times are available:
on modify
This execution time is used to execute business logic immediately
after the data has been changed. The result of this side effect is
available to other applications immediately after the operation.
This should be used if essential side effects occur to, for example,
add some defaults that influence further processing.
on save
This execution time is used to execute business logic before
saving the data to ensure that the data is consistent, and all side
effects are invoked properly. This should be the default, that is, for
performance reasons (to avoid running application logic multiple
times if not needed) or to trigger subsequent business processes.
In the save phase, all determinations defined for on save are
executed during FINALIZE. The application can provide the feature
to consumers (e.g., a client working interactively) to run this
business logic earlier with the help of determine actions (explained
later in this section).
As an example, we now add a determination on save to our example
where, based on the order quantity and the price of the product, we
want to calculate the total amount of the sales order item (see
Listing 11.49).
define behavior for ZR_SalesOrderItemTP alias SalesOrderItem
…
{
…
determination CalculateNetAmount on save { field Product, OrderQuantity,
OrderQuantityUnit; }
…
}
Listing 11.49
Behavior Definition: Calculate Total Amount
The trigger conditions here are the field’s product, order quantity,
and quantity unit because if one of these (or all) changes, the total
amount of the item also changes. Note that the field trigger is met
both if the sales order item is created or updated when a value for
the product, order quantity, or order quantity unit is provided.
With the help of the quick fix, we create the handler method and can
implement the logic to calculate the net amount based on product
price and quantity (see Listing 11.50).
METHOD CalculateNetAmount.
"please note that for the sake of simplicity in this example
"we assume to have only one transaction currency, e.g. USD,
"and only one valid quantity unit for a product, e.g. PCE
READ ENTITY IN LOCAL MODE ZR_SalesOrderItemTP
FIELDS ( Product OrderQuantity OrderQuantityUnit )
WITH CORRESPONDING #( keys )
RESULT DATA(salesorderitems).
SELECT product, price, currency
FROM zi_product WITH PRIVILEGED ACCESS
FOR ALL ENTRIES IN @salesorderitems
WHERE product = @salesorderitems-Product
INTO TABLE @DATA(products).
DATA updates TYPE TABLE FOR UPDATE ZR_SalesOrderItemTP.
LOOP AT salesorderitems ASSIGNING FIELD-SYMBOL(<salesorderitem>).
READ TABLE products ASSIGNING FIELD-SYMBOL(<product>)
WITH KEY Product = <salesorderitem>-Product.
CHECK sy-subrc = 0.
APPEND VALUE #( %tky
= <salesorderitem>-%tky
NetAmount
= <salesorderitem>-OrderQuantity *
<product>-Price
TransactionCurrency = <product>-Currency
) TO updates.
ENDLOOP.
MODIFY ENTITY IN LOCAL MODE ZR_SalesOrderItemTP
UPDATE FIELDS ( NetAmount TransactionCurrency ) WITH updates.
ENDMETHOD.
Listing 11.50
Behavior Implementation: Calculate Amount
Further, we also want to provide an example for a determination that
should run immediately. If the net amount on the item level changes,
we also want to calculate the total net amount of the complete order.
To achieve this, we add a determination to our sales order item
definition as depicted in Listing 11.51.
define behavior for ZR_SalesOrderItemTP alias SalesOrderItem
…
{
…
determination CalculateTotalNetAmount on modify
{ field NetAmount, TransactionCurrency; }
…
}
Listing 11.51
Behavior Definition: Calculate Total Amount
The trigger condition here is the field net amount and its currency
because if this is changed, we want to calculate the total net amount.
Note that the trigger conditions are always defined on the entity and
instance where the change occurs, while the side effects could have
an effect on any other entity and instance. In the given case, the
effect is on the sales order header, but the effect could also have
been on other items or even other business objects. With the help of
the quick fix, we again create the handler method and implement it
accordingly (see Listing 11.52).
METHOD CalculateTotalNetAmount.
"please note that for the sake of simplicity in this example
"we assume to have only one transaction currency, e.g., USD
READ ENTITY IN LOCAL MODE ZR_SalesOrderTP BY \_Item
FIELDS ( NetAmount TransactionCurrency )
WITH CORRESPONDING #( keys )
RESULT DATA(salesorderitems).
DATA updates TYPE TABLE FOR UPDATE ZR_SalesOrderTP.
LOOP AT salesorderitems ASSIGNING FIELD-SYMBOL(<salesorderitem>).
READ TABLE updates ASSIGNING FIELD-SYMBOL(<update>)
WITH KEY id COMPONENTS %tky = CORRESPONDING #( <salesorderitem>-%tky ).
IF sy-subrc <> 0.
APPEND VALUE #( %tky = CORRESPONDING #( <salesorderitem>-%tky ) )
TO updates ASSIGNING <update>.
ENDIF.
<update>-TransactionCurrency = <salesorderitem>-TransactionCurrency.
<update>-NetAmount += <salesorderitem>-NetAmount.
ENDLOOP.
MODIFY ENTITY IN LOCAL MODE ZR_SalesOrderTP
UPDATE FIELDS ( netamount transactioncurrency ) WITH updates.
ENDMETHOD.
Listing 11.52
Behavior Implementation: Calculate Total Amount
In the ABAP RESTful application programming model, the defined
determinations are triggered as soon as a related trigger condition is
met. Therefore, the sequence of determinations is functionally not
that important, but it might have some impact on performance. To
achieve a defined sequence of the determinations, the behavior
implementation classes can be specified with a before/after syntax
on the behavior handler class level (Section 11.4.2). As an
alternative, if the sequence is important or even depends on the
operation, different determinations can be implemented within the
same handler so that the proper execution and sequence can be
ensured via coding means.
Like the managed numbering with GUIDs, the ABAP infrastructure
offers an automatic update of system administrative data (creation
date, last changed by, etc.) by modeling this information as semantic
annotations in the CDS model. The ABAP infrastructure uses this
data to fill this information automatically and correctly at runtime.
These fields are already annotated as not modifiable because they
are to be set by the system and not by the user or another
application.
To enable this functionality in your CDS model, add the semantic
annotations highlighted in Listing 11.53 to the corresponding fields in
each relevant entity.
define view ZR_SalesOrderTP
…
{
…
@Semantics.user.createdBy: true
CreatedByUser,
@Semantics.systemDateTime.createdAt: true
CreationDateTime,
@Semantics.user.lastChangedBy: true
LastChangedByUser,
@Semantics.systemDateTime.lastChangedAt: true
LastChangeDateTime,
…
}
Listing 11.53
Data
CDS View Entity: Sales Order with Annotations for System Administrative
Implement Data Validations
While determinations enrich the data entered, it’s also necessary to
check the data before saving it to the database and to prevent
saving if necessary due to data inconsistencies. Usually, business
consistency should be ensured in the transaction data. Simple
checks that prevent incorrect entries are checks for allowed values,
such as fixed values or foreign keys. Even if all individual values are
correct, combining values, exceeding limits, and so on can lead to an
inconsistent state, for example, if the postal code and city are
entered together but don’t match or if the total amount of goods
added to the shopping cart exceeds a certain budget or limit.
Validations can only be defined on save and are invoked during
CHECK_BEFORE_SAVE of the save phase. If a validation fails, this will
lead to the rejection of the save.
Consistency validations are modeled in the ABAP RESTful
application programming model and triggered by changes in the
same way as determinations, which means that appropriate checks
can be carried out automatically when relevant changes are made.
Thus, validations can trigger (similar to determinations) based on
changes of field values or an instance level for created, changed, or
deleted instances.
If a validation fails, an error message usually should be provided in
addition that explains what the issue is and supports the user in
entering consistent data. As these messages are related to the state
of the instances, they are also called state messages, which is
indicated by field %state_area in the REPORTED structure that is to be
filled by the application with a unique (and recommended) readable
identifier. State messages are buffered by the infrastructure and can
be accessed by the applications via the READ ENTITY statement. If the
same validation runs again, for example, because the relevant data
was corrected, the message should disappear, which happens
based on the same state area. Therefore, to ensure the message
buffer is correctly invalidated and cleared, an empty entry with the
state area needs to be provided in the implementation always as the
first operation (see Listing 11.55 later in this section).
In our model, we now add an example validation on each entity by
defining the validation and the related trigger condition (see
Listing 11.54).
define behavior for ZR_SalesOrderTP alias SalesOrder
…
{
…
validation VerifySoldToParty on save { field SoldToParty; }
…
}
define behavior for ZR_SalesOrderItemTP alias SalesOrderItem
…
{
…
validation VerifyProduct on save { field Product; }
…
}
define behavior for ZR_SalesOrderScheduleLineTP alias SalesOrderScheduleLine
…
{
…
validation VerifyQuantityUnit on save { field OrderQuantityUnit; }
…
}
Listing 11.54
Behavior Definition: Validations
With the known quick fix, the related handler methods can be
created, and we can implement the same. The example of the
method definition and implementation for one of the validations can
be found in Listing 11.55. Here we read the sold-to party and check
whether it’s present and whether the value exists. In addition, for any
checked instance, you can see that we add an empty entry into
REPORTED for the related state area of this validation to invalidate
previous messages added from previous executions of this
validation.
METHODS VerifySoldToParty FOR VALIDATE ON SAVE
IMPORTING keys FOR salesorder~verifysoldtoparty.
…
METHOD VerifySoldToParty.
READ ENTITY IN LOCAL MODE ZR_SalesOrderTP
FIELDS ( SoldToParty )
WITH CORRESPONDING #( keys )
RESULT DATA(salesorders).
SELECT customer FROM ZI_Customer WITH PRIVILEGED ACCESS
FOR ALL ENTRIES IN @salesorders
WHERE customer = @salesorders-SoldToParty
INTO TABLE @DATA(customers).
LOOP AT salesorders ASSIGNING FIELD-SYMBOL(<salesorder>).
APPEND VALUE #( %tky
= <salesorder>-%tky
%state_area = 'VERIFYSOLDTO' )
TO reported-salesorder.
IF <salesorder>-SoldToParty IS INITIAL.
APPEND VALUE #( %tky = <salesorder>-%tky ) TO failed-salesorder.
APPEND VALUE #( %tky = <salesorder>-%tky
%msg = NEW zcm_salesorder(
textid = zcm_salesorder=>sold_to_party_initial
severity = if_abap_behv_message=>severity-error
)
%element-SoldToParty = if_abap_behv=>mk-on
%state_area = 'VERIFYSOLDTO' )
TO reported-salesorder.
ELSE.
READ TABLE customers TRANSPORTING NO FIELDS
WITH KEY customer = <salesorder>-SoldToParty.
IF sy-subrc <> 0.
APPEND VALUE #( %tky = <salesorder>-%tky ) TO failed-salesorder.
APPEND VALUE #( %tky = <salesorder>-%tky
%msg = NEW zcm_salesorder(
textid = zcm_salesorder=>
sold_to_party_does_not_exist
sold_to_party = <salesorder>-SoldToParty
severity = if_abap_behv_message
=>severity-error
)
%element-SoldToParty = if_abap_behv=>mk-on
%state_area = 'VERIFYSOLDTO' )
TO reported-salesorder.
ENDIF.
ENDIF.
ENDLOOP.
ENDMETHOD.
Listing 11.55
Behavior Implementation: Validations
Determine Actions
Usually, you want to perform such checks not only as a side effect of
changes but also as explicit requests. This applies especially if
you’re in an interactive scenario or if an application in use wants to
verify the data before starting the complete save phase. Another
reason for this is that some errors can’t be corrected in the business
object itself by changing the data; instead, they need to react to
changes in the environment, such as the system configuration,
master data, or other business objects. The same is true for
determinations if the consumer wants certain logic to be executed
before the save phase runs. In addition, changes in the environment
might be relevant. The ABAP RESTful application programming
model therefore offers the option of performing determinations and
validations on trigger condition always in the context of a determine
action, which corresponds to the explicit requirement to perform a
check.
Determine actions are special actions that provide neither a
parameter nor a result data type. In an UNMANAGED implementation,
these are then implemented like any other action. For a MANAGED
implementation, these determine actions are fully managed; that is,
no implementation is possible. Instead, as stated previously, you can
assign determinations and validations to these determine actions.
The defined triggers of determinations and validations are evaluated
based on the current changes in the transactional buffer unless
addition always is used. In this case, the determinations and
validations marked in this way are always executed on the
corresponding instances for which the determine action is called.
You can assign determinations and validations of the same entity or
of entities down along the compositional hierarchy to a determine
action by using the entity alias to identify the entity.
As an example, we define a determine action on the sales order root
level to explicitly check the overall consistency by assigning all
previously defined validations to it (see Listing 11.56).
determine action Check
{
validation ( always ) VerifySoldToParty;
validation ( always ) SalesOrderItem ~ VerifyProduct;
validation ( always ) SalesOrderScheduleLine ~ VerifyQuantityUnit;
}
Listing 11.56
Behavior Definition: Determine Action
This action can be consumed as any other action of the behavior
definition.
[»] Determine Actions for SAP Fiori Elements Side Effects
To invoke on save determinations as well as validations during the
interaction, the application can define determine actions that are
also supported end to end via SAP Fiori elements as UI side
effects trigger actions (Section 11.9.2).
11.4.13
Dynamic Feature Control
In addition to changes to data and checks as side effects of
changes, the feature control of an instance of the business object
can also change; for example, a change or action sets a status that
no longer allows further changes to an entity or certain fields. On the
other hand, some fields can only be entered and changed if another
field, such as the type of a sales order, has been entered. These
side effects describe application-specific restrictions based on the
concrete data of an instance. In addition to the data of the business
object, other data can also be considered here, such as the
configuration of the application, by means of which data can’t be
changed or shouldn’t be visible, regardless of a specific instance. In
this section, we’ll explain how you can extend your model to define
the appropriate logic for controlling the features of an application.
In addition to the application-specific restrictions based on data,
you’ve already learned about and implemented other restrictions in
previous sections. On one hand, parallel changes by different users
to the same entities and instances should be avoided using the
enqueue concept (refer to Section 11.4.6). On the other hand, not all
users are authorized to do changes, or they may only change certain
data on certain instances and perform selected operations
(Section 11.4.7). The ABAP infrastructure automatically adds
runtime-relevant information to the application-specific control
information. Thus, locks and authorizations don’t require a dedicated
implementation to control the dynamic feature control other than the
application-specific implementation.
The differentiation between preventing changes based on missing
authorization or via feature control and whether this applies for a
dedicated instance or independent of the instances (i.e., global) are
provider-specific implementation details: the consumer usually is
only interested in the overall information if, for example, an action for
a dedicated instance can be invoked or not. Therefore, the consumer
access via EML offers a statement that returns the merged
information between global and instance authorization and feature
control, as shown in Listing 11.57.
GET PERMISSIONS [ONLY { GLOBAL |
GLOBAL FEATURES |
GLOBAL AUTHORIZATION |
INSTANCE |
INSTANCE FEATURES |
INSTANCE AUTHORIZATION |
FEATURES |
AUTHORIZATION
}] …
Listing 11.57
Behavior Consumption: GET PERMISSION
With the provided additions, the consumer can also restrict the
information request to only global, only instance, only authorization,
or only feature control information.
Feature control covers the disabling of operations (operation control)
as well as actions (action control) and the control of fields to set
these dynamically to mandatory, editable, or read only (field control).
The behavior modeling as well as the implementation handlers are
similar to the authorization, but besides operations, control is also
possible on field level. In addition, feature control can be defined
globally or on instance level. Although for static operations (e.g.,
create or static actions), only global feature control is available and
reasonable; for any instance operation (e.g., create by association,
update, delete, and actions) as well as field settings, both global and
instance feature control can be defined. Global feature control can
be used if, for example, an action isn’t available due to configuration
settings, and thus no instance information is needed to set this
action to disabled.
The static operations can be enhanced with syntax features ( global
) in the behavior definition:
create ( features : global );
static [factory] action ( features : global ) … ;
The instance operations can be enhanced with syntax features (
instance ) in the behavior definition (we stick to instance-related
feature control here while the global feature control can also be
defined for instance operations):
association … { create ( features : instance ); }
update ( features : instance );
delete ( features : instance );
[factory | repeatable ] action ( features : instance ) … ;
For determine actions, no feature control can be defined. These are
always allowed by definition.
On field level, the syntax looks the same, whereas it’s not allowed
and not reasonable to define dynamic field control if a field is
statically defined as readonly or readonly:update:
field ( features : instance ) …;
In our example, we now add global feature control to our noninstance-related operations, as shown in Listing 11.58.
create ( features : global );
…
static factory action ( features : global ) CreateFromQuote
deep parameter ZD_SalesOrderCreateFromQuoteP [1];
Listing 11.58
Behavior Definition: Global Feature Control
Adding and activating the behavior definition reveals a missing
implementation for the global feature control handler for these
operations. Adding these via quick fix, we get them in one combined
handler method, and for both operations, a control indicator
becomes part of the request (see Figure 11.13) as well as the result
structure (see Figure 11.14) similarly.
Figure 11.13
Handler Signature: Global Feature Control Request
Figure 11.14
Handler Signature: Global Feature Control Result
Because both are operations that create a sales order, we check in
the implementation if this is allowed in the current system; if not, we
just return the identical request indicators as the semantics is
disabled if set. This ensures that if no implementation is provided,
the operations are usable and not disabled. We don’t provide real
functionality here in our example, as shown in Listing 11.59.
METHODS get_global_features FOR GLOBAL FEATURES
IMPORTING REQUEST requested_features FOR salesorder RESULT result.
…
METHOD get_global_features.
DATA(salesordercreationswitchedoff) = abap_false.
"read system configuration to check if creation is allowed
"...
IF salesordercreationswitchedoff = abap_true.
result = CORRESPONDING #( requested_features ).
ENDIF.
ENDMETHOD.
Listing 11.59
Behavior Implementation: Global Feature Control
Regarding instance operation control, we enhance our example to
control all instance operations accordingly. Here the definition of
feature control isn’t available for operations defined as internal, as
by definition, these operations can only be called internally, that is, IN
LOCAL MODE, and aren’t available for external consumers. Thus, this
access is always privileged and not invoking authorization or feature
control checks. Listing 11.60 shows our enhanced behavior
definition.
…
define behavior for ZR_SalesOrderTP alias SalesOrder
…
{
…
update ( features : instance );
…
association _Item { create ( features : instance ); }
…
action ( features : instance ) Delete;
…
}
define behavior for ZR_SalesOrderItemTP alias SalesOrderItem
…
{
update ( features : instance );
delete ( features : instance );
…
association _ScheduleLine { create ( features : instance ); }
unmanaged association _RequestedScheduleLine { create ( authorization : update,
features : instance ); }
unmanaged association _ConfirmedScheduleLine { create ( authorization : update,
features : instance ); }
…
action ( authorization : update, features : instance ) Copy
parameter ZD_SalesOrderItemCopyParameter result [1..*] $self;
…
}
define behavior for ZR_SalesOrderScheduleLineTP alias SalesOrderScheduleLine
…
{
update ( features : instance );
delete ( features : instance );
…
}
Listing 11.60
Behavior Definition: Instance Feature Control
We create the related implementation methods via quick fix for every
entity. On the element level, the related control indicators are part of
the request signature of the method (see Figure 11.15) and have no
instance relation. In other words, the request of features is always
homogenous for all requested instances via the key parameter
(%tky). In the result signature of the method (see Figure 11.16), the
instance relation is obviously given as the feature control value might
vary for every instance.
Figure 11.15
Handler Signature: Instance Feature Control Request
Figure 11.16
Handler Signature: Instance Feature Control Result
To keep it simple in our example implementation, we just deactivate
all operations after the delivery status is set to Fully Delivered. To
achieve this, we read the delivery status, and if it’s equal to 'C', we
set all control flags that are requested (see Listing 11.61 for the sales
order item). The implementations on the header and schedule line
level look similar.
METHODS get_instance_features FOR INSTANCE FEATURES
IMPORTING keys REQUEST requested_features FOR SalesOrderItem RESULT result.
…
METHOD get_instance_features.
READ ENTITY IN LOCAL MODE ZR_SalesOrderItemTP BY \_SalesOrder
FIELDS ( DeliveryStatus )
WITH CORRESPONDING #( keys )
RESULT DATA(salesorders)
FAILED failed.
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
READ TABLE salesorders ASSIGNING FIELD-SYMBOL(<salesorder>)
WITH KEY id COMPONENTS %tky = CORRESPONDING #( <key>-%tky ).
CHECK sy-subrc = 0 AND <salesorder>-DeliveryStatus = 'C'.
APPEND VALUE #( %tky
= <key>-%tky
%features = CORRESPONDING #( requested_features ) ) TO result.
ENDLOOP.
ENDMETHOD.
Listing 11.61
11.4.14
Behavior Implementation: Instance Feature Control
Mappings
Because CDS views are used to model the business object, you
might want to model the business object differently from the
underlying database tables rather than just reading it one-to-one
from a database table. This is the case especially in brownfield or
bluefield implementations. In principle, CDS modeling allows you to
use various SQL functions, such as joins, unions, expressions, and
so on. If a bijective mapping is possible when writing to the
database, the use of these functions would be conceivable. In an
UNMANAGED implementation, this is no big deal, as usually your internal
transactional buffer or database updates reside in the legacy logic,
and you can implement the mapping within your handlers
accordingly. To ease this task, the behavior definition allows you to
define a reusable mapping that can be leveraged directly in the
ABAP CORRESPONDING operator. With this, you can define a mapping,
for example, for the entities but also for action and function
parameters, and action and function results accordingly. An example
mapping is shown in Listing 11.62.
mapping for zsalesorder corresponding [except …]
{
DistributionChannel = distributionchannel;
…
}
Listing 11.62
Behavior Definition: Mapping
In our case, the mapping definition would not be needed because
we’ve created a new database table with identical field names. As
stated, the mapping can be used directly by the ABAP CORRESPONDING
operator where the correct mapping is chosen automatically based
on the source and target data types (see Listing 11.63).
<target_type> = CORRESPONDING #( <behavior_type> MAPPING FROM ENTITY [USING CONTROL]
).
<behavior_type> = CORRESPONDING #( <source_type> MAPPING TO ENTITY
[CHANGING CONTROL] ).
Listing 11.63 Behavior Implementation: Usage of Mapping via ABAP
CORRESPONDING Operator
In the MANAGED implementation, the mapping comes in quite handy. If
the fields on the database only differ in their field names (e.g., due to
aliasing in the related CDS views), a mapping for the defined
database table (persistent table) can be defined that is
automatically considered by the ABAP infrastructure. If this isn’t
sufficient, the database updates (via direct database modifications or
via update task function) can be implemented by the application itself
with addition UNMANAGED SAVE. Here, method SAVE_MODIFIED in the
saver handler needs to be implemented where the necessary logic
and mapping can be applied. The infrastructure provides the
changes on the entity level already distributed into parameters
create, update, and delete (see Figure 11.17). For delete only, the
keys are provided, whereas for create and update, besides the data
fields and their values, the control indicators are also provided.
Figure 11.17
Handler Signature: Unmanaged or Additional Save
The same method/signature is provided for ADDITIONAL SAVE where
the infrastructure still manages the save of the business object into
the defined persistence, and you can execute and persist additional
things such as writing change documents and so on.
[+] Mapping Applying CORRESPONDING Logic
When defining the mapping, we recommend always using the
addition CORRESPONDING to ensure that further added fields, for
example, extension fields, aren’t missed during future
enhancements of the application.
11.4.15
Calculated Fields
In addition to the pure persisted entities and fields, in the
transactional model often more helper fields are needed to retain
some processing data within the transaction. Such fields can be
defined by simply adding constants, expressions, or calculations in
the transactional CDS model. However, when introducing these
fields, you must ensure that the result set of the CDS view (i.e., the
number of rows in the result) isn’t changed. Therefore, these fields
don’t require a mapping to the persistence (Section 11.4.14) but
need to be handled in the transactional buffer and application logic.
These fields are often read only, but this isn’t a necessary
precondition as a change of such fields from the consumer side
might lead to side effects changing data that is finally persisted.
Using calculated fields can be useful, for example, if the total amount
of the sales order is calculated directly in the database via an
aggregate function using the associated sales order items to
determine the values efficiently when reading (especially for many
sales orders). Another example is the transformation of a field value
to provide your consumer with information that is easier to consume.
Listing 11.64 shows an example in which we expose a special value
as a separate DeliveryIsCompleted field derived from the status of
the sales order with several values. In addition, we’ve set the field in
the behavior definition to read only.
define view ZR_SalesOrderTP
…
{
…
DeliveryStatus,
@Semantics.booleanIndicator: true
cast ( case DeliveryStatus
when 'C' then 'X' else ' ' end
as compl_ind preserving type ) as DeliveryIsCompleted,
…
}
Listing 11.64
CDS View Entity: Sales Order Header with Calculated Field
Note that for the fields calculated in the database, the data doesn’t
necessarily come directly from the database during transactional
access. Therefore, the calculation can’t take place there exclusively.
The sales order items may change during the transaction. In this
case, too, the correct value for the total amount must be calculated
and returned for transactional access. This also applies in our
example of field DeliveryIsCompleted. Therefore, you must still
provide a determination that performs the same calculation in ABAP
as in the CDS view. An example is provided in the code samples.
Despite the redundant implementation, this procedure makes sense
in many cases because the ABAP calculation is only performed for
transactional access, whereas the CDS calculation is performed in
the database for pure reading of the data and also is accessible for
analytical applications.
[»] Data Calculated Exclusively in ABAP
If you have fields that you can only calculate in ABAP, this
currently isn’t possible at the transactional object model level. You
can only add and implement such fields in the transactional
projection model. Details can be found in Section 11.5.5. It’s also
currently not possible to define and implement complete entities
on the transactional object model layer that don’t provide the data
via SQL (i.e., CDS custom entities).
11.4.16
Prechecks
You’ve already learned that an operation is rejected automatically by
the infrastructure if the operation is disabled via authorization check
or feature control check or due to concurrent editing and lock
conflicts. These rejections are independent of the provided data in
the operation but are based on the current data and state of the
instances. In general, this is fine, and usually any changes are taken
over into the transactional buffer and verified before being saved to
the database. If the data isn’t appropriate, saving is prevented via
validations. But there might also be reasons to reject a request early,
that is, to not even take over the data into the transactional buffer.
One reason could be the change of authorization-relevant fields, for
example, changing the sales organization of the sales order. Here
you might want to prevent the user from changing to a sales
organization where no authorization is granted for the target sales
organization. Another example is a violation to defined cardinalities
of associations; for example, if only one schedule line would be
allowed, you might want to prevent the creation of further ones if one
already exists.
While again in an UNMANAGED implementation, this additional logic can
be implemented within the modify handler, there is no such option for
MANAGED implementations. Therefore, the create, update, and delete
operations can be enhanced by a precheck to handle such use
cases and reject changes early. In addition, for actions and even
static actions, a precheck can be defined. Here the use case is to
check the incoming parameters. In this case, even in a MANAGED
implementation, the logic could be put into the action
implementation, but we recommend leveraging this ABAP
infrastructure feature and implementing such checks in the precheck
handler.
In our example, we implement a precheck for the mentioned use
case to check if the change of a sales order type is allowed, that is, if
the user has authorization for the target sales order type. We
enhance the behavior definition (see Listing 11.65) and create the
implementation handler via the quick fix.
define behavior for ZR_SalesOrderTP alias SalesOrder
…
{
…
update ( features : instance, precheck );
…
}
Listing 11.65
Behavior Definition: Precheck
In the implementation, we check whether the interesting field has
been changed and verify the new value accordingly (see
Listing 11.66). In case of a wrong or not allowed value, we reject the
operation and issue an explaining message.
METHODS precheck_update FOR PRECHECK
IMPORTING entities FOR UPDATE salesorder.
…
METHOD precheck_update.
LOOP AT entities ASSIGNING FIELD-SYMBOL(<salesorder>)
WHERE %control-SalesOrderType = if_abap_behv=>mk-on.
AUTHORITY-CHECK OBJECT 'V_VBAK_AAT'
ID 'ACTVT' FIELD '02'
ID 'AUART' FIELD <salesorder>-SalesOrderType.
IF sy-subrc <> 0.
APPEND VALUE #( %cid
= <salesorder>-%cid_ref
%tky
= <salesorder>-%tky
%fail-cause = if_abap_behv=>cause-unspecific
%update
= if_abap_behv=>mk-on
) TO failed-salesorder.
APPEND VALUE #( %tky = <salesorder>-%tky
%msg = NEW zcm_salesorder(
textid
= zcm_salesorder=>no_auth_update_type
salesorder = <salesorder>-SalesOrder
severity
= if_abap_behv_message=>severity-error
)
) TO reported-salesorder.
ENDIF.
ENDLOOP.
ENDMETHOD.
Listing 11.66
11.4.17
Behavior Implementation: Precheck
HTTP ETags
As indicated by the name ABAP RESTful application programming
model, one major consumption channel of our application is
established via OData. This implies that the interaction between
consumer (e.g., web browser) and server (in our case, ABAP server)
is stateless. This means as soon as data modifications are sent via
an OData request, these are processed and immediately stored.
Technically, every OData change set is one ABAP logical unit of
work (LUW) and ends each with a separate COMMIT WORK or ROLLBACK
WORK. Consuming such an application as such isn’t based on
exclusive locks as known within the ABAP application server, but
only the request processing can prevent parallel modifications via
the standard ABAP enqueue or database locks (if sufficient). To
enable the consumer to ensure a reliable modification based on the
previously read data, an optimistic concurrency control can be
adapted via HTTP ETag. The ABAP RESTful application
programming model supports this approach out of the box by
defining a dedicated field of the application as an ETag field in the
behavior definition on the entity level where the ABAP RESTful
application programming model again follows the master and
dependent by concept:
etag master <field_name>
The ABAP RESTful application programming model requires one
field to be named to act as an ETag field, which is consistently
updated/changed if the entity instance is updated. In most cases,
this is a last-changed time stamp that is also the clear
recommendation (as these are also supported out of the box for the
implementation type MANAGED; see Section 11.4.12). For legacy
applications that, for example, persist separate fields as
LastChangeDate and LastChangeTime, the same can be defined as
calculated fields via CDS expressions, as shown in Listing 11.67.
cast( dats_tims_to_tstmp( LastChangeDate, LastChangeTime, 'UTC', $session.client,
'NULL' )
as LastChangeDateTime
Listing 11.67
CDS View Entity: Last Changed Date Time Calculation
A proper field for ETag usage might also be a change indicator or
version that is increased with every change. There might also be
other options to calculate an ETag, for example, by concatenating
the fields, but if no appropriate field is available, we recommend
introducing a proper time stamp field.
On the child entity level, you may similarly name a dedicated ETag
field of this child entity, and we recommend following this approach.
If no appropriate local field is present, you can assign the same ETag
field of an ancestor entity via statement dependent by:
etag dependent by <association_to_etag_master>
The concrete usage of the ETag will then be described in
Section 11.7.1 and Section 11.8.1.
In our simple example, we have a time stamp for the last change
information on every entity, so we don’t make use of the dependent
ETag, but use the existing time stamp as ETag master (see
Listing 11.68).
define behavior for ZR_SalesOrderTP alias SalesOrder
…
etag master LastChangeDateTime
{
…
}
define behavior for ZR_SalesOrderItemTP alias SalesOrderItem
…
etag master LastChangeDateTime
{
…
}
define behavior for ZR_SalesOrderScheduleLineTP alias SalesOrderScheduleLine
…
etag master LastChangeDateTime
{
…
}
Listing 11.68
11.4.18
Behavior Definition: ETag
Draft
We follow the goal to expose new applications via OData and SAP
Fiori apps, and, as explained in the previous section, the interaction
between the UI (web browser) and backend (in our case the ABAP
server) is stateless. Consuming our application via these services
has the impact for the end user that concurrent changes of data are
prevented via optimistic concurrency control, and any kind of side
effects occur only after the data has been sent to the backend and
saved.
This behavior occurs because the complete data input is done on the
frontend side with data read from the backend; then all data is sent,
processed, and saved/committed by the backend only during the
save phase with one request. Thus, only then the application logic
implemented on the backend side is processed. For simple
applications with very few input fields or only actions, this might be
sufficient. Further, some simple application logic can be implemented
on the UI side (rich client), such as simple calculations or validations
of entered data against the values provided via a related value help.
In addition, functional backend logic could be invoked via OData
functions, but all these enhancements and frontend-based
implementations require more and more internal data to be retrieved
to the frontend for proper processing, leading to certain duplication of
application logic.
Especially in the ABAP environment, a large amount of existing
backend logic is usually available and should be leveraged. It’s
expected by the end user that on data entry and each interaction,
side effects are invoked, and related data is updated immediately. In
many cases, the users also expect feedback regarding the
consistency of the data up front before persisting the data. If editing
takes longer for more complex applications or business objects, end
users also expect exclusive editing of a business object instance to
avoid lots of changes that can’t be saved finally due to concurrent
changes from other users. All this requires a different approach, and
the concept introduced to tackle this is the draft concept. With the
draft concept, the data entry is immediately sent to the server, which
allows the application logic to be run immediately on the server side,
especially application logic relevant for the interaction phase. The
entered data isn’t stored in the application tables but in a separate
persistence called draft tables. This is also a relational persistence
derived from the transaction object model and its CDS entities, but,
theoretically, the data could also have been persisted in a completely
different way.
With this, the REST paradigm can be followed, existing application
logic can be leveraged, and there is no need to reimplement this
logic on the client side. The latter is often not possible because a lot
of data would need to be provided to the client, including information
and functionality from other connected applications.
[+] Draft Concept on the UI Level
The full power of the draft concept can be seen mainly in the
related UI orchestration and functionality that we’ll describe again
in detail when exposing our ABAP RESTful application
programming model application for the development of an SAP
Fiori elements UI in Section 11.9.2.
In addition to following the REST principles, the draft concept also
provides further advantages and features out of the box. Following
REST, by definition, there is no timeout as the communication is
stateless, even if editing is interrupted for some reason. As the data
is sent and stored immediately, data loss is prevented. In addition,
the draft concept also allows you to define an exclusive lock to
prevent concurrent changes. While for an existing business object
instance, the existence of the draft is already the indication that a
user is working on this business object instance, in the ABAP
platform, a standard enqueue is also used. Different from the known
enqueue locks, the lock isn’t bound to the session and user, but
rather to the draft instance, and thus lives longer than the classical
ABAP session. The enqueue object and argument used are the
same as for the editing of the active business object instance without
draft. This allows the interoperability of editing via draft or directly, for
example, due to legacy applications (classic UIs) or other input
channels (e.g., business-to-business [B2B] or application-toapplication [A2A] integration).
A draft instance is always assigned to exactly one user and can be
displayed and edited only by the owning user (similar to editing via
an ABAP session). All other users can only access the active
instance, but as a feature, they are aware that another user is
currently working on this instance because a draft exists (which is
visible information, especially in the SAP Fiori elements UI; see
Section 11.9.2). So other users still see the unchanged original data,
and only when the draft is finally saved will the changes become
visible to all users and be incorporated into the subsequent
processes, such as visible in analytical applications or triggering
further process steps.
As the draft is more or less just a persistence, while the complete
application logic and interaction runtime and orchestration doesn’t
change, only the model needs to be enhanced with certain syntax
elements for a MANAGED application to enable the draft. The draft is
enabled simply by adding with draft; into the behavior definition.
Checks guide through further required adaptions, such as addition
with draft also for the related associations, the definition of the draft
database tables to store the draft, the definition of the draft actions,
and the definition of a total etag.
As for every entity, a draft table needs to be provided and declared in
the behavior definition:
draft table <draft_table_name>
We propose to follow the name of the standard application tables
followed by the suffix “D” for draft tables. In our example, we’ve
defined the database table for the sales order header as ZSALEDORDER
and the related draft table as ZSALESORDERD. By adding this statement
and a table name, a quick fix is available to directly create the draft
table with the proper database schema. The draft table is derived
from the related CDS entity and needs to contain all fields of the
CDS entity as well as an admin include of type
sych_bdl_draft_admin_inc that contains some technical fields
needed by the infrastructure.
[+] Usage of %tky
The introduction of the draft feature will enhance the data
structures of the handler implementations with key field %is_draft
to distinguish the active instance and its draft instance. Ensuring
the enhancement with draft usually doesn’t require any code
changes, but it’s strongly recommended that you always use the
%tky component group where available when accessing the
instances as this component group is enhanced accordingly. With
this, the implemented application logic also works properly when
introducing the draft feature at a later point in time.
In addition, the draft introduction requires a couple of actions that are
relevant for the draft orchestration. The creation of a draft instance
works similarly via a standard create or factory action. At runtime,
draft and active instances are distinguished by a new element in the
key definition named %IS_DRAFT. The same can be set in the create
operations and is inferred also in the signature of factory actions as
an additional parameter to allow the creation of both draft and active
instances.
The draft actions are implicitly defined and have a fixed reserved
name, but in strict mode, these need to be stated explicitly. The
following draft actions, which we’ll explain next, need to be added as
such on the root entity, as shown in Listing 11.69.
draft action Activate optimized;
draft action Edit;
draft action Resume;
draft action Discard;
draft determine action Prepare;
Listing 11.69
Behavior Definition: Draft Actions
All these draft actions are managed by the ABAP infrastructure, so
no implementation is needed nor possible. Draft determine action
Prepare is a special determine action that at runtime consists of the
assigned determinations or validations (Section 11.4.12).
To move the draft data into the active instances, the Activate action
is available. Here, the changed data in the draft is handed over as
one MODIFY call to the active implementation (be it MANAGED or
UNMANAGED). In the UI orchestration the draft determine action Prepare
is always invoked to check the draft data up front. The optimized
addition ensures that all determinations and validations assigned to
the Prepare action aren’t triggered again for the active instance
during saved (unless there are subsequent changes that trigger
these again).
The Edit action is allowed on active instances only and creates a
draft for an existing instance. When the Edit action is invoked, the
complete composition hierarchy of a root instance/lock master
instance is always copied to the draft.
The Resume action is called if an exclusive lock has expired. You
might have asked yourself what happens if a user leaves and still
has an open draft and especially an exclusive lock. In fact, the
exclusive lock is present only for a defined period as it allows you to
prevent concurrent editing but isn’t essential for the draft functionality
as such. The default expiration of the exclusive lock is usually 30
minutes. After this time of inactivity, the exclusive lock is removed,
and the draft moves into optimistic concurrency control. Thus, if now
another user or process changes the active instance, there will be no
lock conflict, and changes can be made. On the other hand, if the
user resumes editing, the lock can be acquired, and if successful
and no changes have been made concurrently, the user will be able
to continue without even knowing that the exclusive lock was gone in
between. The transition from the optimistic concurrency control state
to the exclusive lock state is done based on the following:
1. The exclusive lock can be acquired.
2. The active business object instance hasn’t been changed in the
meantime (by comparing the total etag).
3. Further application-specific checks (implemented via the syntax
with additional implementation of the Resume action) for a
changed environment (e.g., configuration, master data or
authorizations, or even derived updates of the draft data) are
invoked successfully.
For the detection, if the active business object instance has been
changed, the before-mentioned total etag is used. The total etag is
like an HTTP ETag but reflects the complete compositional hierarchy.
While the ETag on the sales order header level may only reflect
changes on the sales order header, but might not change if sales
order items are added, changed, or removed, the total etag needs
to be changed also in these cases. Often, the usage of the same
field is appropriate because due to side effects, changes on lowerlevel entities usually also lead to changes on the root entity.
From a syntax perspective, the total etag is added to the lock
master:
lock master [unmanaged] total etag <etag_field>
To include your own further checks as mentioned before, as a third
step, you can enhance the syntax of action Resume via with
additional implementation. In this case, you can create and
implement a corresponding handler class.
Draft action Discard is called to discard a draft business object
instance, that is, to delete it from the draft tables. It can be called
explicitly by the user to remove a draft that won’t be activated as
such. It’s called implicitly by the ABAP infrastructure draft
orchestration after successful draft activation. Finally, it’s also called
by the draft lifecycle handler, which removes draft instances that
haven’t been touched for a certain period (e.g., the removal of the
exclusive enqueue lock). The default expiration of a draft is 28 days.
[»] Draft Enqueue and Draft Expiration Periods
The default expiration for enqueue and the discard of the draft are
predefined but can also be defined specifically on the object level
via CDS annotations:
@ObjectModel.lifecycle.enqueue.expiryInterval: 'PT15M'
@ObjectModel.lifecycle.draft.expiryInterval: 'P28D'
The value of the annotation must be done according to the
duration representation defined in ISO 8601. We don’t recommend
changing these default settings as usually this also isn’t needed.
The syntax addition with additional implementation of the draft
actions not only can be added to draft action Resume but also to other
draft actions Edit, Activate, and Discard. The main usage is the
integration of applications that weren’t implemented with the ABAP
RESTful application programming model and that support the draft
concept and orchestration. As this is a rather special scenario and
should be avoided whenever possible by moving these applications
into the ABAP RESTful application programming model, we just want
to mention it here without providing a real code example or use case.
Authorization and feature control for draft actions can only be
specific for draft action Edit. Here the same applies as for any other
action, and you can check whether a user is authorized to change
the business object instance (usually the update authorization is
invoked here) and whether editing is even allowed, for example, due
to the status of the business object instance. For all other draft
actions, these additions can’t be specified. While Resume is an
internal action, Discard can’t be deactivated because it always needs
to be possible to discard the draft. Draft determine action Prepare
follows the rules for any other determine action that also can’t be
deactivated via feature control. Finally, draft action Activate might do
some further authorization checks, but because the data is taken
and the active implementation is invoked as one MODIFY statement,
all the related checks regarding authorization and feature control are
implemented and invoked automatically based on the MODIFY
implementation.
As the draft itself is always a MANAGED implementation, the statement
regarding the application logic implemented via determinations and
validations given in the beginning of this section holds true for
implementation type MANAGED only. For implementation type
UNMANAGED, the draft modeling with draft tables, draft action, and so
on is identical, but the application logic is implemented in modify
handlers and not in determinations and validations. Thus, this logic
won’t run for draft instances, but only during draft action Activate.
The pure draft enablement of such an application thus will enable
exclusive editing as well as data loss prevention, but no interactive
application logic because the complete logic is only invoked during
the activation step. All application logic implemented in the dedicated
handlers, such as authorization or feature control, will work out of the
box. Nevertheless, the necessary application logic for the draft can
be added step-by-step into determinations and validation, which also
gives a good starting point for the refactoring of such a legacy
application.
You now enhance the application model to enable it for draft and
thus add the relevant syntax enhancements in the behavior definition
(see Listing 11.70). Changes are marked bold. Here we also add all
our determinations and validation on save to draft determine action
Prepare.
managed implementation in class zbp_r_salesordertp unique;
strict;
with draft;
define behavior for ZR_SalesOrderTP alias SalesOrder
persistent table zsalesorder
draft table zsalesorderd
with additional save
lock master unmanaged total etag LastChangeDateTime
…
{
…
association _Item { create ( features : instance ); with draft; }
…
draft action Activate optimized;
draft action ( features : instance ) Edit;
draft action Resume;
draft action Discard;
draft determine action Prepare
{
determination SalesOrderItem~CalculateNetAmount;
validation VerifySoldToParty;
validation SalesOrderItem~VerifyProduct;
validation SalesOrderScheduleLine~VerifyQuantityUnit;
}
…
}
define behavior for ZR_SalesOrderItemTP alias SalesOrderItem
persistent table zsalesorderitem
draft table zsalesorderitemd
…
{
…
association _SalesOrder { with draft; }
association _ScheduleLine { create ( features : instance ); with draft; }
unmanaged association _RequestedScheduleLine
{ create ( authorization : update, features : instance ); with draft; }
unmanaged association _ConfirmedScheduleLine
{ create ( authorization : update, features : instance ); with draft; }
…
}
define behavior for ZR_SalesOrderScheduleLineTP alias SalesOrderScheduleLine
persistent table zsalesordersline
draft table zsalesorderslind
…
{
…
association _SalesOrder { with draft; }
association _SalesOrderItem { with draft; }
…
}
Listing 11.70
Behavior Definition: Draft Enablement
With the help of the quick fix, we now create the three necessary
draft tables. In the provided creation popup (see Figure 11.18), all
necessary information is predefined, and we can directly complete
the creation by clicking Next > and Finish.
Figure 11.18
ADT: Create Draft Table
The create draft database table (see Listing 11.71) can then be
activated immediately. The same is repeated to create the draft
tables for the other two entities.
@EndUserText.label : 'Draft table for entity ZR_SALESORDERTP'
@AbapCatalog.enhancement.category : #EXTENSIBLE_ANY
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zsalesorderd {
key mandt
: mandt not null;
key salesorder
: vbeln not null;
salesordertype
: auart;
salesorganization
: vkorg;
soldtoparty
: kunag;
distributionchannel : vtweg;
organizationdivision : spart;
@Semantics.amount.currencyCode : 'zsalesorderd.transactioncurrency'
netamount
: netwr_ap;
transactioncurrency : waerk;
deliverystatus
: lfstk;
deliveryiscompleted : compl_ind;
deletionindicator
: loekz;
@AbapCatalog.anonymizedWhenDelivered : true
createdbyuser
: ernam;
creationdatetime
: creation_date_time;
@AbapCatalog.anonymizedWhenDelivered : true
lastchangedbyuser
: aename;
lastchangedatetime
: last_changed_date_time;
"%admin"
: include sych_bdl_draft_admin_inc;
}
Listing 11.71
Database Table: Draft Table
To complete the draft handling, we enhance the authorization
handler to also cover draft action Edit accordingly, as shown in
Listing 11.72. The instance feature control implementation doesn’t
need to be enhanced in our simplified example because we
deactivate every operation based on a single field value.
…
LOOP AT salesorders ASSIGNING FIELD-SYMBOL(<salesorder>).
IF requested_authorizations-%update = if_abap_behv=>mk-on
OR requested_authorizations-%action-Edit = if_abap_behv=>mk-on.
…
ENDIF.
ENDLOOP.
…
Listing 11.72
Behavior Implementation: Draft Authorization Implementation
Now the draft enablement is complete, and we could leverage the
draft functionality via EML access in our implementation.
11.4.19
Side Effects
Side effects are essential because they allow relevant data to be
read as needed, and the UI doesn’t have to be completely updated
after each change. Side effects are especially important for draft
(Section 11.4.18) and for SAP Fiori elements (Section 11.9.2).
The following side effects can occur through changes (i.e., direct
change operations) or actions and determine actions in particular:
Changes to data, for example:
Change fields of the same entity and instance.
Change fields of other associated instances of the same or
other entities.
Create (associated) instances of the same or other entities.
Delete (associated) instances of the same or other entities.
Change the target instance of an association (e.g., foreign key
association).
Feature control changes, for example:
Fields become mandatory, editable, or read only.
Operations such as create by association, update, or delete
become enabled or disabled.
Actions become enabled or disabled.
Changes of (state) messages, for example:
Existing (state) messages are no longer relevant and disappear.
New (state) messages occur and are appearing.
In a MANAGED implementation, changes to data as side effects are
usually done in determinations. The changes of the state messages
take place in validations. The feature control information changes
implicitly and is only recalculated if feature control is explicitly
requested.
Certain side effects don’t need to be explicitly defined but result from
the operation itself. For example, it’s implicitly given that a successful
create operation has created a new instance (whose key is also
always returned as a result) or that a successful delete operation has
removed the corresponding instance. The same applies to foreign
key relationships if the foreign key is changed so that the association
points to another instance.
The previously mentioned possible side effects are addressed in the
syntax via affects. Side effects can be defined for changes to fields
or entities, as well as for actions and the special determination
actions. For the latter, several trigger conditions can also be defined
that trigger the side effects, as shown in Listing 11.73.
side effects {
field <Feldname>
affects $self,
entity <Assoziationspfad>,
field <Feldname>,
field <Assoziationspfad>.<Feldname>,
permissions ( create <Assoziation>,
update,
delete,
field <Feldname>,
field Assoziationspfad>.<Feldname>,
action <Aktionsname> ),
messages;
$self
affects …;
action <Aktionsname>
affects …;
determine action <Actionname>
executed on
$self,
entity <Assoziationspfad>,
field <Feldname>,
field <Assoziationspfad>.<Feldname>
affects …;
}
Listing 11.73
Behavior Definition: Side Effects Syntax
A special side effect for a determine action is the global side effect,
which has no triggering condition, but can be explicitly triggered in
the UI, for example, by pressing the (Enter) key or a dedicated
button (depending on the UI implementation):
determine action <Actionname>
executed on global affects …;
In our concrete application example, we now want to define some
side effects. In the sales order header, we’ve defined an action for
deletion. Because the semantics of this action (unlike the delete
operation) is no longer known, an explicit side effect is now
necessary (see Listing 11.74), which specifies that the entity as well
as its child entities may change. Furthermore, after the (logical)
deletion, the delete action is no longer reasonable and enabled, so
the corresponding feature control is also changed if the operation is
invoked successfully.
In addition, we have the special determine action Prepare that is part
of the draft orchestration, so we define it as a global side effect also
(see Listing 11.74) and specify as effects essentially all changes that
are possible via the assigned determinations and validations.
define behavior for ZR_SalesOrderTP alias SalesOrder
…
{
…
side effects
{
action Delete
affects $self,
entity _Item,
entity _Item._ScheduleLine,
permissions ( action Delete ),
messages;
determine action Prepare executed on global
affects field SoldToParty,
field NetAmount,
field TransactionCurrency,
field _Item.NetAmount,
field _Item.TransactionCurrency,
field _Item.OrderQuantity,
field _Item.OrderQuantityUnit,
field _Item.Product,
field _Item._ScheduleLine.OrderQuantity,
field _Item._ScheduleLine.OrderQuantityUnit,
messages;
}
}
Listing 11.74
Behavior Definition: Side Effects for Sales Order Header
On the sales order item, we’ve defined a copy action that copies an
item and its schedule lines. Therefore, we specify the entity as well
as the child entity in the corresponding side effect here too (see
Listing 11.75). For the determination that runs directly on the change,
we also specify a simple side effect.
define behavior for ZR_SalesOrderItemTP alias SalesOrderItem
…
{
…
side effects
{
action Copy
affects $self,
entity _ScheduleLine;
field NetAmount
affects field _SalesOrder.NetAmount,
field _SalesOrder.TransactionCurrency;
}
}
Listing 11.75
Behavior Definition: Side Effects for Sales Order Items
We’ll see how the side effects work and their impact later in
Section 11.9.2.
11.4.20
Change Documents
When business object data is changed, it’s often desirable and
important to know who changed the data, what was changed, and
when the changes were made. This functionality is covered in the
ABAP platform by change documents. When we define a change
document object, a corresponding class is generated, which can
then be called when saving the changed data. We could implement
this call using the additional save functionality that we’ve already
learned about. An easier and more elegant option is available
because there is a seamless integration of the change documents in
the ABAP RESTful application programming model, which we’ll look
at now.
First, we define a new change document object for our business
object. To do this, we start the creation dialog, for example, via the
context menu of our package, and select New • Other Repository
Object. In the dialog, we then select Change Document Object
(see Figure 11.19).
Figure 11.19
Change Document Object: Create Dialog
Enter a name and description in the corresponding Name and
Description fields for the change document object (see
Figure 11.20).
Figure 11.20
Change Document Object: Creation
Finally, add the relevant database table and which types of changes
you want to collect in the change documents (see Figure 11.21).
Figure 11.21
Change Document Object: Assign Database Tables
Activation generates a class that you can call during save. Because
we want to use the built-in functionality, let’s add our change
document object in the behavior definition. On the main entity level
(usually the root entity) that the change document object is
associated with, this is done using the following syntax:
changedocuments master ( <ChangeDocumentObjectName> ).
For dependent entities, there is the corresponding dependent syntax
as usual:
changedocuments dependent by <AssociationToMasterEntity>.
Now, change documents are written for our business object, which
can be displayed, for example, with the corresponding SAP Fiori
application or can be integrated into your own SAP Fiori application
(see also http://s-prs.co/v564205).
[»] Relevant Fields for Change Documents
Whether a field or a change in the values of a field is relevant for
logging in a change document is defined at the related data
element. You can find this setting under Additional Properties
and then Change Document Logging. For CDS simple types,
this is defined accordingly via CDS annotations.
11.4.21
Events
In addition to transactional changes, which have an effect especially
when using the draft in the interaction and can be made known
statically to a client via side effects, you also want to not only log
changes when changing the data but also trigger asynchronous
subsequent processes. Here we look at the (asynchronous) events
among the possible options.
An event is defined in an entity of a behavior definition via:
event <EventName>
By default, if the event is triggered at runtime, it contains the key of
the corresponding entity on which the event was defined. Optionally,
the event can also have an extended payload, for example, to
simplify the evaluation for consumers of the event and to decide
whether the event is interesting or not without further accessing and
reading of data. The structure of the event parameter is defined
using the already known CDS abstract entities and is assigned as
follows:
event <EventName> parameter <NameOfDataType>
For our example, we’ll limit ourselves to the three standard events
for creating, updating, and deleting a sales order. For the creation
event, we want to add some additional data via parameters. To do
this, we create an abstract entity that we mark as an event signature
via annotation (see Listing 11.76).
@EndUserText.label: 'Sales Order Created'
@Event: {
description: 'Sales Order Created',
sapObjectNodeType: 'SalesOrder',
implementedBy: ['ABAP:RAP_EVENT']
}
@VDM.usage.type: [#EVENT_SIGNATURE]
define abstract entity ZD_SalesOrderCreated
{
SalesOrderType
: auart_unv;
SalesOrganization
: vkorg;
DistributionChannel : vtweg;
OrganizationDivision : spart;
SoldToParty
: kunnr;
EventRaisedDateTime : abp_creation_utcl;
}
Listing 11.76
Event: Parameter Data Type
In the behavior definition, we now define three events, as depicted in
Listing 11.77.
define behavior for ZR_SalesOrderTP alias SalesOrder
…
{
…
event Created parameter ZD_SalesOrderCreated;
event Changed;
event Deleted;
}
Listing 11.77
Behavior Definition: Event
We now trigger the events in our implementation. In the simplest
case, we can use the incoming change information directly in the
corresponding statement (as you can see in the event for newly
created sales orders in Listing 11.78). Because we want to consider
logically deleted sales orders as deleted, we don’t trigger an event
for a change for them, but the event for the deletion.
METHOD save_modified.
DATA changed TYPE TABLE FOR EVENT zr_salesordertp~changed.
DATA deleted TYPE TABLE FOR EVENT zr_salesordertp~deleted.
IF create-salesorder IS NOT INITIAL.
RAISE ENTITY EVENT zr_salesordertp~created
FROM CORRESPONDING #( create-salesorder
MAPPING eventraiseddatetime = creationdatetime ).
ENDIF.
deleted = CORRESPONDING #( delete-salesorder ).
LOOP AT update-salesorder ASSIGNING FIELD-SYMBOL(<salesorder>).
IF <salesorder>-deletionindicator = abap_true.
APPEND VALUE #( salesorder = <salesorder>-salesorder )
TO deleted.
ELSE.
APPEND VALUE #( salesorder = <salesorder>-salesorder )
TO changed.
ENDIF.
ENDLOOP.
IF changed IS NOT INITIAL.
RAISE ENTITY EVENT zr_salesordertp~changed FROM changed.
ENDIF.
IF deleted IS NOT INITIAL.
RAISE ENTITY EVENT zr_salesordertp~deleted FROM deleted.
ENDIF.
ENDMETHOD.
Listing 11.78
Behavior Implementation: Raise Event
In addition to the events for the standard operations, we can also
define further specific events, for example, for special status
transitions such as release or completion of a sales order or similar.
In this context, it’s then also a question of dedicated times at which
an event is triggered to be able to react to it more specifically.
Another use case is the question of whether the data of an event is
sufficient to be able to react to it, or whether further information must
be read for this. To avoid this in simple cases, you can define a
dedicated event with an extended parameter, which is then raised in
the implementation itself. But you also can define derived events that
are raised based on another event (i.e., under the same conditions)
but have their own extended parameters:
managed event <ManagedEventName> on <EventName>
parameter <EntityName>;
These events are automatically raised by the ABAP infrastructure
based on the referenced event. To supply the event parameter, data
must be read. Therefore, the event parameter in this case isn’t a
CDS abstract entity, but a CDS view entity that must select from the
underlying CDS entity of the behavior definition in which the event is
defined. Thus, for managed events, the data is read via SQL.
The use case outlined is more for extensions (see Chapter 15).
Nevertheless, for illustrative purposes, we define such an event in
our example, where the user wants to respond only to specific
customers. To do this, we define an event parameter as in
Listing 11.79.
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_ALLOWED
@EndUserText.label: 'Sales Order Created Derived'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType:{
serviceQuality: #B,
sizeCategory: #L,
dataClass: #TRANSACTIONAL
}
define view entity ZD_SalesOrderCreatedDerived
as select from ZR_SalesOrderTP
{
key SalesOrder,
SoldToParty
}
Listing 11.79
Managed Event: Parameter Data Type
The event is then defined in the behavior definition, as shown in
Listing 11.80.
managed event CreatedWithSoldToParty on Created
parameter ZD_SalesOrderCreatedDerived;
Listing 11.80
Behavior Definition: Managed Event
As already stated, we don’t need to take care of the raising of the
event but refer to an existing event and its raising via on Created.
Where and how we can consume events is described in detail in
Section 11.10.1.
11.5 Transactional Projection Object
Models
In this section, you’ll learn about the transactional CDS projection
object models with which you can expose your application for
specific use cases. You only want to implement the business object
and the associated business logic once to avoid redundancies and
ensure the consistency of the system. At the same time, however,
you’ll use the business object in different business processes,
different applications, external Web APIs, and UIs. In addition, you
only expose certain parts of your transactional model in a specific
API or UI, and you define restrictions both at the data level and at
the behavior level or available function level.
11.5.1
Projection Object Models
The projection object models are defined—as always—via CDS (see
also Chapter 6). The corresponding CDS views are defined as
projections of the CDS views of the transactional object model. In the
transactional projection object models, you can mainly restrict the
transactional object model of your application to expose a selected
part of your overall application. The following restrictions and
changes are allowed:
Aliasing (key word AS)
Fields can be renamed.
Exposed fields (columns; key word SELECT)
The selection of the fields of the underlying views can be
restricted.
Result set (rows; key word WHERE)
The result set can be restricted using conditions.
Exposed entities
Not all entities of the business object need to be exposed.
Exposed and filtered associations
Not all associations of the transactional object model need to be
exposed. The associations can further be restricted via filter
condition.
Besides pure projections, you can also add fields from the underlying
model by following associations with maximum cardinality 1 or
restricted to maximum cardinality 1 based on further association
filters (denormalization). A special supported type of denormalization
is the language-dependent text field that is filtered by the current
logon language, which we describe in Section 11.5.4.
To some extent, you can also enhance the projection object model
with projection-specific information. You can add ABAP calculated
fields (Section 11.5.5), whereas CDS calculated fields via CDS
expressions aren’t allowed. You can define and expose further
associations to other CDS entities, but the direct usage of these
associations (as well as JOINs) isn’t allowed within the projection
entity.
Further you must respect the following modeling restrictions:
The key of the CDS views of the projection object model must
correspond to the key in the object model. In particular, all key
fields of the underlying CDS view must exist in the projection list of
the projection object model and must be defined as keys.
The root entity must always be exposed. To express the same in
compositional models, the key word root is also needed on the
projection level. In addition, all entities on the path to the root
entity must exist in the projection object model for each entity
included. This means that only leaves or entire subtrees can be
omitted.
Because a new CDS view is introduced in the projection object
model for each entity exposed, the composition-like associations
can’t be projected 1:1 as they would refer to the CDS view of the
underlying object model as a target and not to its projection.
Therefore, these associations need to be redirected properly. At
the same time, composition and parent associations may not only
be redirected but also need to follow the similar syntax regarding
their type, that is, redirected to composition child and redirected
to parent.
Aggregation functions and calculations using the corresponding CDS
features aren’t allowed, nor are unions or joins for the CDS views of
the transactional projection object model. As a basic rule, functions
aren’t allowed that prevent a unique mapping of the result set of a
CDS view of the projection object model to the result set of the
underlying CDS view of the object model. Adding fields to the
projection list using path expressions is generally allowed if the
resulting joins don’t change the result set cardinality.
The CDS provider contract (see also Chapter 2, Section 2.19)
defines the possible uses of a projection model or its restrictions.
Currently, the provider contracts are transactional_interface and
transactional_query. In the case of provider contract
transactional_interface for use in interface behavior definitions
(Section 11.6), no extensions are possible beyond the pure
projection functionality described previously. For provider contract
transactional_query, adding fields to the projection list using path
expressions is allowed if the resulting denormalization doesn’t
change the cardinality of the result set. Furthermore, defining new
associations and using localized fields (Section 11.5.4) or virtual
fields (Section 11.5.5) is also possible.
In our example, we now expose the transactional object model of the
sales order for internal access and use some of the previously
mentioned features. We only want to expose the sales order header
and the sales order item, omitting the schedule line and reducing the
field list. For the sales order header, we further alias one field and
reduce the result set by not exposing logically deleted sales orders
(see Listing 11.81).
@EndUserText.label: 'Sales Order'
@AccessControl.authorizationCheck: #MANDATORY
define root view entity ZI_SalesOrderTP
provider contract transactional_interface
as projection on ZR_SalesOrderTP
{
key SalesOrder,
SalesOrderType,
SalesOrganization,
SoldToParty as Customer,
DistributionChannel,
OrganizationDivision,
NetAmount,
TransactionCurrency,
DeliveryStatus,
@Consumption.hidden: true
LastChangeDateTime,
_Item : redirected to composition child ZI_SalesOrderItemTP,
_SoldToParty
}
where
DeletionIndicator = ''
Listing 11.81
CDS Projection View Entity: Sales Order Header
Listing 11.82 shows the definition of the projection of the sales order
item.
@EndUserText.label: 'Sales Order Item'
@AccessControl.authorizationCheck: # MANDATORY
define view entity ZI_SalesOrderItemTP
as projection on ZR_SalesOrderItemTP
{
key SalesOrder,
key SalesOrderItem,
Product,
OrderQuantity,
OrderQuantityUnit,
NetAmount,
TransactionCurrency,
@Consumption.hidden: true
LastChangeDateTime,
_SalesOrder : redirected to parent ZI_SalesOrderTP,
_Product
}
Listing 11.82
CDS Projection View Entity: Sales Order Item
We also plan to expose the same application via OData as a Web
API as well as for an SAP Fiori UI. Because these follow a different
CDS provider contract and therefore offer more implementation
options, we create similar models with a 1:1 projection of the
underlying model for the Web API and the UI, following the familiar
naming conventions from VDM with the prefixes “A” and “C”. We’ll
leverage these models in Section 11.9.1 and Section 11.9.2.
[»] Code Examples
All code examples are available for download on this book’s SAP
PRESS web page (www.sap-press.com/5642).
11.5.2
Access Control
The projection views also need their own access control. For
transactional projection object models, the defined access controls
need to inherit the access control from the underlying transactional
object entity. You can find an example in Listing 11.83.
@EndUserText.label: 'Sales Order'
@MappingRole: true
define role ZI_SalesOrderTP {
grant
select
on
ZI_SalesOrderTP
where
inheriting conditions from entity ZR_SalesOrderTP;
}
Listing 11.83
Access Control: Sales Order Header Projection
The DCLs for the other projection view entities and transactional
projection object models created for the OData exposure as Web
API or UI are defined similarly.
11.5.3
Denormalized Fields
From here on, we switch to the projection object model for the OData
service that we’ll discuss later in Section 11.9.2 with CDS provider
contract transacional_query to explain the further features. We
refrain from defining further associations, but denormalize some
language-independent texts for the UI.
As an example, the sales order header with the name of the
customer and the user descriptions of the creation and last changed
user are added here. The other entities, as well as the CDS access
control, are modeled similarly to Listing 11.84.
define root view entity ZC_SalesOrderTP
provider contract transactional_query
as projection on ZR_SalesOrderTP
{
key SalesOrder,
SalesOrderType,
SalesOrganization,
@ObjectModel.text.element: ['CustomerName']
SoldToParty,
_SoldToParty.CustomerName,
DistributionChannel,
OrganizationDivision,
@Semantics.amount.currencyCode: 'TransactionCurrency'
NetAmount,
TransactionCurrency,
DeliveryStatus,
@Semantics.booleanIndicator: true
DeliveryIsCompleted,
@Semantics.booleanIndicator: true
DeletionIndicator,
@ObjectModel.text.element: ['CreatedByUserDescription']
CreatedByUser,
_CreatedByUser.UserDescription as CreatedByUserDescription,
CreationDateTime,
@ObjectModel.text.element: ['LastChangedByUserDescription']
LastChangedByUser,
_LastChangedByUser.UserDescription as
LastChangedByUserDescription,
LastChangeDateTime,
_Item : redirected to composition child ZC_SalesOrderItemTP
}
Listing 11.84
11.5.4
CDS Projection View Entity: Denormalized Fields
Localized Elements
As mentioned, the projection view syntax also allows you to
specifically denormalize language-dependent text fields from
associated entities. This is specified by adding key word localized to
the field. The same works if the association target has a key field for
the language (ABAP data type LANG), and all other key fields of the
association target are bound along the association path.
In our example, we follow the association to the product and from
there to the product text to add the language-dependent product
name (see Listing 11.85). If there were multiple language-dependent
fields, you may also add any of these, for example, a short and a
long name. The usage of the localized key word is at present
equivalent to the usage via a filtered association, which you can
easily verify by adding a second field, as shown in Listing 11.85.
Along with a better readable syntax, the advantage of the first option
is the fact that it’s not bound to the system language, thus a fallback
language feature would work out of the box once it’s supported by
the ABAP platform.
define view entity ZC_SalesOrderItemTP
as projection on ZR_SalesOrderItemTP
{
key SalesOrder,
key SalesOrderItem,
Product,
_Product._Text.ProductName : localized,
_Product._Text[1:Language=$session.system_language].ProductName
as ProductNameViaFilter,
…
}
Listing 11.85
11.5.5
CDS Projection View Entity: Sales Order Item with Localized Element
Calculated Fields/Virtual Elements
As already explained in Section 11.4.15, fields may also need to be
included in the data model that aren’t persisted, especially in the
environment of transactional applications. These can be transformed
fields, for example, which are used for a simplified or additional
presentation in the UI or control fields to dynamically hide UI
elements based on certain instance information. In contrast to the
object model, you can also add such fields in the projection object
model and supply them with values using an ABAP implementation.
As this is achieved via an ABAP exit, this is only respected by the
OData runtime but not the SQL runtime, so this feature can only be
used and consumed in projection object models exposed via SADLbased OData services.
Adding such a field can be done in projection views via syntax
element virtual where the related data type needs to be given. The
implementing class to correctly fill the field value at runtime is
annotated via the following annotation:
@ObjectModel.virtualElementCalculatedBy:'...'
The implementing class must implement interface
IF_SADL_EXIT_CALC_ELEMENT_READ. In method GET_CALCULATION_INFO,
you define which fields of your own entity are required for the
calculation so that it’s ensured that the data is read from the
database and is available for the calculation even if this field isn’t
explicitly requested by the consumer. In method CALCULATE, the data
read is then made available and can be enhanced by the field to be
calculated here.
[»] Searching, Sorting, and Filtering Virtual Elements
Because virtual elements aren’t implemented by SQL and provide
no value, the affected database operations, such as searching,
filtering, sorting, and so on, aren’t available. When the OData
service is generated, these restrictions of virtual elements are
considered automatically, and the corresponding OData properties
in the OData entity types are exposed as not searchable, not
filterable, and not sortable.
The restrictions on sorting and filtering can be removed if these
operations can be easily and uniquely mapped to persistent fields in
the CDS entity. You can implement the mapping rules in an ABAP
class by applying the following annotations:
@ObjectModel.sort.transformedBy:'…'
@ObjectModel.filter.transformedBy:'…'
The implementing class must implement interface
IF_SADL_EXIT_SORT_TRANSFORM for the sort exit and
IF_SADL_EXIT_FILTER_TRANSFORM for the filter exit.
[»] Sort and Filter Exits
Note that the sort and filter exits aren’t just usable for virtual
elements but could also be used for any element. Similar use
cases could be the improvement of performance based on sorting
or filtering on CDS calculated fields that can be redirected to
persisted information only to prevent performance-intensive
calculations or materialization of a complete calculated table
column.
The example should now be extended on the sales order item level
to include such a field as an example. As this feature is only
available for the OData exposure, we don’t add it to our projection
defined for internal access but on a similar projection for the UIbased OData service (Section 11.9.2). Therefore, we enhance the
model as shown in Listing 11.86.
define view entity ZC_SalesOrderItemTP
as projection on ZR_SalesOrderItemTP
{
…
@Semantics.booleanIndicator: true
@ObjectModel.virtualElementCalculatedBy:
'ABAP:ZCL_F_SALESORDERITEM'
virtual OrderIsFreeOfCharge:abap.char(1),
…
}
Listing 11.86
CDS Projection View Entity: Sales Order Projection with Virtual Element
In the implementation of this field, you then set the value using
standard ABAP. In the example from Listing 11.87, we define field
NETAMOUNT as a necessary field for the calculation. The calculation
itself now checks whether the value is 0 and sets virtual field
ORDERISFREEOFCHARGE in this case.
METHOD if_sadl_exit_calc_element_read~get_calculation_info.
INSERT CONV #('NETAMOUNT') INTO TABLE et_requested_orig_elements.
ENDMETHOD.
METHOD if_sadl_exit_calc_element_read~calculate.
LOOP AT it_original_data ASSIGNING FIELD-SYMBOL(
<ls_salesorderitem>).
ASSIGN COMPONENT 'NETAMOUNT'
OF STRUCTURE <ls_salesorderitem>
TO FIELD-SYMBOL(<lv_netamount>).
CHECK sy-subrc = 0.
CHECK <lv_netamount> IS INITIAL.
ASSIGN COMPONENT 'ORDERISFREEOFCHARGE'
OF STRUCTURE ct_calculated_data[ sy-tabix ]
TO FIELD-SYMBOL(<lv_orderisfreeofcharge>).
CHECK sy-subrc = 0.
<lv_orderisfreeofcharge> = abap_true.
ENDLOOP.
ENDMETHOD.
Listing 11.87
ABAP Implementation: Calculating a Virtual Element
In this simple example, sort and filter delegation is possible, thus we
now also enhance the model by adding the related annotations by
enabling sorting and filtering and providing the implementation class
(see Listing 11.88).
define view entity ZC_SalesOrderItemTP
as projection on ZR_SalesOrderItemTP
{
…
@Semantics.booleanIndicator: true
@ObjectModel.virtualElementCalculatedBy:
'ABAP:ZCL_F_SALESORDERITEM'
@ObjectModel.sort.enabled: true
@ObjectModel.sort.transformedBy:
'ABAP:ZCL_F_SALESORDERITEM'
@ObjectModel.filter.enabled: true
@ObjectModel.filter.transformedBy:
'ABAP:ZCL_F_SALESORDERITEM'
virtual OrderIsFreeOfCharge :abap.char(1),
…
}
Listing 11.88 CDS Projection View Entity: Sales Order Projection with Sortable and
Filterable Virtual Element
We consequently implement the other two necessary interfaces in
the same class. The example sort implementation is quite
straightforward as we can just delegate the sorting to related field
NetAmount by taking into account to transform a DESCENDING sorting to
ASCENDING and vice versa (see Listing 11.89). The example filter
implementation might be a little bit more complex, but as we have a
Boolean indicator, only the equal operator is applicable. In
Listing 11.90, we transform the Boolean values to the related simple
condition of NetAmount.
METHOD if_sadl_exit_sort_transform~map_element.
ASSERT iv_entity = 'ZC_SALESORDERITEMTP'.
ASSERT iv_element = 'ORDERISFREEOFCHARGE'.
APPEND VALUE #( name = 'NETAMOUNT' reverse = abap_true ) TO
et_sort_elements.
ENDMETHOD.
Listing 11.89
ABAP Implementation: Sorting a Virtual Element
METHOD if_sadl_exit_filter_transform~map_atom.
ASSERT iv_entity = 'ZC_SALESORDERITEMTP'.
ASSERT iv_element = 'ORDERISFREEOFCHARGE'.
CASE iv_operator.
WHEN if_sadl_exit_filter_transform~co_operator-equals.
IF iv_value = abap_true.
DATA(isfree) = abap_true.
ELSE.
isfree = abap_false.
ENDIF.
WHEN if_sadl_exit_filter_transform~co_operator-greater_than.
IF iv_value = abap_true.
RETURN.
ELSE.
isfree = abap_true.
ENDIF.
WHEN if_sadl_exit_filter_transform~co_operator-less_than.
IF iv_value = abap_true.
isfree = abap_false.
ELSE.
RETURN.
ENDIF.
WHEN if_sadl_exit_filter_transform~co_operator-is_null.
ASSERT 1 = 0. "may never happen
WHEN if_sadl_exit_filter_transform~co_operator-covers_pattern.
ASSERT 1 = 0. "may never happen
ENDCASE.
DATA(condition) =
cl_sadl_cond_prov_factory_pub=>create_simple_cond_factory( ).
IF isfree = abap_true.
condition->element( iv_name = 'NETAMOUNT' )->equals( iv_value = 0 ).
ELSE.
condition->element( iv_name = 'NETAMOUNT' )->greater_than(
iv_value = 0 ).
ENDIF.
ENDMETHOD.
Listing 11.90
ABAP Implementation: Filtering a Virtual Element
11.6
Define Interface Behavior Definition
Now, as we’ve defined the data model of the projection object model,
we want to enhance the same also with dedicated behavior. In this
section, you’ll learn about the interface behavior definition, how to
expose certain features into a dedicated projection, and what
enhancements are possible on this layer.
11.6.1
Create Interface Behavior Definition
As mentioned before, the projection functionalities with CDS provider
contract transactional_interface are limited. For example, no new
fields or associations can be added. Similarly, it’s not possible to add
new operations or other functionalities in interface behavior
definitions. Only functionalities from the base behavior definition can
be exposed here and renamed if necessary. In particular, nothing
can (and must) be implemented for an interface behavior definition,
so that no implementing class can be specified either.
To create an interface behavior definition, right-click on the CDS
projection root view entity with CDS provider contract
transactional_interface and choose New Behavior Definition.
The Name can’t be changed as it’s identical to the CDS root view
entity, and the implementation type is defined as Interface, so only
the Description can be changed (see Figure 11.22).
Figure 11.22
ADT: Create Interface Behavior Definition
The resulting behavior definition already prepopulates the entities
defined as projections as well as all operations and features from the
underlying base behavior definition (see Listing 11.91).
interface;
use draft;
define behavior for ZI_SalesOrderTP alias SalesOrder
{
use create;
use update;
use action Delete;
use action CreateFromQuote;
use action Check;
use action Activate;
use action Discard;
use action Edit;
use action Prepare;
use action Resume;
use function GetSalesOrder;
use function GetNumberOfItems;
use association _Item { create; with draft; }
}
define behavior for ZI_SalesOrderItemTP alias SalesOrderItem
{
use update;
use delete;
use action Copy;
use association _SalesOrder { with draft; }
}
Listing 11.91
Interface Behavior Definition: Sales Order
In the next sections, we’ll explain the syntax elements of the
interface behavior definition.
11.6.2
Static Feature Control
Along with restricting the data model by only projecting parts of it, the
interface behavior definition might not expose all the features
available. Thus, in the interface behavior definition, it’s possible to
restrict the static field control to some extent. A field that is editable
in the base behavior definition might become statically read only in a
dedicated projection, which can be achieved with the following
syntax:
field ( readonly ) <field_name>;
In addition, a field may not be changeable any more only after the
creation:
field ( readonly : update ) <field_name>;
On the other hand, you might also want an editable field to be
mandatorily filled during creation of an instance that isn’t defined as
such in the base behavior definition or only checks during save for a
mandatory value. The same can be achieved via the following:
field ( mandatory:create ) <field_name>;
Other values of static field control aren’t allowed in the interface
behavior definition. In addition, influencing or defining the dynamic
feature control of underlying fields or operations isn’t possible here.
A special feature for fields is the option to remove them from the
projection if the field is added to the CDS entities only for technical
reasons, for example, for the access control or for the association
definition, but should not be available for EML access. The field is
then technically available in the CDS entity and for SQL access, but
not in the corresponding ABAP data type for EML access. Fields that
will be omitted can be defined via the following:
field ( suppress ) <field_name>;
Due to the possible exposure via an OData service, it’s
recommended and reasonable to also annotate the related field in
the CDS entity with the following to ensure the technical field is also
not available in the OData metadata:
@Consumption.hidden:true
In our example, we arbitrarily added these features to some of the
fields to verify and show the functionality:
field ( mandatory : create, readonly : update ) Customer;
11.6.3
Operations
As already prepopulated in the example (refer to Listing 11.91), you
can decide which operations should be provided via the interface
behavior definition. This applies for create, update, and delete, as
well as associations and their create behavior, actions, and
functions. Adding operations and features to the interface behavior
definition is done using the key word use on the entity level, as
shown in Listing 11.92.
use create;
use update;
use delete;
use assocation <association_name>;
use assocation <association_name> { create; }
use action <action_name>;
use function <function_name>;
Listing 11.92
Interface Behavior Definition: Use Operations
In addition, the external action and function names can be defined in
the interface behavior definition (see also Section 11.4.10):
use action <ActionName> external '<ExternalActionName>';
use function <FunctionName> external '<ExternalFunctionName>';
This comes in handy if the application is exposed via OData V2
because both actions and functions are exposed as function imports.
Thus, these reside in the same global namespace, and the same
action names on different entities will lead to name clashes.
Therefore, we recommend adding the entity name as a prefix to the
action name in projections intended for OData V2 services.
Besides the external name, we also need to deal with the proper
result entity if the action or function has an entity result. As you’ve
already learned on the CDS side for associations, the special key
word redirected is used to indicate the same. For action and
function results, this is indicated by (re)defining the result entity via
the following:
use action <ActionName> result entity <EntityName>;
use function <FunctionName> result entity <EntityName>;
Note that for the special result entity $self, this is done implicitly
based on the CDS-defined redirect. Replacing nonentity-based
result data types isn't possible as of the time of writing.
11.6.4
Draft, ETag, and Side Effects
If the base behavior definition provides a draft, this also can be
exposed (mainly for projections intended for UI OData services). The
draft itself is valid for the complete behavior definition; the
associations that are draft enabled also need to be exposed as such
on the entity level:
use draft;
use assocation <association_name> { with draft; }
In addition, the draft actions also need to be explicitly added to the
interface behavior definition, as shown in Listing 11.93.
use action Edit;
use action Activate;
use action Resume;
use action Discard;
use action Prepare;
Listing 11.93
Interface Behavior Definition: Use Draft Actions
Side effects that are already defined in the underlying behavior
definition can also be applied to the interface behavior definition. The
corresponding syntax is as follows:
use side effects;
For OData exposure, the ETag capability of the base behavior
definition can be leveraged on the entity level via the following:
use etag;
This makes sense because the interface behavior definitions also
serve as a base model for projection behavior definitions
(Section 11.7.1), which are then in turn used for OData services. It’s
mandatory that the underlying field of the ETag is included in the
corresponding projection view entity.
11.6.5
Events
Events can also be exposed via interface behavior definitions via the
following:
use event <EventName>;
This is especially useful and necessary for using extensions that can
only use released objects (see also Section 11.6.7).
In our example, we expose the three defined standard events (see
Listing 11.94).
define behavior for ZI_SalesOrderTP alias SalesOrder
…
{
…
use event Created;
use event Changed;
use event Deleted;
}
Listing 11.94
11.6.6
Interface Behavior Definition: Use Event
Consumption via EML
Everything described in Section 11.4.3 also applies to interface
behavior definitions. Thus, an interface behavior definition also can
be consumed via EML like any other behavior definition. Especially
for other applications or extensions (see Chapter 15), it makes sense
to use a stable interface behavior definition (see Section 11.6.7) to
compensate, at least temporarily, for possible incompatible changes
in case of further developments of the base behavior definition.
11.6.7
Release for Consumption
As mentioned in Chapter 14, artifacts can be provided with a stability
contract to keep their usage compatible in other developments such
as customer developments on SAP or partner functionality.
In addition to CDS entities, behavior definitions can also be provided
with the C1 stability contract. Technically, base behavior definitions
can be provided with this contract as well as interface behavior
definitions and projection behavior definitions (which we’ll cover in
the next section). However, we recommend that only interface
behavior definitions are released for usage.
To define the stability contract for an interface behavior definition,
select, for example, API State • Add Use System-Internally
(Contract C1) from the context menu in the Project Explorer view.
In the subsequent dialog, select Use in Cloud Development and
Enable Configuration of Authorization Default Values (see
Figure 11.23).
Figure 11.23
Interface Behavior Definition: Define the Stability Contract
In the following check, you’ll notice that in the course of releasing a
behavior definition, the underlying data model, that is, the CDS
entities used, must also be released accordingly. In our case, these
are the two CDS projection entity views as well as the parameter and
result data types for the actions, functions, and events used in the
interface behavior definition, including their abstract behavior
definitions.
11.7
Define Projection Behavior Definition
The definition of a projection behavior definition is initially very
similar to the definition of an interface behavior definition, but allows
for much more functionality, as you’ll learn in this section. The only
limitation of projection behavior definitions compared to interface
behavior definitions is that no further behavior definitions can be
placed on top of them, while interface behavior definitions can in turn
be used in projection behavior definitions. Thus, a (maximum) threelevel model layering of base behavior definition, interface behavior
definition, and projection behavior definition is possible.
11.7.1
Create Projection Behavior Definition
As mentioned before, we want to define dedicated projection
behavior definitions for our OData services in addition to the
interface behavior definition. The creation of a corresponding
behavior projection is similar to the interface behavior definition, but
this time on a CDS entity with the CDS provider contract
transactional_query and resulting behavior definition of type
projection.
In our examples, we use the ETag feature for the OData-related
projections ZA_SalesOrderTP and ZC_SalesOrderTP and the draft
feature for UI-related projection ZC_SalesOrderTP. In the OData use
cases for SAP Fiori applications, the deep types used for actions and
functions parameters and results aren’t yet supported. Thus, here we
only expose the actions with simple abstract entities. Listing 11.95
shows the projection behavior definition for the UI projection focusing
on the draft feature.
projection;
strict ( 2 );
use draft;
define behavior for ZC_SalesOrderTP alias SalesOrder
use etag
{
use create;
use update;
use action Delete;
use action Check;
use action Edit;
use action Activate;
use action Resume;
use action Discard;
use action Prepare;
use association _Item { create; with draft; }
}
define behavior for ZC_SalesOrderItemTP alias SalesOrderItem
use etag
{
use update;
use delete;
use action Copy;
use association _SalesOrder { with draft; }
use association _ScheduleLine { create; with draft; }
}
define behavior for ZC_SalesOrderScheduleLineTP alias SalesOrderScheduleLine
use etag
{
use update;
use delete;
use association _SalesOrder { with draft; }
use association _SalesOrderItem { with draft; }
}
Listing 11.95
11.7.2
Projection Behavior Definition: Sales Order UI Projection
Actions and Functions
While new associations in the data model can be added in the
projection layer for pure read access without transactional behavior,
it’s possible to also add certain behavior operations in the projection
layer. You can add projection-specific actions and functions using the
same syntax and feature set as described in Section 11.4.10 and
Section 11.4.11 for the base behavior definition. Because an
implementation is needed in this case, an implementation class also
needs to be provided and assigned, following the same known
syntax:
projection implementation in class <class_name> unique;
In our projection example for the UI, we want to mitigate the missing
feature of deep parameters and create a CreateFromSalesQuote
action with a simple parameter allowing just one sales quote as input
instead of a list of sales quotes (see Listing 11.96). For the sake of
simplicity, we can even reuse the underlying action parameter detail
structure.
projection implementation in class zbp_c_salesordertp unique;
…
define behavior for ZC_SalesOrderTP alias SalesOrder
{
…
static factory action ( features : global ) CreateFromSalesQuote
parameter ZD_SalesOrderCreateFromQuotesP [1];
…
}
Listing 11.96
Projection Behavior Definition: Sales Order Header New Action
As you can see, feature control also can be implemented for this
action similarly. With the help of the quick fix, the implementation
class is provided where we simply delegate the action to the
underlying action of the base behavior definition on our own. The
delegation of the feature handler is depicted in Listing 11.97, and the
delegation of the action call is shown in Listing 11.98.
METHODS get_global_features FOR GLOBAL FEATURES
IMPORTING REQUEST requested_features FOR SalesOrder RESULT result.
…
METHOD get_global_features.
GET PERMISSIONS ONLY GLOBAL FEATURES OF ZR_SalesOrderTP
ENTITY SalesOrder REQUEST CORRESPONDING #( requested_features )
RESULT
DATA(result_base)
REPORTED DATA(reported_base).
result
= CORRESPONDING #( DEEP result_base ).
reported = CORRESPONDING #( DEEP reported_base ).
ENDMETHOD.
Listing 11.97
Behavior Implementation: Projection Feature Control
METHODS CreateFromSalesQuote FOR MODIFY
IMPORTING keys FOR ACTION SalesOrder~CreateFromSalesQuote.
…
METHOD CreateFromSalesQuote.
DATA keys_base TYPE TABLE FOR ACTION IMPORT zr_salesordertp~createfromsalesquote.
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
APPEND VALUE #( %cid
= <key>-%cid
%param-%is_draft
= <key>-%param-%is_draft
%param-_salesquotes = VALUE #( ( <key>-%param-salesquote ) )
) TO keys_base.
ENDLOOP.
MODIFY ENTITY ZR_SalesOrderTP
EXECUTE CreateFromQuote FROM keys_base
MAPPED DATA(mapped_base)
FAILED DATA(failed_base)
REPORTED DATA(reported_base).
mapped
= CORRESPONDING #( DEEP mapped_base ).
failed
= CORRESPONDING #( DEEP failed_base ).
reported = CORRESPONDING #( DEEP reported_base ).
ENDMETHOD.
Listing 11.98
11.7.3
Projection Behavior Implementation: Action
Prechecks
Because a projection usually is defined for a special purpose, it
might not only be needed to restrict the editability of a field statically
or make it mandatory but also to apply further checks. A valid use
case might be to restrict the projection to certain types of instances;
thus, at the same time, it might be valid to only allow the creation of
those types that can be verified in a precheck.
The definition and implementation are similar to that described in
Section 11.4.16, so we won’t provide a dedicated example here.
11.7.4
Augmentation
As you’ve learned, you can restrict the feature set and can add
actions and functions delegating these to the base behavior
definition. You might wonder if it’s also possible to redefine or
enhance the standard operations create, update, or delete. This is
possible to some extent with augmentation in which you can, for
example, create additional entity instances that are specific for a
certain projection layer and thus not provided as default on the base
layer already.
For example, in denormalized language-dependent texts fields, the
description is denormalized in the projection view via key word
localized if the description itself is also transactional, that is,
belongs to your own object. This works properly in a standard
SAPUI5 UI only if the related instance where the description is
defined exists. With the help of the augmentation, if an instance of
the entity is created, you can immediately within the same request
create an instance of the language-dependent text entity for the
current logon language.
The augmentation can be added to any create or update operation
with the following syntax:
use create ( augment );
use update ( augment );
use association <association_name> { create
( augment ); }
Additional fields also can be made changeable via augmentation.
Added fields in the projection views, such as denormalized fields,
special denormalization via localized, or virtual elements added via
virtual, aren’t part of the transactional projection object model and
therefore by definition are set to read only automatically. With the
following statement, these can be added to the transactional
projection object model:
field ( modify ) <field_name>;
In our denormalized text example, we not only want to create an
(empty) default instance for the logon language but also set the
entered description properly. This can be done by enabling the
editability of the denormalized description field and delegating it to
the correct entity instance and field of the base layer in the
augmentation implementation. To do so, EML offers special syntax
MODIFY AUGMENTING to indicate augmenting operations. This statement
is only allowed in augmentation handlers.
Because we don’t have a denormalized editable description in our
example but used this feature only for the product data description,
we augment our virtual element to add some UI convenience. If
OrderIsFreeOfCharge isn’t set yet, we enable it to be editable; if it’s
checked, we set the NetAmount to “0”. To do so, we need to enable
the field and add augmentation for the related create (by association)
and update operation (see Listing 11.99).
define behavior for ZC_SalesOrderTP alias SalesOrder
…
{
…
use association _Item { create ( augment ); with draft; }
}
define behavior for ZC_SalesOrderItemTP alias SalesOrderItem
…
{
use update ( augment );
…
field ( modify, features : instance ) OrderIsFreeOfCharge;
}
Listing 11.99
Projection Behavior Definition: Augmentation
As you can see, you can also add field control for these additional
fields that can be implemented on the projection layer, as described
in Section 11.4.13. We use this for our simplified example to only
enable the input if the value isn’t set because removing the value
would require further logic to provide a certain default for the
amount.
The implementation handlers can be created via a quick fix, and a
default implementation for the augmentation can be found in
Listing 11.100 for create by association and Listing 11.101 for
update. The field control implementation follows the standard
approach, which we discussed in Section 11.4.13.
METHODS augment_cba_Item FOR MODIFY
IMPORTING entities FOR CREATE SalesOrder\_Item.
…
METHOD augment_cba_Item.
DATA creates TYPE TABLE FOR CREATE zr_salesordertp\\salesorder\_item.
DATA create TYPE STRUCTURE FOR CREATE zr_salesordertp\\salesorder\_item.
LOOP AT entities ASSIGNING FIELD-SYMBOL(<entity>).
LOOP AT <entity>-%target ASSIGNING FIELD-SYMBOL(<target>)
WHERE %control-OrderIsFreeOfCharge = if_abap_behv=>mk-on
AND %control-NetAmount
= if_abap_behv=>mk-off
AND OrderIsFreeOfCharge
= abap_true.
APPEND VALUE #( %cid
= <target>-%cid
%key
= <target>-%key
%is_draft
= <target>-%is_draft
NetAmount
= 0
%control-NetAmount = if_abap_behv=>mk-on
) TO create-%target.
ENDLOOP.
IF create-%target IS NOT INITIAL.
create-%cid_ref = <entity>-%cid_ref.
create-%tky
= <entity>-%tky.
APPEND create TO creates.
ENDIF.
ENDLOOP.
IF creates IS NOT INITIAL.
MODIFY AUGMENTING ENTITY ZR_SalesOrderTP
CREATE BY \_Item FROM creates.
ENDIF.
ENDMETHOD.
Listing 11.100
Projection Behavior Implementation: Create Augmentation
METHODS augment_update FOR MODIFY
IMPORTING entities FOR UPDATE SalesOrderItem.
…
METHOD augment_update.
DATA updates TYPE TABLE FOR UPDATE zr_salesordertp\\salesorderitem.
LOOP AT entities ASSIGNING FIELD-SYMBOL(<entity>)
WHERE %control-OrderIsFreeOfCharge = if_abap_behv=>mk-on
AND %control-NetAmount
= if_abap_behv=>mk-off
AND OrderIsFreeOfCharge
= abap_true.
APPEND VALUE #( %cid_ref
= <entity>-%cid_ref
%tky
= <entity>-%tky
NetAmount
= 0
%control-NetAmount = if_abap_behv=>mk-on
) TO updates.
ENDLOOP.
IF updates IS NOT INITIAL.
MODIFY AUGMENTING ENTITY ZR_SalesOrderItemTP UPDATE FROM updates.
ENDIF.
ENDMETHOD.
Listing 11.101
11.7.5
Behavior Implementation: Update Augmentation
Side Effects
Side effects that are already defined in the underlying behavior
definition can also be applied to the projection. The corresponding
syntax is as before:
use side effects;
Because not all operations and elements are necessarily present in
the projection, only the relevant and existing operations and
elements are transferred to the projection for the side effects. This
happens automatically through the ABAP infrastructure. A side effect
of an action that isn’t exposed to the projection is therefore not
present here at all, or a field in the effect of a side effect that wasn’t
projected is also removed.
Side effects can also be defined on the projection itself. This is
necessary, for example, if actions are defined in the projection itself
or if the side effects affect denormalized elements or virtual elements
defined in the projection.
For our example, we’ve added a virtual element to the sales order
item that refers to exactly one other element of the underlying model.
Because we’ve left both fields modifiable, we also need to define
side effects for both (see Listing 11.102).
define behavior for ZC_SalesOrderItemTP alias SalesOrderItem
use etag
{
…
side effects
{
field NetAmount
affects field OrderIsFreeOfCharge;
field OrderIsFreeOfCharge
affects field NetAmount;
}
}
Listing 11.102
Projection Behavior Definition: Side Effects Sales Order Item
In addition, we add the global side effect as the underlying field.
Because the addition global is currently not allowed in projections,
we use a non-changeable field as a workaround (see Listing 11.103).
define behavior for ZC_SalesOrderTP alias SalesOrder
use etag
{
…
side effects
{
determine action Prepare executed on field CreationDateTime
affects field _Item.OrderIsFreeOfCharge;
}
}
Listing 11.103
Projection Behavior Definition: Side Effects Sales Order Header
Side effects from different sources are added additively; that is, in
our case the side effect of the Prepare action simply gets another
effect.
11.7.6
Events
Events can’t be exposed in projections because, as we’ll see in
Section 11.10, their use for different use cases is foreseen only for
base or interface behavior definitions.
11.7.7
Consumption via EML
Technically, projections can be used via EML in the same way as
basic or interface behavior definitions. However, interface behavior
definitions should be used for this, if possible, if no projectionspecific functionalities are required. This is because there is a
restriction on exits that are called for query access only, such as
virtual elements, as these aren’t part of the EML runtime structures
(similar to any denormalized field). After such fields are made
editable via augmentation (Section 11.7.4), they become part of the
EML runtime structures for the entity, while there is currently no
option to provide proper values during an EML read operation.
11.8
Runtime Orchestration
Now that you’ve seen the available features and functions of the
ABAP RESTful application programming model and how they can be
implemented, it makes sense to also look at the overall runtime
orchestration to understand how all these things work together.
The access to a business object or one of its projections is identical;
even the internal runtime does a direct delegation, and no additional
call stack is created from the application point of view. Access via
EML is usually done from other applications/business objects,
integration tests, generic consumption tools such as data migration,
or specific consumption (e.g., OData or Simple Object Access
Protocol [SOAP]). For OData consumption, the ABAP platform
provides end-to-end support as indicated in Section 11.9. The
runtime orchestration is independent of the provider implementation
type.
11.8.1
Interaction Phase Operation Flow
If an operation is invoked (e.g., via EML or OData), the internal
processing follows a defined sequence that is partially enforced and
provided by the ABAP runtime and will be partially implemented by
the provider implementation.
Looking at the example of an update or action call for an instancebound action, the following steps are done to invoke this action:
1. Check global authorization
Is the user allowed to invoke this operation or action at all?
2. Check global feature control
Is the operation or action enabled at all?
3. Lock the instance
Invoke enqueue lock for the instances (i.e., the related ancestor
instance of the lock master entity).
4. Check the ETag
Does the provided ETag still match the current ETag? This step
is only relevant in case of HTTP access.
5. Application-specific operation check (precheck)
For example, check incoming data such as change field values
or action parameters.
6. Application-specific augmentation
Delegate the operations or field change to the underlying
behavior definition. This step is relevant only in the case of
projection behavior definitions and for create or update
operations.
7. Check instance authorization
Is the user allowed to invoke this operation or action for this
instance?
8. Check instance feature control
Is the operation or action enabled for this instance?
9. Invoke operation and action implementation
Execute modifications (via EML).
Invoke side effects (application-specific).
The listed provider steps are all part of the ABAP infrastructure and
invoked via the related handler implementations, except the
implementation of an action, which is always application specific. For
an action or implementation type UNMANAGED in general, all these
things could also be implemented with the action or the modify
handler, but we recommend leveraging the infrastructure
orchestration and handlers in any case. With side effects, we refer to
application logic that is invoked immediately based on changes (e.g.,
if the quantity of an ordered item is changed, then the price is
calculated and updated), which is reflected by determinations on
modify for implementation type MANAGED.
For any other operations, the sequence is the same, but not all steps
are always relevant, and different application handlers are invoked
based on the operation. Further, instance-related steps aren’t
relevant for static operations and actions, and locking,
authorizations, and feature control aren’t relevant for read operations
or functions.
Within the interaction phase, multiple operations can be called that
result in changes of the transactional buffer in the provider until, at
some point, the editing is completed, and the data will be finally
checked and saved to the database.
11.8.2
Save Phase Operation Flow
Finalizing the transaction and processing and storing the changed
data is done via EML statement COMMIT ENTITIES. Here, all involved
business objects within the transaction are invoked by the ABAP
runtime during the save phase. The sequence of invoking the
different business objects is based on their first occurrence within the
transaction, but you may never rely on any defined sequence. It
consists of the following steps/methods:
Finalize
Check Before Save
Cleanup Finalize
---point of no return--Adjust Numbers
Save
---cleanup--Cleanup
These methods are called for each involved behavior definition one
after the other while the save is rejected if one method or behavior
implementation fails (indicated via return parameter FAILED). During
Finalize, all application logic and not-yet-executed side effects are
invoked. For implementation type MANAGED, these correspond to
determinations on save. Then during Check Before Save, everything is
checked for consistency and to determine whether it can be saved at
all. For implementation type MANAGED, these correspond to
validations on save. If an error occurs within these two phases, the
transaction moves back to the interaction phase, allowing the
consumer to correct the issues and run the save again. Here,
optionally, Cleanup Finalize can be implemented to revert changes
made during finalize that should only occur during save.
If everything is consistent, then processing moves on to the late save
phase and passes the point of no return. Once passed, the
transaction and ABAP LUW will terminate in any case with a COMMIT
or ROLLBACK, so there is no option to return to the interaction phase
from here. Thus, all checks are expected to happen in the early save
phase.
First, numbers are drawn from number ranges (for applications with
late numbering, e.g., due to legal requirements for gap-less
numbering). Then, these numbers can be fetched during the Save
step by other providers or on the consumer side by the transaction
controller that executed the COMMIT ENTITIES via ABAP statement
CONVERT KEYS. Finally, in the Save step, the data is saved to the
database (directly via database modifications or via registering an
update task function module).
After the save is complete, a CLEANUP ensures that transactional
buffers are also cleaned up and are reset to the proper state. If an
issue occurs in these late phases, this leads to a termination of the
transaction and an implicit ROLLBACK.
[»] COMMIT WORK versus COMMIT ENTITIES
The ABAP RESTful application programming model save phase is
also included in the standard COMMIT WORK handling; that is, if any
EML invocation was done during the transaction and ABAP LUW,
the ABAP RESTful application programming model save phase is
executed. This enables interoperability and use of the ABAP
RESTful application programming model even in legacy scenarios
and other used programming models. The difference is that any
failure during COMMIT WORK (even in the early save phase) will lead
to a runtime error, while COMMIT ENTITIES responds in this case with
a SY-SUBRC = 4 and further information in FAILED and REPORTED and
returns to the interaction phase, keeping the unsaved changes.
Thus, the applications need to ensure up front that everything is
consistent. One option to ensure the same is the usage of COMMIT
ENTITIES IN SIMULATION MODE that runs the early save phase and
then returns to the interaction phase.
If a consumer wants to clean up the transaction and revoke changes,
this can be done via statement ROLLBACK ENTITIES (or ROLLBACK WORK
similarly). In this case, only the CLEANUP method is called, and all
changes are discarded.
11.8.3
Runtime Component Overview
A simplified overview of the different runtime components for the
different consumption channels and handler classes is depicted in
Figure 11.24.
Figure 11.24
ABAP Infrastructure: Runtime Component Overview
In the middle, we have the ABAP RESTful application programming
model runtime that acts as the main orchestration and transaction
handler. From here, the different handlers we described in
Section 11.8.1 are invoked. The handlers independent of the
provider implementation are depicted on the right side, for example,
the global authorization handler. The handlers implemented by the
provider are depicted below the ABAP RESTful application
programming model runtime. Depending on the implementation type,
the provider could be MANAGED, BOPF MANAGED (see also Appendix B),
or UNMANAGED. The provider implements the handlers for the CRUD
operations and is responsible to manage the transactional buffer and
the changes to later on persist these changes to the database.
The provider reads and writes data to the database (ideally using the
ABAP update task), depending on the instance to the draft or active
database tables. The ABAP RESTful application programming model
runtime layer accesses the persisted data directly in case of query
accesses (see left arrow) via the modeled CDS entities or draft
tables.
The ABAP RESTful application programming model implementation
can be consumed internally via EML, for example, from your own
implementation or from other ABAP RESTful application
programming model implementations (depicted by the connection on
the right side) or from any other ABAP code (outside of the ABAP
RESTful application programming model). Further, the consumption
can be done with OData via the SAP OData runtime and the
provided OData adapter. Depending on the use case, the consumer
might be a different system or a UI (see also Section 11.8.4).
11.8.4
Consumption via OData
As stated before, for the consumption via an OData service, the
ABAP RESTful application programming model provides full end-toend support for both OData V2 and OData V4. Thus, if an OData
service is defined via service binding (Section 11.9), the complete
protocol layer implementation is provided by the ABAP platform for
translating any kind of request to either SQL (for query read access)
or EML (for transactional access). With this architectural approach,
the application implementation is agnostic to the protocol and
protocol version, so the effort to support additional channels or
switch the protocol or protocol version is minimal. In addition, the
application can focus on real application logic instead of dealing with
technical protocol implementation.
11.9
SAP Fiori and OData Consumption
Finally, we now want to expose our ABAP RESTful application
programming model application via OData for the different use cases
of Web APIs and UIs. From the technical point of view, there is no
difference between these use cases in terms of how to define or
consume these OData services. Rather, the difference lies in which
information is modeled in the related projection layer for different
purposes and which information is exposed by the infrastructure
based on the chosen service binding type.
11.9.1
OData Service for Web API Consumption
As we’ve defined our projection layer for the Web API consumption,
we now can expose the same via an OData service. As you learned
in Chapter 6, we need to define a service definition by exposing and
aliasing the related entities. Based on the service definition, we
define service binding ZAPI_SALESORDER where we choose
OData V4 – Web API as the binding type for this use case (see
Figure 11.25). We don’t want to expose the draft in this service.
Because the service is to be kept stable as an external API, a
release with stability contract C2 should also be made here.
Figure 11.25
Service Binding: OData V4 Web API
Once published (either locally directly from the service binding via
Publish or via Transaction /IWFND/V4_ADMIN), the OData service
now contains all metadata that can be derived from the CDS-based
projection object model and projection behavior definition (see
Figure 11.25). For example, some CDS annotations are translated
directly into OData annotations, and the model is enriched
accordingly. In detail, each CDS view in OData becomes an OData
entity set with the corresponding entity type, which contains the
fields of the CDS view as OData properties with the corresponding
name and type as well as the same key information. CDS
associations are mapped as OData associations or OData
association sets and OData navigation properties. Because we have
a transactional object model that can also contain actions and
functions defined in the behavior definition in addition to CDS, we
also want to have them exposed. This also happens automatically. In
OData V2, both actions and functions are mapped as function
imports, whereas in OData V4, these are mapped as bound actions
and bound functions. Remember to take into account the potential
name clashes for action and function names in OData V2 as
described earlier in Section 11.7.2.
You can directly access the metadata of the service via link Service
URL on the right side of the screen and test the published OData
service via any REST client or directly via Swagger by clicking the
Test button.
11.9.2
OData Service for UI Consumption
To close this chapter, we want to take a short excursion to an SAP
Fiori app. Because we’ve defined our projection layer for UI
consumption, we also want to define an OData service for our SAP
Fiori elements app. To make sure we have a usable UI from the
beginning, we enhanced the projection object model with further
information as you learned about in previous chapters, for example,
by adding text annotations, value help annotations, and so on. But
there’s more to it.
We already have rich metadata available through the OData services
that can be used in the implementation of UI applications. With SAP
Fiori elements (see SAP Fiori for Web Design Guidelines at
https://experience.sap.com/fiori-design-web/smart-templates), SAP
offers a template-based approach that leverages this metadata to
easily implement a UI application. With CDS and the ABAP
infrastructure, you can define additional UI-specific metadata via
CDS annotations in the CDS projection object model; this metadata
is provided as separate metadata via OData annotations. An SAP
Fiori UI based on SAP Fiori elements uses this metadata so that you
can create an executable SAP Fiori app with just a few additional
steps to test your application.
We limit ourselves here to selected annotations from the large pool
of UI annotations to introduce the topic. In detail, we need to define
which fields are available in a standard presentation for each entity
in a table or form presentation and in which order these fields will be
presented. In addition, we need to define which fields are provided
as default selection fields for filtering in the list report. Of course, all
exposed fields are available in the OData service, so a user can add
further fields or define the sequence using personalization.
Let’s dive into our selected annotations, as follows:
@UI.selectionField
This annotation and its elements define the presentation of
selection fields.
@UI.lineItem
This annotation and its elements define the representation of an
entity in a tabular presentation.
@UI.headerInfo
This annotation and its elements define the representation of an
entity on the object page.
@UI.facet
This annotation and its elements define the facets of an object
page.
@UI.identification
This annotation and its elements define the representation of an
entity instance in a single form-based representation.
@UI.fieldGroup
This annotation and its elements define the representation of an
entity instance in a single form-based representation within a field
group.
You define the respective sequence with position. With importance,
you can declare the importance of a field. The latter is used to hide
less important fields by default in the UI when space is limited, for
example, on a tablet or mobile device (i.e., responsive design).
Finally, you also want to add the action defined in Section 11.4.10
into your UI as a button. This is achieved by a special CDS
annotation. If you want the action to appear in the list, simply add the
following annotation to one of the annotated fields:
@UI.lineItem: { type: #FOR_ACTION,
dataAction: '<name of action>',
label: '<label of action button>' }
If you want the action to appear on the detail object page, you can
use the following annotation:
@UI.identification: { type: #FOR_ACTION,
dataAction: '<name of action>',
label: '<label of action button>' }
As described in Chapter 4, we use a CDS metadata extension for
the definition of UI-specific annotations. To enable this, we add the
following annotation to all our projection views defined for the UI
projection:
@Metadata.allowExtensions: true
Only then can we use a metadata extension for the definition of UIspecific annotations. For your specific OData service, you add a
metadata extension to your CDS model, as shown in Listing 11.104
for the sales order.
@Metadata.layer: #CORE
@UI.headerInfo.typeName: 'Sales Order'
@UI.headerInfo.typeNamePlural: 'Sales Orders'
@UI.headerInfo.title.label: 'Sales Order'
@UI.headerInfo.title.value: 'SalesOrder'
annotate view ZC_SalesOrderTP with
{
@UI.facet: [{
id: 'SalesOrderGeneralData',
purpose: #STANDARD,
type: #FIELDGROUP_REFERENCE,
label: 'General Data',
targetQualifier: 'GeneralData',
position: 10
},
{
id: 'SalesOrderAdminData',
purpose: #STANDARD,
type: #FIELDGROUP_REFERENCE,
label: 'Administrative Data',
targetQualifier: 'AdminData',
position: 20
},
{
id: 'SalesOrderItem',
purpose: #STANDARD,
type: #LINEITEM_REFERENCE,
label: 'Items',
position: 30,
targetElement: '_Item'
}]
@UI.identification: [{position: 10, importance: #HIGH,
type:#FOR_ACTION, dataAction: 'DELETE', label: 'Delete' },
{position: 11, importance: #HIGH,
type:#FOR_ACTION, dataAction: 'CHECK', label: 'Check' }]
@UI.lineItem: [{position: 10, importance: #HIGH}]
@UI.fieldGroup: [{qualifier: 'GeneralData', position: 10, importance: #HIGH }]
@UI.selectionField: [{position: 10}]
SalesOrder;
@UI.lineItem: [{position: 20, importance: #HIGH}]
@UI.fieldGroup: [{qualifier: 'GeneralData', position: 20, importance: #HIGH }]
@UI.selectionField: [{position: 20}]
SalesOrderType;
@UI.lineItem: [{position: 30, importance: #HIGH}]
@UI.fieldGroup: [{qualifier: 'GeneralData', position: 30, importance: #HIGH }]
@UI.selectionField: [{position: 30}]
SalesOrganization;
@UI.lineItem: [{position: 40, importance: #HIGH}]
@UI.fieldGroup: [{qualifier: 'GeneralData', position: 40, importance: #HIGH }]
@UI.selectionField: [{position: 40}]
DistributionChannel;
@UI.lineItem: [{position: 50, importance: #HIGH}]
@UI.fieldGroup: [{qualifier: 'GeneralData', position: 50, importance: #HIGH }]
@UI.selectionField: [{position: 50}]
OrganizationDivision;
@UI.lineItem: [{position: 60, importance: #HIGH}]
@UI.fieldGroup: [{qualifier: 'GeneralData', position: 60, importance: #HIGH }]
@UI.selectionField: [{position: 60}]
SoldToParty;
@UI.lineItem: [{position: 70, importance: #HIGH}]
@UI.fieldGroup: [{qualifier: 'GeneralData', position: 70, importance: #HIGH }]
NetAmount;
@UI.hidden:true
TransactionCurrency;
@UI.fieldGroup: [{qualifier: 'GeneralData', position: 80, importance: #HIGH }]
DeliveryIsCompleted;
@UI.fieldGroup: [{qualifier: 'GeneralData', position: 90, importance: #HIGH }]
DeliveryStatus;
@UI.fieldGroup: [{qualifier: 'GeneralData', position: 100, importance: #HIGH }]
DeletionIndicator;
@UI.fieldGroup: [{qualifier: 'AdminData', position: 110, importance: #LOW }]
CreatedByUser;
@UI.fieldGroup: [{qualifier: 'AdminData', position: 111, importance: #LOW }]
CreationDateTime;
@UI.fieldGroup: [{qualifier: 'AdminData', position: 112, importance: #LOW }]
LastChangedByUser;
@UI.fieldGroup: [{qualifier: 'AdminData', position: 113, importance: #LOW }]
LastChangeDateTime;
}
Listing 11.104
CDS Metadata Extension: Sales Order Header with UI Annotations
Same as for the OData service for the Web API, we define a service
definition exposing and aliasing the related entities and a service
binding ZUI_SALESORDER where we choose OData V4 - UI as the
binding type to create an OData service for our UI consumption (see
Figure 11.26).
Figure 11.26
Service Binding: OData V4 UI
Besides the OData metadata and Swagger test, for this service
binding type, you can test the complete UI based on SAP Fiori
elements preview, which can be launched via the Preview button or
by double-clicking the root entity in the details tree.
The specified UI annotations provide you with an SAP Fiori elements
app in which the annotated fields are displayed by default and by
which you can create, update, and delete sales orders using the
specific action. The initial list with extensive filter and selection
options is like the one shown in Figure 11.27. In addition, new sales
orders can be entered using the Create button above the list.
Figure 11.27
SAP Fiori Elements: List Report
The input helps described in Chapter 10 are also available for the
selection fields as type ahead and value help popups (see
Figure 11.28).
Figure 11.28
SAP Fiori Elements: List Report Value Help
Because we’ve adapted the draft concept in the related filters, you
can see a dedicated Editing Status field (see Figure 11.29) with
which you can, for example, filter on your own drafts.
Figure 11.29
SAP Fiori Elements: List Report Filter Editing Status
Further in the list, the editing status is indicated and bound to the
column marked as the semantic key, in our case, the SalesOrder.
Here, additional information for your own draft is displayed (see
Figure 11.30). If the sales order is edited by a different user (i.e., a
draft exists), and the sales order is locked, further information is
required to contact the user (see Figure 11.31). If the exclusive lock
is already expired, this is indicated by Unsaved Changes (see
Figure 11.32).
Figure 11.30
SAP Fiori Elements: List Report Draft Information
Figure 11.31
SAP Fiori Elements: List Report Locked Information
Figure 11.32
SAP Fiori Elements: List Report Unsaved Changes Information
When navigating to a sales order instance, Figure 11.33 shows a
possible representation of the form-based UI. Because you allow
editing in the projection object model and have specified the action
you defined for deletion in the UI annotations, corresponding buttons
are available for these operations (in the top-right screen).
Figure 11.33
SAP Fiori Elements: Object Page in Display Mode
To edit the sales order, click the Edit button, and a draft is created
for the sales order as a result. Thus, editable fields can now be
changed, and operations only allowed in edit mode, such as creating
or deleting items, are enabled (see Figure 11.34).
Any data entered or changed is immediately sent and stored in the
draft. This is indicated beside the Save button as you can see in
Figure 11.35 and Figure 11.36.
Figure 11.34
SAP Fiori Elements: Object Page in Edit Mode
Figure 11.35
SAP Fiori Elements: Object Page Saving Draft Information
Figure 11.36
SAP Fiori Elements: Object Page Draft Saved Information
On clicking the Save button, the draft is saved by technically calling
the activate action. All application checks are running to verify that
only consistent data is being saved. If errors occur, these usually
also appear up front and not only during the save based on the side
effects concept (Section 11.4.12). After a successful save, the sales
order is again available for other users for editing.
If you don’t want to save but want to discard your changes, this can
be done via the Discard Draft button. After the confirmation by
clicking Discard (see Figure 11.37), the draft is discarded, and the
sales order is again available for other users for editing.
Figure 11.37
SAP Fiori Elements: Object Page Discard Changes
If you try to edit a sales order that is currently being edited by
another user (as indicated already in the list report), the UI behavior
depends on whether the sales order still holds an exclusive lock. If
there is a foreign lock, editing isn’t possible unless the other user has
finished editing and has saved or discarded the changes, as shown
in Figure 11.38. If the exclusive lock is gone, the system warns you
that unsaved changes of another user are present (see
Figure 11.39). Then you can choose if it’s okay to overwrite the
changes (which will result in deleting the draft of the other user and
creating your own draft) or if you would rather contact the user to
clarify the situation.
Figure 11.38
SAP Fiori Elements: Object Page Locked Information
Figure 11.39
SAP Fiori Elements: Object Page Unsaved Changes Information
Finally, creating a new sales order is done via the list report by
clicking the Create button. Because we’ve decided (for the sake of
this example) that the external sales order number needs to be given
immediately (mandatory: create), the UI provides a popup to enter
the sales order number directly before sending the create request to
the backend, as shown in Figure 11.40.
Figure 11.40
SAP Fiori Elements: List Report Create Dialog
On confirming with the Continue button on the popup, a draft is
created, and you navigate to the object page where you can enter
the data and finally either save the sales order or discard your
changes. As stated before, during editing, any checks and
information provided by the backend implementation is shown to the
end user, ideally bound to fields or sections (see Figure 11.41).
Figure 11.41
SAP Fiori Elements: Object Page Create
To create your dedicated SAP Fiori elements app, you use SAP
Business Application Studio (check http://s-prs.co/v564206) or Visual
Studio Code in SAP Business Technology Platform (SAP BTP),
which is connected to your backend system or the SAP Gateway
hub.
[»] Additional Resources
Further information on SAPUI5, SAP Fiori, and SAP Fiori elements
can be found in the SAP Help documentation.
11.10 SAP Event Mesh and Local Event
Handlers
You learned how to define and trigger events earlier in
Section 11.4.21, so now let’s briefly discuss how these events can
be consumed. The events defined in the behavior definition are
intended for asynchronous processing; synchronous interaction or
reacting to changes is usually done via determinations and
validations and their triggering conditions. This is also and especially
true for extensions (see Chapter 15).
Asynchronous processing doesn’t necessarily mean that processing
takes place in another system. You can therefore consume events in
the system via local event handlers or publish the events to the SAP
Event Mesh so that other external users can consume these events.
11.10.1
Local Event Handler
It’s very easy to implement a local event handler because the
handling of events from behavior definitions is also integrated into
the ABAP language. A class for handling these events is defined by
the following statement:
CLASS <ClassName> DEFINITION PUBLIC ABSTRACT FINAL
FOR EVENTS OF <BehaviorDefinitionName>.
Here, you specify the behavior definition whose events you want to
consume.
For the concrete event and the implementation of the event handler,
create a local class that inherits from
cl_abap_behavior_event_handler and declare methods as outlined in
Listing 11.105.
CLASS <LocalClassName> DEFINITION
INHERITING FROM cl_abap_behavior_event_handler.
PRIVATE SECTION.
METHODS <MethodName> FOR ENTITY EVENT
<ImportParameterName> FOR <BehaviorDefinitionName>~<EventName>.
ENDCLASS.
Listing 11.105
Local Event Handler: Definition
The method signature is typed according to the definition of the
event and its parameter structure. Of course, you can also react to
multiple events in the same method by defining an import parameter
for multiple events. This can be useful if, for example, you want
something to happen both when creating or making any changes.
For our example, we now create a local event handler (see
Listing 11.106) and define a corresponding handler method in the
local handler class for all defined events (see Listing 11.107).
CLASS zcl_salesorder_eventhandler DEFINITION
PUBLIC ABSTRACT FINAL
FOR EVENTS OF ZR_SalesOrderTP.
PUBLIC SECTION.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_salesorder_eventhandler IMPLEMENTATION.
ENDCLASS.
Listing 11.106
Local Event Handler: Global Class Definition
CLASS lhe_salesorder DEFINITION INHERITING FROM cl_abap_behavior_event_handler.
PRIVATE SECTION.
METHODS on_created FOR ENTITY EVENT
created
FOR SalesOrder~Created
created_specific FOR SalesOrder~CreatedWithSoldToParty.
METHODS on_changed FOR ENTITY EVENT
changed FOR SalesOrder~changed.
METHODS on_deleted FOR ENTITY EVENT
deleted FOR SalesOrder~deleted.
ENDCLASS.
CLASS lhe_salesorder IMPLEMENTATION.
METHOD on_changed.
"do something
ENDMETHOD.
METHOD on_created.
"do something
ENDMETHOD.
METHOD on_deleted.
"do something
ENDMETHOD.
ENDCLASS.
Listing 11.107
Local Event Handler: Local Class Definition
We’ll refrain from a concrete implementation of the handler here
because this is standard ABAP coding and doesn’t provide any
further insights within the scope of this book. You’ll see in Chapter 16
how the event handler works at runtime.
Technically, the events are processed via background remote
function calls (bgRFCs). Accordingly, their processing and in
particular errors can be seen, for example, in the bgRFC monitor
(Transaction SBGRFCMON).
11.10.2
SAP Event Mesh
While handling events in the system is useful for both your own
applications and extensions, a second natively supported use case
is publishing events to SAP Event Mesh.
To publish an event, an event binding must be created. This is done,
for example, via the context menu by selecting New • Other
Repository Object and searching for Event Binding in the dialog
box. There, you enter information in the Name and Description
fields. We recommend including the object and event in the name of
the event binding (see Figure 11.42).
Figure 11.42
Event Binding: Create
In the event binding itself, you then specify the Type Namespace,
SAP Object Type, and Operation. We recommend reusing the
name of the event to be published as the operation name. Now we
create the first version of the event and select the origin of the event,
that is, the behavior definition and the event defined there. After
saving and activating the event binding, the Type (or topic) is
automatically created (see Figure 11.43).
Figure 11.43
Event Binding
Now, when the event is raised, it’s automatically transferred to SAP
Event Mesh. The prerequisite for this is, of course, that the
communication has been set up accordingly and our new topic has
been assigned in the outbound binding of the channel. We won’t go
into this part here, but refer you to the official documentation, for
example, at http://s-prs.co/v564207.
11.11
Summary
In this chapter, you learned how to leverage CDS for the definition
and implementation of transactional applications. We provided an
overview of the transactional runtime based on the ABAP RESTful
application programming model, and you’ve learned how to enable
the transactional runtime for your application. You’ve learned about
transactional object models and projection object models as well as
the related behavior definition, interface behavior definition, and
projection behavior definition. We described how to define static and
dynamic properties; how to implement numbering, exclusive locks,
authorization checks, and feature controls; how to implement your
business logic considering side effects; and how to define and
implement actions and functions. In addition, we’ve shown how you
can define events and raise or publish them for local and remote
usage. Further, we’ve shown how you can consume your application
implementation programmatically via EML and how to expose your
application as an OData service for Web APIs and for UIs to create a
simple SAP Fiori elements UI application on top.
In the next chapter, we’ll look into hierarchies in CDS.
12
Hierarchies in CDS
Business data is often structured hierarchically; for example,
there are hierarchies of organization units, reporting
hierarchies of employees, hierarchies of financial accounts in
the balance sheet, hierarchies of products and product
types, and many more.
Hierarchical structures help users survey and process large amounts
of data. In analytical applications, measures can be aggregated at
hierarchy nodes and selectively expanded to greater detail.
Core data services (CDS) offers two methods for modeling and
processing hierarchies:
Annotation-based hierarchies
Annotation-based hierarchies can be defined with a standard CDS
view or entity and have been available since the introduction of
CDS technology. However, because they need the analytic
processor (analytic engine) that we introduced in Chapter 10,
Section 10.4, they are mainly used in analytical applications.
CDS hierarchies
CDS hierarchies were first introduced in SAP S/4HANA release
1809 as a special new type of CDS entity, exclusively for that
purpose. They are defined by a specific hierarchy syntax, which is
aligned with native SAP HANA hierarchies. CDS hierarchies are
integrated with ABAP SQL, which offers several new functions for
the traversal and aggregation along hierarchies that are executed,
like the hierarchy selects, by the SAP HANA hierarchy engine.
Section 12.1 presents two different hierarchy categories, leveled
hierarchies and parent-child hierarchies, as well as some hierarchy
basics. In Section 12.2 and Section 12.3, we’ll explore both methods
for defining hierarchies, walk through several examples, and discuss
all necessary ingredients.
12.1
Hierarchy Categories and Basics
In leveled hierarchies, every hierarchy level needs a separate data
field. A simple example is the two-level hierarchy of countries and
regions, which is represented by view I_Region with key fields
Country and Region for the levels. As explicit fields are available for
every hierarchy level, standard SQL requests are sufficient for
executing typical hierarchy requests, such as aggregating measures,
that are defined on the lowest level, to higher levels of the hierarchy.
You can, for example, determine all regions of a country via a simple
SQL request and aggregate their values. Special hierarchy functions
aren’t necessary. Therefore, we’ll focus on a different type of
hierarchy, the parent-child hierarchy.
A parent-child hierarchy consists of a set of hierarchy nodes and a
set of parent relations from a child node to a parent node.
Figure 12.1 shows an example.
Figure 12.1
Parent-Child Hierarchy
The top node in the hierarchy example is a root node, which doesn’t
have its own parent. There can be more than one root node in a
hierarchy. Nodes without their own child nodes are called leaf nodes
of the hierarchy. The descendants of a node are its children, its
grandchildren, and so on, excluding the node itself.
Parent-child hierarchies can have two exceptional properties:
Nodes with more than one parent
Nodes that form a cycle
In a cycle, you can start from a node, go to its parent, repeatedly
continue to the next parent, and reach the starting node after some
steps.
Hierarchies without these exceptional properties are called strict
hierarchies. Strict hierarchies are simpler to process and can be
displayed easier in user interfaces (UIs). Section 12.3.6 and
Section 12.3.7 will show examples of hierarchies that aren’t strict.
In a strict hierarchy, every node has exactly one root node. This can
be reached by traversing along the parent relation. The number of
steps to the root node is called distance from root. You get the level
of the hierarchy node by adding 1 to the number of steps. A root
node’s distance from root is 0, and its level is 1.
The relational data model for a parent-child hierarchy is based on an
entity for the nodes and a parent relation from the entity to itself.
Every data row has fields to uniquely identify a child node and to
uniquely identify a parent node. In CDS, a hierarchy node entity can
be modeled by a CDS view or a CDS entity, and the parent relation
can be modeled by a CDS association.
In business reality, the important entities, such as employees or cost
centers, can be found in many different hierarchical arrangements,
often combined with other entities of the business world. Therefore,
it’s common practice to not include business attributes in the
hierarchy node entities but have a relation (association) to a
separate entity that represents the employee or the cost center.
Entities with different business semantics can be combined in a
hierarchy that way.
This separation also enables the definition of multiple alternative
hierarchical structures for the same entities, for example, planning
variants or historical versions. The same cost center can be
associated from multiple hierarchy variants or versions. Usually, a
separate entity, a hierarchy directory, is used to maintain these
alternative hierarchical structures.
Parent-child hierarchies have an important advantage over leveled
hierarchies: the number of hierarchy levels is independent from the
number of available fields to represent a level and, theoretically,
unlimited. This capability, however, requires programming loops or
recursive processing of the hierarchy. Unfortunately, this isn’t
possible with standard requests in SQL. For using parent-child
hierarchies, additional support by a hierarchy engine is necessary.
For analytic applications, the hierarchy engine of the analytic engine
has been available since CDS was introduced. It’s used for
annotation-based hierarchies, which we’ll introduce in Section 12.2.
CDS hierarchy entities, covered in Section 12.3, use the SAP HANA
hierarchy engine.
12.2 Annotation-Based Parent-Child
Hierarchies
Annotations for describing hierarchical properties of CDS models
were introduced early in CDS to define parent-child hierarchies.
Their application is still limited to analytical models as only the
analytical infrastructure’s hierarchy engine can execute them. This
section introduces the annotations for hierarchies and their usage.
For an annotation-based hierarchy, one of the related entities with
business semantics is distinguished as the hierarchy base entity.
The following types of CDS views are used:
A view for the hierarchy base entity
A view for hierarchy nodes
Optional views for other entities that are represented as nodes in
the hierarchy
An optional view for the hierarchy directory
Optional text views for the base entity, for the hierarchy nodes, for
other entities, and for the hierarchy directory
These views are connected by associations, as shown in
Figure 12.2. The types of the associations are shown as well.
Figure 12.2
CDS Views for Hierarchies
In simple cases, the same view represents the base entity and the
hierarchy nodes, and there are no other views. The employeemanager is an example. The relationship of the hierarchy base entity
to its hierarchy node view is established by an association. It’s
modeled by annotation @ObjectModel. hierarchy.association at the
representative key field of the base entity.
The essential information for the definition of the hierarchy structure
is the view of the hierarchy nodes. If it’s not identical to the view of
the base entity, it’s marked as a hierarchy node view by annotation
@ObjectModel. dataCategory: #HIERARCHY. Structured view annotation
@Hierarchy.parentChild defines the hierarchy structure. The details
are shown in Table 12.1.
Annotation
Semantics
@Hierarchy.parentChild.name
Specifies a technical name for
the hierarchy. It’s mandatory if
no hierarchy directory is used.
@Hierarchy.parentChild.label
Description of the hierarchy
(optional).
@Hierarchy.parentChild.recurseBy
The relation to the parent
node is defined by an
association of the view to the
view itself; used as an
alternative to annotation
recurse.parent and
recurse.child.
Annotation
Semantics
@Hierarchy.parentChild.
recurse.parent
@Hierarchy.parentChild.
recurse.child
The relation to the parent
node is defined by specifying
corresponding fields; used as
an alternative to annotation
recurseBy.
@Hierarchy.parentChild.
siblingsOrder.by
Specifies a field that is used
to define the order of siblings.
@Hierarchy.parentChild.
siblingsOrder.direction
Direction for ordering siblings;
can be 'ASC' (ascending) or
'DESC' (descending), with
'ASC' as default.
@Hierarchy.parentChild.directory
Association to the hierarchy
directory (optional).
Table 12.1
Annotations for Defining the Hierarchy Structure
The most important information is the relationship to a parent node.
For its definition, using a self-association in annotation
@Hierarchy.parentChild. recurseBy is most elegant. It must have
cardinality [0..1] and bind the key fields of the target. Alternatively,
the key fields of the view are specified after annotation
@Hierarchy.parentChild.recurse.child, and the view fields that
identify the parent node are specified after annotation
@Hierarchy.parentChild.recurse.parent in a sequence
corresponding to the key fields.
We’ll walk through an example and see how to determine and test a
parent-child hierarchy in the following sections.
12.2.1
Example of a Parent-Child Hierarchy
Now, we’ll use the cost center hierarchy, as delivered by SAP, as an
example. Listing 12.1 shows the relevant part of base entity
I_CostCenter. Note association _CostCenterHierarchyNode to the
hierarchy node view.
@ObjectModel.representativeKey: 'CostCenter'
define view I_CostCenter as select …
association[0..*] to I_CostCenterText as _Text
on $projection.ControllingArea
= _Text.ControllingArea
and $projection.CostCenter
= _Text.CostCenter
and $projection.ValidityEndDate
= _Text.ValidityEndDate
association[0..*] to I_CostCenterHierarchyNode
as _CostCenterHierarchyNode
on $projection.ControllingArea
= … .ControllingArea
and $projection.CostCenter
= … .CostCenter
{ key kokrs as ControllingArea,
@ObjectModel.text.association: '_Text'
@ObjectModel.hierarchy.association:
'_CostCenterHierarchyNode'
key kostl as CostCenter,
@Semantics.businessDate.to: true
key datbi as ValidityEndDate,
@Semantics.businessDate.from: true
datab as ValidityStartDate,
…
}
Listing 12.1
Hierarchy Base Entity for the Cost Center
Related node view I_CostCenterHierarchyNode is shown in
Listing 12.2. Note that identical key fields of the child and parent
node can be left out in the definition of the parent-child relation via
recurse.
@ObjectModel.dataCategory: #HIERARCHY
@Hierarchy.parentChild:
{ recurse:
{
parent: 'ParentNode',
child: 'HierarchyNode'
},
siblingsOrder:
{
by: 'HierarchyNodeSequence',
direction: 'ASC'
},
directory:
'_Hierarchy'
}
define view I_CostCenterHierarchyNode …
association [0..*] to I_CostCenterHierarchyNodeT as _Text
on $projection.CostCenterHierarchy = ….CostCenterHierarchy
and $projection.HierarchyNode
= ….HierarchyNode
and $projection.ControllingArea
= ….ControllingArea
and $projection.CostCenter
= ''
association [0..*] to I_CostCenter as _CostCenter
on $projection.CostCenter
= ….CostCenter
and $projection.ControllingArea
= ….ControllingArea
association [1..1] to I_CostCenterHierarchy as _Hierarchy
on $projection.CostCenterHierarchy = ….CostCenterHierarchy
and $projection.ControllingArea
= ….ControllingArea
and $projection.ValidityEndDate
= ….ValidityEndDate
association [0..1] to I_ControllingArea as
_ControllingArea
on $projection.ControllingArea
= ….ControllingArea
{
@ObjectModel.foreignKey.association: '_ControllingArea'
key … as ControllingArea,
@ObjectModel.foreignKey.association: '_Hierarchy'
key … as CostCenterHierarchy,
@ObjectModel.text.association: '_Text'
key … as HierarchyNode,
key … as ValidityEndDate,
… as ParentNode,
… as ValidityStartDate,
@ObjectModel.foreignKey.association: '_CostCenter'
… as CostCenter,
… as HierarchyNodeSequence,
_Text,
_CostCenter,
_Hierarchy,
_ControllingArea
…
}
Listing 12.2
Hierarchy Node View of the Cost Center Hierarchy
The related hierarchy directory, view I_CostCenterHierarchy, is
shown in Listing 12.3.
define view I_CostCenterHierarchy …
{ key … as ControllingArea,
key … as CostCenterHierarchy,
@Semantics.businessDate.to: true
key … as ValidityEndDate,
@Semantics.businessDate.from: true
… as ValidityStartDate,
…
}
Listing 12.3
Hierarchy Directory for Cost Center Hierarchies
Figure 12.3 shows the views of the example.
Figure 12.3
Hierarchy Example with Association Types
12.2.2
Determination of a Hierarchy
Using the view definitions, the infrastructure (or a developer) can
derive how the hierarchy is formed. For the cost center hierarchy,
this happens as follows:
1. In the hierarchy annotation of the node view, an association to a
hierarchy directory is provided that must be considered. The
user must therefore select a specific cost center hierarchy row
from the directory.
The hierarchy directory is time dependent (see Chapter 8,
Section 8.7), so the selection of the entry is done for a key date.
2. Now, all hierarchy nodes for the selected cost center hierarchy
are read. When doing so, filters are used on fields
ControllingArea, CostCenterHierarchy, and ValidityEndDate
with the values of the selected cost center hierarchy because
the association to the hierarchy directory is defined on these
fields.
3. All selected nodes have identical values on key fields
ControllingArea, CostCenterHierarchy, and ValidityEndDate.
Therefore, key field HierarchyNode is the effective key of these
nodes.
As specified in the hierarchy annotation, data field ParentNode
points to the parent node that is uniquely identified by
HierarchyNode now. By this, the nodes are ordered in a
hierarchical structure. The infrastructure creates an optimized
hierarchy representation that enables the efficient processing of
hierarchical requests.
4. In the next step, the individual cost centers (the instances of the
base entity) must be assigned to the hierarchy nodes. This vital
assignment is done according to a complex logic. For every
single hierarchy node, a node type is determined.
[»] Mixed Hierarchies
With the help of the node type, mixed hierarchies can be
processed. These can have multiple types of entities as nodes,
for example, cost centers, profit centers, or company codes, in
the same hierarchy.
Every possible node type is represented by a corresponding
association from the hierarchy node view. For every node of the
hierarchy and every association, the ON condition of the
association definition is checked in sequential order according to
its definition. The first association with a valid ON condition
defines the type of the node, and no further associations are
checked.
In the node view of the cost center, the definition of the text
association has additional condition $projection.CostCenter =
''. Therefore, all nodes with an initial cost center get node type
_Text. All nodes with a not-initial cost center get the type of the
association checked next, _CostCenter.
The association to the hierarchy directory is excluded from this
logic, and the association to the controlling area doesn’t play a
role because it’s defined last.
[ ! ] Typical Error Source for Hierarchy Definitions
If the association to the controlling area were defined as the first
association, all nodes would have gotten type _ControllingArea.
All nodes would get the same text in the following step, the
description of the controlling area, and the hierarchy functionality
would not work as expected.
5. Now, all nodes get a text because the node key alone isn’t very
meaningful. This text depends on the node type and is
determined via the association belonging to the node type. For
type _Text, this is standard behavior. For other node types, the
association is expected to be a foreign key association. Then,
the text can be determined via the related entity. For the cost
center, this is the related cost center text.
Now the hierarchy is sufficiently described for the infrastructure. A UI
can request the hierarchy in a form suitable for displaying from the
infrastructure, drilling into child nodes, or retrieving sums calculated
over subtrees for analytic applications.
12.2.3
Test a Hierarchy
The easiest way to test a hierarchy definition is the test environment
for analytic views, Transaction RSRTS_ODP_DIS, in SAP GUI. Its
use is explained in detail in Chapter 10, Section 10.2.2. With it, you
can test our hierarchy example as follows:
1. Ensure that the hierarchy base entity is marked as an analytic
dimension view by annotation @Analytics.dataCategory:
#DIMENSION.
2. Start Transaction RSRTS_ODP_DIS, and enter the SQL view
name of the base entity for ABAP Data Dictionary-based CDS
views, for example, “IFICOSTCENTER”, in the ODP Name field
for the cost center view. For CDS view entities, enter the entity
name. And in case the view is annotated with
@Analytics.technicalName, enter the name defined by the
annotation. Execute the transaction.
3. You’ll see the analytic model of view I_CostCenter in
Figure 12.4. In the lower-right corner, beside the line for the cost
center field, a green tree-like icon
signifies that a hierarchy
definition exists for this field. As the cost center field is the
representative key field of the view, this information refers to the
view itself—it possesses a hierarchy.
Figure 12.4
Analytic Model of View I_CostCenter
4. Start an analytic test of the view with the Standard Query
function.
5. In the multidimensional analysis shown in the next screen that
appears, group by cost center in rows.
6. Open the Properties tab in the context menu of the Cost
Center field. You’ll see the properties screen shown in
Figure 12.5.
Figure 12.5
Analytic Properties of the Cost Center Field
7. When you change to the Hierarchy tab, you can choose an
entry from the hierarchy directory (see Figure 12.6). The
example shows a hierarchy for demonstration purposes.
Note that this tab is only available if hierarchy directory entries
exist.
Figure 12.6
Hierarchy Directory
8. After confirming the chosen hierarchy, the list of cost centers is
displayed hierarchically (see Figure 12.7).
Figure 12.7
Cost Center Hierarchy
Note the artificial root node with the Not Assigned Cost Center (s)
description, which is added by the analytical infrastructure. It has all
instances of the hierarchy base entity as children, which aren’t
assigned to any node of the selected hierarchy. That way, all
selected cost centers are shown in the hierarchical presentation.
The example of hierarchy annotations showed how combinations of
annotations, which are quite simple by themselves, can model a
complex matter and control the infrastructure processing it.
Annotation-based hierarchies are tailored for use in analytic
applications, however. They don’t offer much flexibility, and there is
no option for developers to use hierarchies in their own programs.
Therefore, SAP developed a hierarchy entity in CDS, which we’ll
present in the next section.
12.3
CDS Hierarchies
Since SAP S/4HANA release 1809 (SAP_BASIS 7.53), there has been a new category of entities in
CDS that natively embeds hierarchies into the CDS syntax, offers more flexibility than annotationdefined hierarchies, can be used from ABAP programs with powerful functions, and is executed by the
SAP HANA hierarchy engine for improved performance. This section introduces these CDS hierarchies
and demonstrates their functionality with several examples.
These examples implement a simple reporting line hierarchy for the employees of an enterprise:
Employees are the nodes of the hierarchy.
The parent-child relation points from an employee to their direct manager.
12.3.1
Data for an Example of a Reporting Line Hierarchy
To show some special aspects of CDS hierarchies, we need to define appropriate business and
hierarchy data. We need one data source for the employees and their attributes, and a second data
source for the hierarchical relation of employees to their managers. We can avoid the creation of
database tables by defining the data by CDS views. View entity ZHP_Employee in Listing 12.4 provides
the employee data.
@Metadata.ignorePropagatedAnnotations: true
define view entity ZHP_Employee as select from t000
{ '0001'
as Employee,
cast( 'Anne' as text40 )
as EmployeeName,
cast( 'F'
as char1)
as Gender,
cast( 100 as abap.dec( 15, 2 )) as PartTimePercent
} where mandt = '000'
union all select from t000 { '0002' as Employee,
'Ben' as EmployeeName, 'M' as Gender, 80 as PartTimePercent }
where mandt = '000'
...
Listing 12.4
CDS View as Data Source for Employee Data
Complete the implementation of the data source by adding all remaining employees from Table 12.2.
Beside the ID of an employee and the employee’s name, there are attributes for gender and part-time
percentage.
Employee Name
Gender Part-Time Percentage
0001
Anne
F
100
0002
Ben
M
80
0003
Carmen
F
100
0004
David
M
100
0005
Elisabeth F
100
0006
Frank
M
60
0007
Greta
F
100
0008
Harald
M
80
0009
Isabell
F
90
Employee Name
Gender Part-Time Percentage
0010
Jon
M
100
0011
Kim
F
50
0012
Linus
M
100
Table 12.2
Employee Data for the Hierarchy Examples
Based on this data source, Listing 12.5 defines CDS view entity ZHI_Employee for an entity Employee,
which adds a few semantic annotations. The view also includes calculated field FullTimeEquivalent,
which will be used in a hierarchical analysis as a key figure for the volume of employment.
@ObjectModel.representativeKey: 'Employee'
define view entity ZHI_Employee as select from ZHP_Employee
{
@ObjectModel.text.element: ['EmployeeName']
@EndUserText.label: 'Employee'
key Employee,
@Semantics.text: true
@EndUserText.label: 'Name'
EmployeeName,
@EndUserText.label: 'Gender'
Gender,
@EndUserText.label: 'Part-time Percentage'
PartTimePercent,
@Aggregation.default: #SUM
@EndUserText.label: 'Full-time Equivalent'
division(PartTimePercent, 100, 2)
as FullTimeEquivalent
}
Listing 12.5
CDS View for an Employee
Now define view entity ZHP_EmployeeRelation as a data source for the parent relation, as shown in
Listing 12.6.
@Metadata.ignorePropagatedAnnotations: true
define view entity ZHP_EmployeeRelation as select from t000
{ '0001' as Employee,
'0000' as Manager
} where mandt = '000'
union all select from t000 { '0002' as Employee,
'0001' as Manager } where mandt = '000'
...
Listing 12.6
Data Source for the Parent Relation
Complete the view definition by the parent relations shown in Table 12.3.
Employee Manager
0001
0000
0002
0001
0003
0001
0004
0003
0005
0001
0006
0005
0007
0005
0008
0007
0009
0000
Employee Manager
0010
0009
0011
0009
Table 12.3
Parent Relations for the Reporting Line Hierarchy
The hierarchy resulting from these relations is shown in Figure 12.8.
Figure 12.8
Hierarchy Resulting from the Example Parent Relations
Now define CDS view entity ZHI_EmployeeRelation, as shown in Listing 12.7. This view entity adds
associations that are needed for the hierarchy definition, in particular, the self-association to the parent
nodes, the parent association.
define view entity ZHI_EmployeeRelation
as select from ZHP_EmployeeRelation
association [0..1] to ZHI_EmployeeRelation
as _Manager on $projection.Manager = _Manager.Employee
association [0..1] to ZHI_Employee
as _Employee on $projection.Employee = _Employee.Employee
{ Employee,
_Employee,
Manager,
_Manager
}
Listing 12.7
12.3.2
CDS View for the Parent Relations with Parent Association
Define the CDS Hierarchy
With the data sources prepared in the previous section, you can now define a CDS hierarchy. Create a
CDS data definition with the ABAP Development Tools (ADT) under the name
ZHI_HierarchicalEmployee01, and enter the source code from Listing 12.8. Note that the name of the
data definition and the name of the hierarchy must be identical (deviating cases in spelling of the names
are ignored). Save and activate the hierarchy like a usual CDS view. (For details on creating a CDS
view, see Chapter 1, Section 1.2.2.)
define hierarchy ZHI_HierarchicalEmployee01
as parent child hierarchy(
source ZHI_EmployeeRelation
child to parent association _Manager
start where Manager is initial
siblings order by Employee
)
{
@ObjectModel.foreignKey.association: '_Employee'
key Employee,
_Employee,
Manager,
_Manager
}
Listing 12.8
Simple CDS Hierarchy
The syntax for defining a CDS hierarchy is similar to other CDS entities. The main difference is the
definition of the data source by the key words as parent child hierarchy (…) with multiple parameters.
ZHI_HierarchicalEmployee01 uses the following:
source ZHI_EmployeeRelation
This specifies the data source of parent relations, the hierarchy data source.
child to parent association _Manager
The given self-association of the hierarchy data source, the parent association, specifies the parent
relation within the hierarchy data source.
start where Manager is initial
The given start condition of the hierarchy data source identifies the start nodes for determining the
hierarchy. Together with the start nodes, all their children, grandchildren, and so on are included in the
hierarchy. In the example, all employees with an initial manager ID (0000) are the start nodes.
siblings order by Employee
All nodes of a hierarchy are ordered sequentially by traversing the hierarchy depth first from the root
nodes, while considering the defined siblings order. A stable sequence is ensured by a strictly
monotonous siblings order, like the employee ID in our example.
[»] Limitations in the Hierarchy Syntax
Not all CDS syntax options are available in CDS hierarchies. For example, you can’t define
associations or calculated fields within the data definition of a CDS hierarchy. You can move such
definitions to the hierarchy data source, however, if you need them.
When you execute the just-defined hierarchy ZHI_HierarchicalEmployee01 and its hierarchy data source
ZHI_EmployeeRelation in the ADT data preview, you get the same results. But the results could also
deviate as the nodes of the hierarchy are determined as follows:
1. Select all rows of the hierarchy data source that fulfill the start where condition, sorted by the
siblings order. The result are the root nodes of the hierarchy. Process the root nodes in this order
according to step 2.
2. Select all children of a node by following the parent associations in the reverse direction. Sort the
children by the siblings order. Process the children recursively in this order according to step 2.
The resulting sequence of hierarchy nodes is also called the standard traversal of the hierarchy.
Depending on the start where condition and the parent relations, it’s possible that not all rows in the
hierarchy data source actually result in a node of the hierarchy. You’ll see an example in Section 12.3.5.
In strict hierarchies, not more than one hierarchy row can result from a row in the data source. In a nonstrict hierarchy, however, the same row of the hierarchy data source can result in multiple rows of the
CDS hierarchy. This sounds strange at first, so we’ll have a closer look in Section 12.3.6 and
Section 12.3.7. The behavior is a consequence of the hierarchy engine’s target to also bring non-strict
hierarchies into a strict format that can be processed more efficiently and displayed in UIs more
conveniently.
12.3.3
Hierarchy Attributes
Besides the fields of the hierarchy data source, each hierarchy node, or more precisely each row
selected from a CDS hierarchy, have further special hierarchy attributes. These can be selected by a
reserved name from the CDS hierarchy. Within the CDS hierarchy definition, the hierarchy attributes are
accessible via virtual structure $node, and you can expose them with an alias.
Let’s select and display the hierarchy attributes of our example with ABAP program
ZH_EMPLOYEE_HIER_SELECT. Create it as shown in Listing 12.9.
REPORT zh_employee_hier_select.
SELECT FROM zhi_hierarchicalemployee01
FIELDS
employee,
\_employee-employeename AS node_text,
node_id,
parent_id,
hierarchy_level,
hierarchy_tree_size,
hierarchy_rank,
hierarchy_parent_rank,
hierarchy_is_orphan,
hierarchy_is_cycle
ORDER BY hierarchy_rank
INTO TABLE @DATA(gt_hier).
cl_demo_output=>write_data( gt_hier ).
cl_demo_output=>display( ).
Listing 12.9
SQL Selection of Hierarchy Attributes in ABAP
Table 12.4 shows the program output. The first column originates from the hierarchy data source, and
the second from the employee data. All other columns show the hierarchy attributes.
EMPLOYEE NODE_
TEXT
NODE_ PARENT_ HIERARCHY_ HIERARCHY_ HIERARCHY_ HIERAR
ID
ID
LEVEL
TREE_
RANK
PAREN
SIZE
RANK
0001
Anne
0001
0000
1
8
1
0
0002
Ben
0002
0001
2
1
2
1
0003
Carmen
0003
0001
2
2
3
1
0004
David
0004
0003
3
1
4
3
0005
Elisabeth 0005
0001
2
4
5
1
0006
Frank
0006
0005
3
1
6
5
0007
Greta
0007
0005
3
2
7
5
0008
Harald
0008
0007
4
1
8
7
0009
Isabell
0009
0000
1
3
9
0
0010
Jon
0010
0009
2
1
10
9
0011
Kim
0011
0009
2
1
11
9
Table 12.4
Output of Hierarchy Attributes
The hierarchy attributes have the following semantics:
node_id
The technical identifier of a (semantic) hierarchy node used by the hierarchy engine. It’s created from
the fields of the hierarchy data source that identify the parent in the parent association, which is field
Employee in our example.
parent_id
The node_id of its parent node.
hierarchy_level
The level of the node in the hierarchy. Root nodes have level 1, their children level 2, and so on.
hierarchy_tree_size
The size of the subtree starting at a node. It’s the number of the node’s descendants plus 1.
hierarchy_rank
The hierarchy ordinal number in a CDS hierarchy. It starts at 1 and counts all hierarchy nodes
sequentially during the standard traversal. The hierarchy ordinal number is a unique identifier for the
data rows of a CDS hierarchy. It’s not stable, however, as any change to the hierarchy structure can
change the sequence of numbered nodes.
hierarchy_parent_rank
The hierarchy ordinal number of the parent node.
hierarchy_is_orphan
Has the value 1 if the node is orphaned; that is, the node has no parent node, and it’s not a root node
according to the hierarchy definition. All other nodes have a value 0. Section 12.3.5 shows an
example of orphaned nodes.
hierarchy_is_cycle
Has the value 1 if this output node closes a cycle. The (output) node then has the same node_id as
the starting (output) node of the cycle, but no children. All other nodes have a value 0. Section 12.3.7
shows an example of cycles.
12.3.4
Visualization of a Hierarchy
For our simple examples, we want to visualize the hierarchies with the ALV Tree Control, a tool of the
SAP List Viewer (ALV), as it’s easy to implement. Listing 12.10 shows an ABAP report that displays
hierarchy ZHI_HierarchicalEmployee01.
REPORT zh_employee_alv_tree.
TYPES:
BEGIN OF ty_gs_hierarchy_display,
employee
TYPE char4,
node_text
TYPE text40,
node_id
TYPE char20,
parent_id
TYPE char20,
hierarchy_level
TYPE i,
hierarchy_tree_size
TYPE i,
hierarchy_rank
TYPE i,
hierarchy_parent_rank TYPE i,
hierarchy_is_orphan
TYPE int1,
hierarchy_is_cycle
TYPE int1,
alv_node_key
TYPE salv_de_node_key,
END OF ty_gs_hierarchy_display.
TYPES: ty_gt_hierarchy_display
TYPE STANDARD TABLE OF ty_gs_hierarchy_display.
* read from hierarchy
DATA: gt_hierarchy_display TYPE ty_gt_hierarchy_display.
SELECT FROM zhi_hierarchicalemployee01
FIELDS
employee, \_employee-employeename AS node_text,
node_id, parent_id,
hierarchy_level, hierarchy_tree_size,
hierarchy_rank, hierarchy_parent_rank,
hierarchy_is_orphan, hierarchy_is_cycle
ORDER BY hierarchy_rank
INTO CORRESPONDING FIELDS OF TABLE @gt_hierarchy_display.
* display hierarchy in ALV tree
DATA: gr_tree
TYPE REF TO cl_salv_tree.
DATA: gt_hierarchy_display2
TYPE ty_gt_hierarchy_display.
DATA: lr_alv_node
TYPE REF TO cl_salv_node.
* create empty tree
cl_salv_tree=>factory( IMPORTING r_salv_tree = gr_tree
CHANGING
t_table = gt_hierarchy_display2 ).
* add nodes to the tree
DATA(gr_alv_nodes) = gr_tree->get_nodes( ).
DATA: lv_tree_text TYPE lvc_value.
LOOP AT gt_hierarchy_display
ASSIGNING FIELD-SYMBOL(<ls_node_data>).
IF <ls_node_data>-hierarchy_level = 1.
lv_tree_text = <ls_node_data>-node_text.
lr_alv_node = gr_alv_nodes->add_node(
related_node = space
relationship = cl_gui_column_tree=>relat_last_child
text
= lv_tree_text ).
<ls_node_data>-alv_node_key = lr_alv_node->get_key( ).
lr_alv_node->set_data_row( <ls_node_data> ).
ELSE.
READ TABLE gt_hierarchy_display WITH KEY
hierarchy_rank = <ls_node_data>-hierarchy_parent_rank
ASSIGNING FIELD-SYMBOL(<ls_parent_data>).
lv_tree_text = <ls_node_data>-node_text.
lr_alv_node = gr_alv_nodes->add_node(
related_node = <ls_parent_data>-alv_node_key
relationship = cl_gui_column_tree=>relat_last_child
text
= lv_tree_text ).
<ls_node_data>-alv_node_key = lr_alv_node->get_key( ).
lr_alv_node->set_data_row( <ls_node_data> ).
ENDIF.
ENDLOOP.
* set column width and labels
DATA(lr_columns) = gr_tree->get_columns( ).
lr_columns->set_optimize( 'X' ).
DATA: lr_column TYPE REF TO cl_salv_column_tree.
lr_column ?= lr_columns->get_column( 'NODE_TEXT' ).
lr_column->set_short_text('Name').
lr_column->set_technical( ).
lr_column ?= lr_columns->get_column( 'NODE_ID' ).
lr_column->set_short_text('NodeID').
lr_column ?= lr_columns->get_column( 'PARENT_ID' ).
lr_column->set_short_text('ParentID').
lr_column ?= lr_columns->get_column( 'HIERARCHY_LEVEL' ).
lr_column->set_short_text('Lvl').
lr_column ?= lr_columns->get_column( 'HIERARCHY_TREE_SIZE' ).
lr_column->set_short_text('TrSz').
lr_column ?= lr_columns->get_column( 'HIERARCHY_RANK' ).
lr_column->set_short_text('Rnk').
lr_column ?=
lr_columns->get_column( 'HIERARCHY_PARENT_RANK' ).
lr_column->set_short_text('PaRnk').
lr_column ?= lr_columns->get_column( 'HIERARCHY_IS_ORPHAN' ).
lr_column->set_short_text('Orph').
lr_column ?= lr_columns->get_column( 'HIERARCHY_IS_CYCLE' ).
lr_column->set_short_text('Cycle').
lr_column ?= lr_columns->get_column( 'EMPLOYEE' ).
lr_column->set_short_text('Employee').
lr_column ?= lr_columns->get_column( 'ALV_NODE_KEY' ).
lr_column->set_short_text('ALVKey').
lr_column->set_technical( ).
* display the tree
gr_tree->display( ).
Listing 12.10
Hierarchy Display with the ALV Tree Control
Figure 12.9 shows the output of the report.
Figure 12.9
12.3.5
Hierarchical Output with Hierarchy Attributes
Hierarchy with an Orphaned Node
Add a new row (see Table 12.5) to data source ZHP_EmployeeRelation, and reselect in the ADT data
preview from hierarchy ZHI_HierarchicalEmployee01. Although there is a parent relation for employee
0012, the employee isn’t included in the hierarchy: the node can’t be reached from the start nodes of the
hierarchy definition. According to the hierarchy definition, it’s orphaned, and orphaned nodes are
ignored by default.
Employee Manager
0012
Table 12.5
1000
Example for an Orphaned Node
You can change this default behavior via option orphans in the hierarchy definition. Add this option as
shown in Listing 12.11.
define hierarchy ZHI_HierarchicalEmployee02
as parent child hierarchy(
source ZHI_EmployeeRelation
child to parent association _Manager
start where
Manager is initial
siblings order by Employee
orphans root
)
{
@ObjectModel.foreignKey.association: '_Employee'
key Employee,
_Employee,
Manager,
_Manager
}
Listing 12.11
CDS Hierarchy with an Option for Orphaned Nodes
Adapt your ALV Tree report to hierarchy ZHI_HierarchicalEmployee02 to get the output shown in
Figure 12.10. The orphaned node was added as an additional root node, although it doesn’t match the
start condition. You can tell that it’s an orphaned node from its hierarchy attribute hierarchy_is_orphan
that has value 1 in the Orph column.
Figure 12.10
Hierarchy with an Orphaned Node
12.3.6
Hierarchy with Multiple Parent Nodes
Now we add an additional parent relation to our hierarchy example. To do so, replace the orphaned
node 0012 from the previous section in data source ZHP_EmployeeRelation by the row of Table 12.6. The
hierarchy data source then provides two rows for employee 0010 with different managers.
Employee Manager
0010
Table 12.6
0007
Example of an Additional Parent Relation
Execution of CDS hierarchies ZHI_HierarchicalEmployee01 or ZHI_HierarchicalEmployee02 will result in
error messages in the database as these hierarchy definitions don’t allow multiple parents. You have to
add option multiple parents allowed to the hierarchy definition, as shown in Listing 12.12, which defines
new CDS hierarchy ZHI_HierarchicalEmployee03.
define hierarchy ZHI_HierarchicalEmployee03
as parent child hierarchy(
source ZHI_EmployeeRelation
child to parent association _Manager
start where
Manager is initial
siblings order by Employee
multiple parents allowed
)
{ @ObjectModel.foreignKey.association: '_Employee'
Employee,
_Employee,
Manager,
_Manager
}
Listing 12.12
Hierarchy with Multiple Parent Nodes
Adapt your ALV Tree Control report to hierarchy ZHI_HierarchicalEmployee03 to get the output shown in
Figure 12.11.
Figure 12.11
Multiple Parent Nodes for Employee 0010
Two output nodes are shown for employee 0010, each below the respective parent node. If the node of
employee 0010 had its own child nodes, these would also lead to double output nodes inserted at both
positions in the output hierarchy structure. You can test this with additional rows in the hierarchy data
source, if you like.
Neither node_id nor field Employee are unique identifiers of the output nodes. Therefore, you should not
mark them as key in the hierarchy definition. The doubled output nodes get their own hierarchy ordinal
number (hierarchy_rank) 9 and 11, respectively, according to their position in the output hierarchy. As
they change with a change of hierarchy data, they are also not suited as stable keys for the CDS
hierarchy. Note that hierarchy_tree_size counts the doubled nodes twice.
This doubling of nodes leaves us with the following question, however: What is a “hierarchy node”?
Let’s distinguish two node concepts. First, every result row when selecting from a CDS hierarchy is
called an output node. This emphasizes that a CDS hierarchy is a data source. From a business
perspective, the nodes of an employee hierarchy are employees. An employee may have two managers
occasionally, but the employee himself isn’t doubled by that (even if the managers would like that). For a
good definition, we use the parent association of the hierarchy data source. This uniquely assigns a
parent node to a node. Therefore, the fields that uniquely identify a parent can be used as the key for a
semantic node. In our example, parent association _Manager is defined by condition $projection.Manager
= _Manager.Employee. Therefore, the semantic nodes are identified by field Employee, which also fits with
the business semantics of the hierarchy.
12.3.7
Hierarchy with Cycles
As a last example, let’s define a hierarchy with a cycle. To do so, change data source
ZHP_EmployeeRelation to provide only the rows of Table 12.7. Note the changed manager assignment of
employee 0001. The reporting line now includes a cycle: 0001 → 0006 → 0005 → 0001.
Employee Manager
0001
0006
0002
0001
0003
0001
0004
0003
0005
0001
0006
0005
0007
0005
0008
0007
Table 12.7
Data for a Hierarchy with a Cycle
Our first hierarchy, ZHI_HierarchicalEmployee01, shows no output for this data as the start condition isn’t
met by any row, and orphaned nodes are ignored. Our second hierarchy, ZHI_HierarchicalEmployee02,
however, causes a database error when processing the orphaned nodes in the context of the cycle.
The hierarchy engine is able to resolve cycles, but it needs a defined entry point (root node), and option
cycles breakup in the hierarchy definition. Listing 12.13 shows suitable hierarchy definition
ZHI_HierarchicalEmployee04.
define hierarchy ZHI_HierarchicalEmployee04
as parent child hierarchy(
source ZHI_EmployeeRelation
child to parent association _Manager
start where Employee = '0001'
siblings order by Employee
multiple parents allowed
cycles breakup
)
{ @ObjectModel.foreignKey.association: '_Employee'
Employee,
_Employee,
Manager,
_Manager
}
Listing 12.13
Definition of a CDS Hierarchy with Cycles
When you adapt the ALV Tree Control report to new hierarchy ZHI_HierarchicalEmployee04, it results in
the output shown in Figure 12.12. Under Employee 0006, a new output node was added for Employee
0001. This new output node indicates the end of the cycle, and its hierarchy attribute
hierarchy_is_cycle has value 1 as you can see in the Cycle column.
Figure 12.12
12.3.8
Hierarchy with Cycle at Employee 0001
Further Options for Defining Hierarchies
By now, you know the basic CDS hierarchy features and their processing by the hierarchy engine.
Several other options are available for the definition of CDS hierarchies as well, which we’ll discuss
briefly:
CDS hierarchies can have parameters. These are usually passed to parameters of the hierarchy data
source or used as filters to limit the total number of hierarchy nodes, which is the main driver of
resource consumption (memory and CPU) in hierarchy processing.
Option directory ... filter by ... indicates how a consumer should use the hierarchy. The key of a
hierarchy directory row is passed by parameters to the CDS hierarchy.
Language element period from ... to ... valid from ... to ... controls the processing of timedependent hierarchies, more precisely, time-dependent hierarchical relations. As these are mainly
non-strict hierarchies, the hierarchy engine’s capabilities for processing them are essential.
Option depth limits the hierarchy (its output nodes) to a certain maximum level.
If a hierarchy has nodes with different business semantics, for example, projects, tasks, or assigned
employees, you can indicate the different semantics by a dedicated field in the hierarchy data source.
Option nodetype identifies that field for all consumers.
With options load and cache, you can optimize your hierarchy performance.
With option generate spantree, you can control the processing of hierarchies with multiple parents.
With this option, one output node is created at most. If a node has multiple parents, the output node is
placed under the parent with the shortest distance to a root node.
[»] Further Information on CDS Hierarchy Options
You can find the latest information on these hierarchy options in the SAP documentation of CDS
hierarchies for SAP BTP, ABAP environment: http://s-prs.co/v564208.
12.3.9
CDS Hierarchies in ABAP SQL
In ABAP SQL, you can select data from CDS hierarchies, including the hierarchy attributes. Additionally,
ABAP SQL offers the powerful hierarchy navigation functions of the SAP HANA hierarchy engine. These
conveniently determine descendants, ancestors, or siblings, as well as hierarchically aggregate data.
You can even define a hierarchy directly in ABAP SQL and use it like a predefined CDS hierarchy. In
this section, we’ll show these functions with two examples: determine descendants and aggregate a
measure hierarchically.
[»] Further Information on Hierarchy Navigation Functions in ABAP SQL
You can find the complete documentation of hierarchy navigation functions in ABAP SQL in the SAP
documentation: http://s-prs.co/v564209.
In the first example, we want to determine all descendants of a node: its children, grandchildren, and so
on. We use hierarchy navigation function HIERARCHY_DESCENDANTS for that purpose, as shown in
Listing 12.14.
REPORT zh_employee_hier_descendants.
SELECT FROM HIERARCHY_DESCENDANTS(
SOURCE zhi_hierarchicalemployee01
START WHERE employee = '0005'
)
FIELDS
employee,
\_employee-employeename,
node_id, parent_id, hierarchy_level,
hierarchy_tree_size, hierarchy_rank
ORDER BY hierarchy_rank
INTO TABLE @DATA(gt_desc).
cl_demo_output=>write_data( gt_desc ).
cl_demo_output=>display( ).
Listing 12.14
Determine Descendants by a Hierarchy Navigation Function
The result of the hierarchical ABAP SQL SELECT is shown in Table 12.8. It’s a subset of all output nodes
of the hierarchy, including the nodes’ hierarchy attributes.
EMPLOYEE EMPLOYEENAME NODE_ PARENT_ HIERARCHY_ HIERARCHY_ HIERARCHY_
ID
ID
LEVEL
TREE_
RANK
SIZE
0005
Elisabeth
0005
0001
2
4
5
0006
Frank
0006
0005
3
1
6
0007
Greta
0007
0005
3
2
7
0008
Harald
0008
0007
4
1
8
Table 12.8
Descendants Determined by the Hierarchy Navigation Function
Besides the descendants navigation, similar functions exist for siblings (HIERARCHY_SIBLINGS) and for
ancestors (HIERARCHY_ANCESTORS).
In the second example, we aggregate measures along the hierarchy. We use the full-time equivalent
(FTE) of an employee as a measure and a hierarchy navigation function in ABAP SQL that also
aggregates data. Listing 12.15 shows an example with an aggregation over all descendants.
REPORT zh_hier_descendants_aggregate.
SELECT FROM HIERARCHY_DESCENDANTS_AGGREGATE(
SOURCE zhi_hierarchicalemployee01 AS h
JOIN
zhi_employee AS t ON h~employee = t~employee
MEASURES SUM( t~FullTimeEquivalent )
AS AggFullTimeEquivalent
)
FIELDS
employee, \_employee-employeename,
\_employee-parttimepercent,
AggFullTimeEquivalent
INTO TABLE @DATA(gt_aggr).
cl_demo_output=>write_data( gt_aggr ).
cl_demo_output=>display( ).
Listing 12.15
Aggregate a Measure over All Descendants
Take care to change data source ZHP_EmployeeRelation back to the rows of Table 12.3 before you
execute the program. The result of the program is shown in Figure 12.13 in a hierarchical format. At
every node, the sum of the FTEs of its descendants is shown, including the node’s own FTE. The
navigation function determines these values by an optimized calculation.
Figure 12.13
Result of a Hierarchical Aggregation over the Descendants
12.3.10
OData Service for CDS Hierarchies
Starting with release 2023, the ABAP RESTful application programming model supports the exposure of
CDS hierarchies by hierarchical OData services in OData V4. This enables the display of a hierarchy in
UIs with the SAP Fiori control tree table. The draft-base change of a hierarchy is planned for the year
2024.
This section shows how you can define an appropriate hierarchy, a projection model, a related service
definition, and a service binding.
A hierarchy has to be strict if you want to use it in an OData service. Be sure to use the hierarchy data
source ZHI_EmployeeRelation with the parent relations of Table 12.3. With that, you define a lean CDS
hierarchy ZHI_HierarchicalEmployee05 that has no other field or associations (see Listing 12.16).
define hierarchy ZHI_HierarchicalEmployee05
as parent child hierarchy(
source ZHI_EmployeeRelation
child to parent association _Manager
start where
Manager is initial
siblings order by Employee
)
{
key Employee,
Manager
}
Listing 12.16
Lean CDS Hierarchy for a Hierarchical OData Service
For good performance, it’s beneficial that the hierarchy definition doesn’t include unnecessary fields.
You can add further fields in the projection model. Define the projection model on hierarchy data source
ZHI_EmployeeRelation, as shown in Listing 12.17.
@OData.hierarchy.recursiveHierarchy:
[{ entity.name: 'ZHI_HIERARCHICALEMPLOYEE05' }]
define root view entity ZHC_HierarchicalEmployee
provider contract transactional_query
as projection on ZHI_EmployeeRelation
{ key Employee,
_Employee,
Manager,
_Manager : redirected to ZHC_HierarchicalEmployee,
_Employee.EmployeeName,
_Employee.PartTimePercent
}
Listing 12.17
CDS Projection Model for Hierarchical OData Service
Define a suitable service definition ZHUI_HIERARCHICALEMPLOYEE, as shown in Listing 12.18.
define service ZHUI_HIERARCHICALEMPLOYEE {
expose ZHC_HierarchicalEmployee as HierarchicalEmployee;
}
Listing 12.18
Service Definition for Hierarchical OData Service
Now define service binding ZHUI_HIEREMPLOYEE_V4 with binding type OData V4 – UI for the service
definition, as shown in Figure 12.14, and publish the local service endpoint.
Figure 12.14
Service Binding for Hierarchical OData Service
You can call the newly defined OData service as usual and also preview the nodes of the hierarchy in a
flat list.
This service also supports hierarchical OData requests as defined in the OASIS specification OData
Extension for Data Aggregation Version 4.0 (see Section 5.5.2 of http://s-prs.co/v564210). Hierarchical
requests are used by UI controls like the tree table to read data together with hierarchy information and
display it hierarchically.
The metadata of the service can be retrieved, for example, by the SAP Gateway Client (SAP GUI
Transaction /IWFND/GW_CLIENT) via the following request:
/sap/opu/odata4/sap/zhui_hieremployee_v4/srvd/sap/zhui_hierarchicalemployee/0001/$metadata
You find important information in the annotations of term SAP__hierarchy.RecursiveHierarchy in this
metadata, which you’ll need when developing a UI based on that hierarchy service:
The qualifier that identifies hierarchy ZHI_HierarchicalEmployee05 in our example
The property that identifies a node in hierarchy
__HierarchyPropertiesForZHI_HierarchicalEmployee05/NodeId in our example
Properties holding the hierarchical attributes of a node in the OData service (in our example, the
number of descendants of a node is given by
__HierarchyPropertiesForZHI_HierarchicalEmployee05/DescendantCount)
You can call a hierarchical OData service manually, for example, with the request shown in
Listing 12.19.
/sap/opu/odata4/sap/zhui_hieremployee_v4/srvd/sap/zhui_hierarchicalemployee/0001/HierarchicalEmployee?
$apply=com.sap.vocabularies.Hierarchy.v1.TopLevels(
HierarchyNodes=$root/HierarchicalEmployee,
HierarchyQualifier='ZHI_HierarchicalEmployee05',
NodeProperty='__HierarchyPropertiesForZHI_HierarchicalEmployee05/NodeId',
Levels=2)
Listing 12.19
Request from a Hierarchical OData Service
Hierarchical data is retrieved by a function in the OData request, such as TopLevels. It has the following
parameters:
HierarchyNodes
The requested set of hierarchy nodes. In the example, this refers to the nodes of CDS projection view
ZHC_HierarchicalEmployee, which is aliased by HierarchicalEmployee in the service definition.
HierarchyQualifier
The relevant CDS hierarchy, which is ZHI_HierarchicalEmployee05 here.
NodeProperty
OData property that uniquely identifies a hierarchy node.
Levels
Maximum hierarchy level for selecting nodes.
Listing 12.20 shows two nodes from the result of the OData request. Note the properties that are
needed for a hierarchical presentation.
{
"Employee" : "1",
"Manager" : "0",
"EmployeeName" : "Anne",
"PartTimePercent" : 100.00,
"__HierarchyPropertiesForZHI_HierarchicalEmployee05" : {
"NodeId" : "0001",
"DescendantCount" : 7,
"LimitedDescendantCount" : 3,
"DrillState" : "expanded",
"DistanceFromRoot" : 0
}
},
{
"Employee" : "2",
"Manager" : "1",
"EmployeeName" : "Ben",
"PartTimePercent" : 80.00,
"__HierarchyPropertiesForZHI_HierarchicalEmployee05" : {
"NodeId" : "0002",
"DescendantCount" : 0,
"LimitedDescendantCount" : 0,
"DrillState" : "leaf",
"DistanceFromRoot" : 1
}
}, …
Listing 12.20
Result of a Hierarchical OData Request
You already know the properties NodeId, DescendantCount, and DistanceFromRoot. The other two
properties are determined specifically for the hierarchical OData request. Property DrillState indicates
how a hierarchy node should be displayed on the UI: expanded, collapsed, or as a leaf. If a node has no
children, the property has the value leaf. If (all) children of a node are contained in the result set of the
request, the property has the value expanded; otherwise, it has the value collapsed. Property
LimitedDescendantCount also depends on the result set of the request. It provides the number of
descendants that are included in the result set.
Figure 12.15 shows the hierarchy of our example in the SAP Fiori UI control tree table.
In the development environment for apps and SAP Fiori UIs, a simple app with a tree table UI control
based on a hierarchical OData service can easily be developed. This lies beyond the scope of this book.
Figure 12.15
Display of the Hierarchy in a UI Control Tree Table
12.4
Summary
After introducing some hierarchy basics, we focused on parent-child
hierarchies. First, we covered annotation-based hierarchies and
showed their usage in analytics. Then, we showed the various
options of CDS hierarchies with many examples as well as their
usage in ABAP and in OData.
This chapter completes the introduction of the different types of CDS
entities. The next chapter will show the use of CDS models in
different search scenarios.
13 CDS-Based Search
Functionality
The range of search functions spreads from providing
admissible values for input fields up to complex evaluations
for identifying relevant information in and beyond the
company-wide data sets. This chapter explains the search
functions that are supported by core data services (CDS).
This chapter provides you with an introduction to the modeling of
CDS-based value helps and free-text search functions in OData
services that are implemented by the Service Adaptation Definition
Language (SADL) infrastructure. Handcrafted OData services as
well as OData services that are based on analytical queries (i.e.,
CDS views annotated with @Analytics. query:true or subject to CDS
provider contract analytical_query) aren’t covered in this chapter.
[»] Value Helps in Analytical Queries
The CDS-based value help support for filter variables
(@Consumption.filter…) of an analytical query is primarily derived
from the foreign key associations
(@ObjectModel.foreignKey.association…) of its data source. In
addition, you can equip the parameters of an analytical query with
specific value helps by annotating them with
@Consumption.valueHelpDefinition….
In contrast to OData services, which are implemented by SADL,
analytical queries don’t support the free-text search functionality.
In addition, the analytic engine only supports a single value help
definition for a given filter variable, whereas SADL supports
attaching multiple value helps to a single field.
Along with the SADL-based functions of OData services, we’ll also
introduce you to the enterprise search functionality.
In CDS models, value helps can be defined via dedicated value help
annotations. You’ll learn more about the corresponding modeling
options in Section 13.1.
The free-text search functionality allows you to apply entered search
terms on multiple fields of various joined data sources at the same
point in time. You can identify the relevant data records not only with
precisely formulated filter conditions but also with fuzzy selection
criteria (fuzzy search). You’ll learn how to model the free-text search
functionality in your CDS-based OData services in Section 13.2.
Value helps and free-text search functions support users in
identifying relevant records within a given application. Enterprise
search models support users in executing complex search requests,
whose results serve as starting points for entering applications. We’ll
explain in Section 13.3 how you can implement and adjust such
models.
13.1
Modeling Value Helps
In this section, we’ll discuss various modeling aspects of value helps
based on an example that consists of multiple CDS views, a service
definition, a service binding, and an OData service derived from
those.
You’ll learn how to define value help CDS views in Section 13.1.1
and how to incorporate them into CDS models that will expose value
help views in Section 13.1.2. Section 13.1.3 explains how to
combine multiple value helps for reuse. Afterward, we discuss how
to expose CDS-based value helps in OData services in
Section 13.1.4. With Section 13.1.5, we conclude this section by
exploring value help information in the metadata of OData services
and by using value helps in user interface (UI) applications.
13.1.1
Modeling Elementary Value Helps
Our example will define a value help support based on two value
help CDS views.
CDS view Z_ValueHelpViewA from Listing 13.1 defines the basis of
the first value help.
@ObjectModel.supportedCapabilities: [#VALUE_HELP_PROVIDER]
@ObjectModel.dataCategory: #VALUE_HELP
@ObjectModel.representativeKey: 'FieldA1'
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_ValueHelpViewA
as select distinct from t000
{
@EndUserText.label: 'Field A1'
key 'A1_1' as FieldA1,
@Consumption.valueHelpDefinition: [{
entity : {name
: 'Z_ValueHelpViewB',
element : 'FieldB1'} }]
@EndUserText.label: 'Field A2'
'B1_1' as FieldA2
} union select distinct from t000
{
key 'A1_2' as FieldA1,
'B1_1' as FieldA2
} union select distinct from t000
{
key 'A1_3' as FieldA1,
'B1_3' as FieldA2
}
Listing 13.1
Value Help CDS View Z_ValueHelpViewA
CDS view Z_ValueHelpViewA returns three data records in
accordance with Table 13.1.
CDS View
FieldA1 FieldA2 FieldB1 FieldB2 FieldB3
Z_ValueHelpViewA A1_1
B1_1
–
–
–
A1_2
B1_1
–
–
–
A1_3
B1_3
–
–
–
–
–
B1_1
B2_1
B3_1
–
–
B1_2
B2_2
B3_2
–
–
B1_3
B2_3
B3_3
Z_ValueHelpViewB
Table 13.1
Data Records of Value Help CDS Views
Annotation @ObjectModel.dataCategory: #VALUE_HELP or annotation
@ObjectModel. supportedCapabilities :[#VALUE_HELP_PROVIDER]
specifies that CDS view Z_ValueHelpViewA serves as a value help
provider. Annotation @ObjectModel. representativeKey… specifies the
key field that the CDS view provides a value list, that is, the field the
value help will be used for. In our case, CDS view Z_ValueHelpViewA
provides values for field FieldA1. Both these annotations document
the CDS view. They don’t have any functional impact in the context
of the value help support.
[+] Use Specialized Value Help CDS Views
To offer optimized value help support for your end users, you
should define tailored CDS value helps. Such specialized value
help CDS views delivered by SAP carry the suffix ValueHelp or VH.
In CDS view Z_ValueHelpViewA from Listing 13.1, field FieldA2 is
annotated with @Consumption.valueHelpDefinition…. This annotation
incorporates CDS view Z_ValueHelpViewB from Listing 13.2 as a
value help. Furthermore, it binds annotated local field FieldA2 to field
FieldB1 of value help CDS view Z_ValueHelpViewB. As a result, a
nested value help is defined in which a consumer of the first value
help can trigger a second value help.
[»] Value Help Annotations
You should always define value helps via annotation
@Consumption.valueHelpDefinition…. Annotation
@Consumption.valueHelp… is outdated. Due to functional
restrictions, it will no longer be used.
Listing 13.2 illustrates the definition of aforementioned CDS view
Z_ValueHelpViewB.
@ObjectModel.supportedCapabilities: [#VALUE_HELP_PROVIDER]
@ObjectModel.dataCategory: #VALUE_HELP
@ObjectModel.representativeKey: 'FieldB1'
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_ValueHelpViewB
as select distinct from t000
{
@EndUserText.label: 'Field B1'
@Consumption.valueHelpDefault.binding.usage: #FILTER_AND_RESULT
key 'B1_1' as FieldB1,
@EndUserText.label: 'Field B2'
@Consumption.valueHelpDefault.display: true
'B2_1' as FieldB2,
'B3_1' as FieldB3
} union select distinct from t000
{
key 'B1_2' as FieldB1,
'B2_2' as FieldB2,
'B3_2' as FieldB3
} union select distinct from t000
{
key 'B1_3' as FieldB1,
'B2_3' as FieldB2,
'B3_3' as FieldB3
}
Listing 13.2
Value Help CDS View Z_ValueHelpViewB
CDS view Z_ValueHelpViewB returns three records as depicted earlier
in Table 13.1. It’s classified as a value help provider similarly to CDS
view Z_ValueHelpViewA from Listing 13.1. In addition, it specifies
default settings for the value help inclusions.
Annotation
@Consumption.valueHelpDefault.binding.usage:#FILTER_AND_RESULT
defines that field FieldB1 is to be used for filtering records of the
value list as well as for returning data after a value help record was
selected. This allows you to incorporate CDS view Z_ValueHelpViewB
as value help without explicitly binding the corresponding source and
target fields if both are equally named; that is, if the source field is
named FieldB1 too.
By default, all unbound fields of value help CDS views are treated as
display attributes. If you don’t want this, you can define the display
attributes explicitly by annotating them with
@Consumption.valueHelpDefault.display:true. The remaining
unbound fields will then be suppressed in the value help.
In our case, field FieldB2 is annotated accordingly. As a result, it will
be displayed, whereas field FieldB3 won’t appear in the value help
dialog.
13.1.2
Integrating Value Help CDS Views
In this section, we demonstrate how you can integrate value help
CDS views in other CDS views for supporting your end users in
filtering records of these CDS views or in entering admissible field
values in the course of maintaining data.
The discussed example is based on CDS view
Z_ViewWithValueHelpsA from Listing 13.3.
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_ViewWithValueHelpsA
as select distinct from t000
{
key 'K1' as KeyField,
'A1_1' as FieldA,
'B1_1' as FieldB
} union select distinct from t000
{
key 'K2' as KeyField,
'A1_3' as FieldA,
'B1_3' as FieldB
} union select distinct from t000
{
key 'K3' as KeyField,
'A1_3' as FieldA,
'B1_3' as FieldB
}
Listing 13.3
Data Source of the Value Help Consuming CDS View
CDS view Z_ViewWithValueHelpsA returns three records. Within these
records, the values of field FieldA are part of the value list of field
FieldA1 of value help CDS view Z_ValueHelpViewA from Listing 13.1,
and values of field FieldB are contained in the value list of field
FieldB1 of value help CDS view Z_ValueHelpViewB from Listing 13.2
(refer also to Table 13.1).
CDS view Z_ViewWithValueHelpsA acts as a data source for the
actual value help consuming CDS view Z_ViewWithValueHelpsB from
Listing 13.4.
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_ViewWithValueHelpsB
as select from Z_ViewWithValueHelpsA
association [0..1] to Z_ValueHelpViewA as _ValueHelpViewA
on $projection.FieldA3 = _ValueHelpViewA.FieldA1
{
@UI.lineItem: [{importance: #HIGH}]
key KeyField,
@UI.lineItem: [{importance: #HIGH}]
@UI.selectionField: [{position:1}]
@Consumption.valueHelpDefinition: [{ entity : {name
'Z_ValueHelpViewA', element : 'FieldA1'} }]
FieldA,
@UI.lineItem: [{importance: #HIGH}]
@UI.selectionField: [{position:2}]
@Consumption.valueHelpDefinition: [{association :
'_ValueHelpViewA'}]
FieldA as FieldA3,
@UI.lineItem: [{importance: #HIGH}]
@UI.selectionField: [{position:3}]
@Consumption.valueHelpDefinition: [{ entity : {name
'Z_ValueHelpViewA', element : 'FieldA1'},
additionalBinding: [{ localElement : 'FieldB1',
element
: 'FieldA2',
usage
: #RESULT }]}]
FieldA as FieldA4,
@UI.lineItem: [{importance: #HIGH}]
@UI.selectionField: [{position:4}]
@Consumption.valueHelpDefinition: [{ entity : {name :
'Z_ValueHelpViewB' }}]
FieldB as FieldB1,
_ValueHelpViewA
:
:
}
Listing 13.4
Value Help Consuming CDS View
Therein value help CDS views Z_ValueHelpViewA from Listing 13.1
and Z_ValueHelpViewB from Listing 13.2 are integrated as value help
providers in various ways.
Annotation @Consumption.valueHelpDefinition.entity.name
establishes a value help support with value help CDS view
Z_ValueHelpViewA for local field FieldA. Annotation
@Consumption.valueHelpDefinition.entity.element binds annotated
local field FieldA to field FieldA1 of the value help CDS view.
Field FieldB1 is annotated similarly, establishing a value help support
with value help CDS view Z_ValueHelpViewB. However, in this case,
an explicit binding to FieldB1 of the value help CDS view is omitted.
This is because value help CDS view Z_ValueHelpViewB already
specified a corresponding default binding for its field FieldB1 (see
Listing 13.2). Note that if field FieldB would not have been renamed
to FieldB1, an explicit binding would have been required as for field
FieldA.
Field FieldA4 also leverages value help CDS view Z_ValueHelpViewA.
In comparison to field FieldA, there is an additional binding defined
via @Consumption.valueHelpDefinition.additionalBinding…. Here,
local field FieldB1 is mapped onto field FieldA2 of the value help
CDS view. The binding type is classified as #RESULT. This means that
the corresponding value of the selected record of the value help will
be transferred to local field FieldB1.
The value help support for field FieldA3 is governed by association
_ValueHelpViewA captured in its annotation
@Consumption.valueHelpDefinition. association…. In this case,
target CDS view Z_ValueHelpViewA of the association serves as a
value help provider. The binding between the local fields and the
fields of the value help CDS view is derived from the on condition of
the association; that is, local field FieldA3 is bound to field FieldA1 of
the value help CDS view.
[»] Use of Associated Value Help Views
CDS views should only be incorporated as value help providers
via associations
(@Consumption.valueHelpDefinition.association…) if the
associated CDS views are intended to be used for other purposes
than merely serving as value help providers. In UI service
definitions, the target CDS views need to be exposed explicitly to
become effective value helps.
In general, it’s recommended not to use associations but to rather
use @Consumption.valueHelpDefinition.entity… for defining the
value help support.
Besides the annotations that define its value help support, CDS view
Z_ViewWithValueHelpsB is additionally equipped with annotations that
control the rendering of SAP Fiori elements–based UIs. Annotation
@UI.selectionField… defines filter criteria, and annotation
@UI.lineItem… classifies fields to be presented as columns in a
tabular result list of a data selection.
13.1.3
Collective Value Helps
In principle, you can assign multiple value helps to a single field of a
CDS model. To achieve this functionality, you incorporate the
respective value helps via array-like annotation
@Consumption.valueHelpDefinition. If a user requests value help
support, they need to select one of the annotated value helps prior to
its execution.
If you follow this approach, you may have to repeat such
incorporations at various occurrences of the same field in different
CDS models. However, you can also outsource such value help
inclusions into a reusable collective value help that you need to
define only once. This fosters consistency and eases the
maintenance of your CDS models.
From a technical perspective, collective value helps are defined as
abstract CDS entities. Therein, you classify the field for which the
collective value help will provide value helps via annotation
@ObjectModel.collectiveValueHelp.for.element. In the collective
value help shown in Listing 13.5, field FieldD1 is classified
accordingly.
@ObjectModel.supportedCapabilities: [#COLLECTIVE_VALUE_HELP]
@ObjectModel.collectiveValueHelp.for.element: 'FieldD1'
define abstract entity Z_CollectiveValueHelp
{
@Consumption.valueHelpDefinition:[
{entity:{name:'Z_ValueHelpViewA',element:'FieldA1'}},
{entity:{name:'Z_ValueHelpViewC',element:'FieldC1'}}]
key FieldD1 : abap.char(4);
}
Listing 13.5
Collective Value Help
Referenced field FieldD1 carries annotation
@Consumption.valueHelpDefinition…, which incorporates value help
CDS view Z_ValueHelpViewA from Listing 13.1 as well as value help
CDS view Z_ValueHelpViewC from Listing 13.6 and establishes a
mapping to the value help fields FieldA1 and FieldC1. Note that to
define further mappings via annotation
@Consumption.valueHelpDefinition.additionalBinding… you could
also introduce additional fields in your collective value help CDS
model.
@ObjectModel.supportedCapabilities: [#VALUE_HELP_PROVIDER]
@ObjectModel.dataCategory: #VALUE_HELP
@ObjectModel.representativeKey: 'FieldC1'
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_ValueHelpViewC
as select distinct from t000
{
key 'A1_1' as FieldC1
} union select distinct from t000
{
key 'A1_4' as FieldC1
}
Listing 13.6
Value Help CDS View Z_ValueHelpViewC
You can embed collective value help models in the same way as
elementary value help CDS views into other CDS models, which you
expose via SADL-based OData services. However, you can’t embed
a collective value help into another collective value help; that is, you
can’t nest collective value helps.
Listing 13.7 illustrates the usages of collective value help
Z_CollectiveValueHelp from Listing 13.5 as the value help definition
source of field FieldA.
@Metadata.ignorePropagatedAnnotations: true
define view entity Z_ViewWithCollectiveValueHelps
as select from Z_ViewWithValueHelpsA
{
key KeyField,
@Consumption.valueHelpDefinition:[{entity:
{name:'Z_CollectiveValueHelp',element:'FieldD1'}}]
FieldA,
@Consumption.valueHelpDefinition:[
{entity:{name:'Z_ValueHelpViewA',element:'FieldA1'}},
{entity:{name:'Z_ValueHelpViewC',element:'FieldC1'}}]
FieldA as FieldA2
}
Listing 13.7
CDS View with Collective Value Help Support
Annotation @Consumption.valueHelpDefinition.entity.element binds
local field FieldA to field FieldD1 of collective value help
Z_CollectiveValueHelp, which in turn is bound to field FieldA1 of
value help CDS view Z_ValueHelpViewA and field FieldC1 of value
help CDS view Z_ValueHelpViewC. When deriving the corresponding
metadata of the OData service, these intermediate mapping steps
are condensed by the SADL infrastructure. As a result, field FieldA is
directly associated to the value helps and their fields. This
corresponds to the OData exposure of field FieldA2, which is
explicitly annotated accordingly in Listing 13.7.
[»] Usage of Collective Value Helps
Due to their reuse and implicit correlations, collective value helps
require a higher degree of consistency of your CDS models.
Because they are only interpreted by the SADL infrastructure, you
should carefully evaluate their usages.
13.1.4
Exposing Value Helps in OData Services
To expose value helps, you need to define an OData service binding
of type UI (see Chapter 6).
In our case, this service binding will be based on service definition
ZUI_ServiceWithValueHelps from Listing 13.8. This service definition
expose CDS view Z_ViewWithValueHelpsB as the main entity as well
as its associated CDS view Z_ValueHelpViewA as a value help
provider for property FieldA3.
define service ZUI_ServiceWithValueHelps {
expose Z_ViewWithValueHelpsB;
expose Z_ValueHelpViewA;
}
Listing 13.8
Service Definition
The UI service binding shown in Figure 13.1 will be named
accordingly and be defined based on protocol OData V4.
[»] Effect of Service Binding Type
Both the usage type and protocol version of a service binding
influence the value help support of the associated service: API
services don’t expose value helps, whereas UI services do. When
using OData V2, value help entities are added to the service itself,
whereas in OData V4 services, dedicated value help services are
created that are referenced by the properties of the main OData
service.
Figure 13.1
13.1.5
UI Service Binding of Type OData V4
Using Value Helps
Let’s now take a closer look at the effects of the different models
discussed earlier. We’ll explore the metadata of the value help and
how it works with an SAP Fiori elements-based UI application in this
section.
OData Metadata of Value Helps
Open the metadata document of the generated OData service. You
can access this metadata by calling the Service URL of the service,
which you find in the form of the service binding (refer to
Figure 13.1). Add query option /$metadata to the service URL.
Our OData service comprises two entity sets. This is shown in the
editor of the service binding (refer to Figure 13.1) as well as in the
metadata of the service. Listing 13.9 illustrates the corresponding
extract from the OData metadata document.
...
<EntityContainer …>
<EntitySet Name="Z_ValueHelpViewA"
EntityType="...Z_ValueHelpViewAType".../>
<EntitySet Name="Z_ViewWithValueHelpsB"
EntityType="... Z_ViewWithValueHelpsBType".../>
...
</EntityContainer>
...
Listing 13.9
Extract from OData Metadata: Entity Sets and Types
Let’s now focus on main entity set Z_ViewWithValueHelpsB. Its
property FieldA offers a value help support. This is expressed by its
OData annotation <Annotation
Term="SAP__common.ValueListReferences"> as illustrated by the
metadata extract shown in Listing 13.10.
...
<Annotations Target="SAP__self.Z_ViewWithValueHelpsBType/FieldA">
<Annotation Term="SAP__common.ValueListReferences">
<Collection>
<String>../../../../srvd_f4/sap/z_valuehelpviewa/0001;ps='srvdzui_servicewithvaluehelps0001';va='com.sap.gateway.srvd.zui_servicewithvaluehelps.v0001.etz_viewwithvaluehelpsb.fielda'/$metadata</String>
</Collection>
</Annotation>
</Annotations>
<Annotations Target="SAP__self.Z_ViewWithValueHelpsBType/FieldA3">
<Annotation Term="SAP__common.ValueListReferences">
<Collection>
<String>../../../../srvd_f4/sap/z_valuehelpviewa/0001;ps='srvdzui_servicewithvaluehelps0001';va='com.sap.gateway.srvd.zui_servicewithvaluehelps.v0001.etz_viewwithvaluehelpsb.fielda3'/$metadata</String>
</Collection>
</Annotation>
</Annotations>
<Annotations Target="SAP__self.Z_ViewWithValueHelpsBType/FieldA4">
<Annotation Term="SAP__common.ValueListReferences">
<Collection>
<String>../../../../srvd_f4/sap/z_valuehelpviewa/0001;ps='srvdzui_servicewithvaluehelps0001';va='com.sap.gateway.srvd.zui_servicewithvaluehelps.v0001.etz_viewwithvaluehelpsb.fielda4'/$metadata</String>
</Collection>
</Annotation>
</Annotations>
<Annotations Target="SAP__self.Z_ViewWithValueHelpsBType/FieldB1">
<Annotation Term="SAP__common.ValueListReferences">
<Collection>
<String>../../../../srvd_f4/sap/z_valuehelpviewb/0001;ps='srvdzui_servicewithvaluehelps0001';va='com.sap.gateway.srvd.zui_servicewithvaluehelps.v0001.etz_viewwithvaluehelpsb.fieldb1'/$metadata</String>
</Collection>
</Annotation>
</Annotations>
...
Listing 13.10
Extract from OData Metadata: Value Help References
This annotation points to a dedicated value help service:
../../../../srvd_f4/sap/z_valuehelpviewa/0001;ps='srvd-zui_servicewithvaluehelps0001';va='com.sap.gateway.srvd.zui_servicewithvaluehelps.v0001.etz_viewwithvaluehelpsb.fielda'/$metadata
The service is derived from value help view Z_ValueHelpViewA and is
specifically generated for annotated property FieldA. Similarly,
OData properties FieldA3, FieldA4, and FieldB1 are associated with
their value help services.
The value help service for property FieldA exposes two entity sets,
as illustrated in Listing 13.11.
<EntityContainer Name="Container">
<EntitySet Name="Z_ValueHelpViewA"
EntityType="….Z_ValueHelpViewAType"/>
<EntitySet Name="Z_ValueHelpViewB"
EntityType="….Z_ValueHelpViewBType"/>
</EntityContainer>
Listing 13.11 Extract from OData Metadata: Entity Sets of Value Help Service of
Property FieldA
Here, entity set Z_ValueHelpViewA represents the value help directly
associated with field FieldA of CDS view Z_ViewWithValueHelpsB from
Listing 13.4. Entity set Z_ValueHelpViewB represents the value help
associated with field FieldA2 of CDS view Z_ValueHelpViewA from
Listing 13.1.
In the metadata of the value help service, you also find information
about the envisioned mapping that will be applied when using the
value help functionality. This mapping is shown in the metadata
extract from Listing 13.12. It’s captured by OData annotation
<Annotation Term="SAP__common. ValueListMapping">….
<Annotations Target="SAP__ParentService.Z_ViewWithValueHelpsBType/FieldA">
<Annotation Term="SAP__common.ValueListMapping">
<Record>
<PropertyValue Property="Label" String="Value Help View"/>
<PropertyValue Property="CollectionPath"
String="Z_ValueHelpViewA"/>
<PropertyValue Property="Parameters">
<Collection>
<Record Type="SAP__common.ValueListParameterInOut">
<PropertyValue Property="LocalDataProperty"
PropertyPath="FieldA"/>
<PropertyValue Property="ValueListProperty"
String="FieldA1"/>
</Record>
<Record Type=
"SAP__common.ValueListParameterDisplayOnly">
<PropertyValue Property="ValueListProperty"
String="FieldA2"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
</Annotations>
Listing 13.12
Value Help Support for Property FieldA of Entity Z_ViewWithValueHelpsB
For property FieldA of the value help consuming entity set
Z_ViewWithValueHelpsB, entity Z_ValueHelpViewA serves as a value
help provider. In this context, property FieldA is bound to value help
property FieldA1 by defining a parameter of type input and output
(ValueListParameterInOut).
This parameter type indicates that a possible input value of the user
for OData property FieldA will be taken into account as a filter when
selecting from value help Z_ValueHelpViewA. Accordingly, it will
restrict the value list shown to the user. Furthermore, after the user
selects a data record from the value list, the value of OData property
FieldA1 will be returned as the result of the value help execution to
source property FieldA.
In contrast to value help property FieldA1, value help property
FieldA2 doesn’t have any binding. It will be included as a plain
display parameter (ValueListParameterDisplayOnly) in the value
help.
The metadata of the value help services also specifies the mapping
information for nested value helps, as shown in Listing 13.13.
<Annotations Target="SAP__self.Z_ValueHelpViewAType/FieldA2">
<Annotation Term="SAP__common.ValueListMapping">
<Record>
<PropertyValue Property="Label" String="Value Help View"/>
<PropertyValue Property="CollectionPath"
String="Z_ValueHelpViewB"/>
<PropertyValue Property="Parameters">
<Collection>
<Record Type="SAP__common.ValueListParameterInOut">
<PropertyValue Property="LocalDataProperty"
PropertyPath="FieldA2"/>
<PropertyValue Property="ValueListProperty"
String="FieldB1"/>
</Record>
<Record Type=
"SAP__common.ValueListParameterDisplayOnly">
<PropertyValue Property="ValueListProperty"
String="FieldB2"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
</Annotations>
Listing 13.13 Value Help Support for Property FieldA2 of OData Entity
Z_ValueHelpViewA
According to this OData metadata, property FieldA2 of entity
Z_ValueHelpViewA is assigned value help entity Z_ValueHelpViewB.
Property FieldA2 is bound to value help property FieldB1 as input
and output parameter (ValueListParameterInOut). Value help
property FieldB2 is display only (ValueListParameterDisplayOnly).
Note that field FieldB3 of CDS view Z_ValueHelpViewB from
Listing 13.2 isn’t defined as an additional display parameter because
field FieldB1 was explicitly annotated with
@Consumption.valueHelpDefault.display:true. This annotation
switches the determination of display-only parameters from the
default determination logic to explicitly annotated fields.
The value help service of property FieldA3 looks similar to the
corresponding service of property FieldA. However, note that if target
CDS view Z_ValueHelpViewA of association _ValueHelpViewA of CDS
view Z_ViewWithValueHelpsB from Listing 13.4 would not have been
explicitly added to the service definition from Listing 13.8, property
FieldA3 would not offer a value help support.
Overall, the value help support of property FieldA4 corresponds to
the value help support of property FieldA too. However, there is a
different binding type for property FieldA2 of the value help entity
Z_ValueHelpViewA, as illustrated in Listing 13.14. This results from
annotation @Consumption.
valueHelpDefinition.additionalBinding.usage : #RESULT defined
earlier in Listing 13.4.
<Annotations Target="SAP__ParentService.Z_ViewWithValueHelpsBType/FieldA4">
<Annotation Term="SAP__common.ValueListMapping">
<Record>
<PropertyValue Property="Label" String="Value Help View"/>
<PropertyValue Property="CollectionPath"
String="Z_ValueHelpViewA"/>
<PropertyValue Property="Parameters">
<Collection>
<Record Type="SAP__common.ValueListParameterInOut">
<PropertyValue Property="LocalDataProperty"
PropertyPath="FieldA4"/>
<PropertyValue Property="ValueListProperty"
String="FieldA1"/>
</Record>
<Record Type="SAP__common.ValueListParameterOut">
<PropertyValue Property="LocalDataProperty"
PropertyPath="FieldB1"/>
<PropertyValue Property="ValueListProperty"
String="FieldA2"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
</Annotations>
Listing 13.14 Value Help Support for Property FieldA4 of OData Entity
Z_ViewWithValueHelpsB
Based on the mapping definition from Listing 13.14, property FieldA2
serves as an output parameter (ValueListParameterOut), which is
bound to property FieldB1 of the value help consuming entity
Z_ViewWithValueHelpsB. In other words, the value of property FieldA2
of the selected value help record will be returned to property FieldB1.
The value help service of property FieldB1 consists of a single entity
set, Z_ValueHelpViewB, as shown in the OData metadata extract from
Listing 13.15.
...
<EntityContainer Name="Container">
<EntitySet Name="Z_ValueHelpViewB"
EntityType="....Z_ValueHelpViewBType"/>
</EntityContainer>
...
Listing 13.15
Entity Sets of Value Help Service of Property FieldB1
The mapping information for this value help entity is shown in
Listing 13.16.
<Annotations Target="SAP__ParentService.Z_ViewWithValueHelpsBType/FieldB1">
<Annotation Term="SAP__common.ValueListMapping">
<Record>
<PropertyValue Property="Label" String="Value Help View"/>
<PropertyValue Property="CollectionPath"
String="Z_ValueHelpViewB"/>
<PropertyValue Property="Parameters">
<Collection>
<Record Type="SAP__common.ValueListParameterInOut">
<PropertyValue Property="LocalDataProperty"
PropertyPath="FieldB1"/>
<PropertyValue Property="ValueListProperty"
String="FieldB1"/>
</Record>
<Record Type=
"SAP__common.ValueListParameterDisplayOnly">
<PropertyValue Property="ValueListProperty"
String="FieldB2"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
</Annotations>
Listing 13.16 Value Help Support for Property FieldB1 of OData Entity
Z_ViewWithValueHelpsB
Local property FieldB1 is bound onto equally named value help
property FieldB1 as an input and output parameter
(ValueListParameterInOut). This binding originates from the default
setting (@Consumption.valueHelpDefault.binding.usage:
#FILTER_AND_RESULT) of field FieldB1 in CDS view Z_ValueHelpViewB
from Listing 13.2. Note that if these OData properties and CDS fields
weren’t named equally, the value help binding information of field
FieldB1 of CDS view Z_ViewWithValueHelpsB from Listing 13.4 would
be incomplete.
As discussed previously, only property FieldB2 of value help entity
Z_ValueHelpViewB becomes a display parameter
(ValueListParameterDisplayOnly) due to its explicit annotation
@Consumption.valueHelpDefault.display:true.
Value Helps in UI Applications
The consumers of OData services are expected to interpret the
value help annotations, which are contained in the OData metadata
documents. Based on these OData annotations, they will offer
suitable value help support for end users. Accordingly, these
annotations are supported by SAP Fiori elements-based UI
applications.
In this section, we demonstrate the effects of the various parameter
types (input, output, display-only) on an SAP Fiori elements-based
UI application.
Select entity set Z_ViewWithValueHelpsB in the editor of service
binding ZUI_SERVICEWITHVALUEHELPS. Click the Preview button to
launch the UI application shown in Figure 13.2.
Figure 13.2
UI Application Immediately after Its Launch
Now, trigger the value help functionality of the FieldA filter by
clicking the corresponding button
dialog from Figure 13.3.
. This opens the value help
The records of value help CDS view Z_ValueHelpViewA from
Listing 13.1 are displayed showing values for both its columns: Field
A1 and Field A2.
Figure 13.3
Value Help of the FieldA Filter Immediately after Its Launch
Property FieldA2 of the value help service offers its own value help.
You can call it by clicking the
button of the Field A2 filter in
Figure 13.3. As a result, a new value help dialog appears. Select the
first record, as shown in Figure 13.4.
Figure 13.4
Value Help of the Field A2 Filter after Selecting the First Record
After clicking OK, the selected B1_1 value is transferred to the Field
A2 filter of the first value help dialog, reducing the list of selectable
records (Items) accordingly (see Figure 13.5).
Figure 13.5
Value Help of the FieldA Filter after Using Nested Value Help
Again, select the first record (see Figure 13.6).
Figure 13.6
Value Help of the FieldA Filter after Selecting the First Record
Click OK to transfer the A1_1 value from the Field A1 column to the
FieldA filter, as shown in Figure 13.7.
Figure 13.7
UI Application after Executing FieldA Filter Value Help
Execute the value help of the FieldA4 filter in the same way. In
comparison to the execution of the value help of the FieldA filter, not
only is the FieldA4 filter populated with the A1_1 value but also the
FieldB1 filter is filled with the B1_1 value from the selected value
help record (see Figure 13.8). This occurs because property FieldB1
is bound as an output parameter in the corresponding value help
service (see Listing 13.14).
Figure 13.8
UI Application after Executing the FieldA4 Filter Value Help
As a last step, open the value help for the FieldB1 filter. As shown in
Figure 13.9, the already-entered B1_1 filter value is transferred as a
filter and result parameter to the Value Help View dialog.
Figure 13.9
Value Help of the FieldB1 Filter Immediately after Its Launch
13.2 Free-Text Search Functionality in
OData Services
You can equip your CDS view models with free-text search
capabilities by annotating them with corresponding annotations of
domain Search.
Listing 13.17 illustrates an example of such a CDS view.
@Metadata.ignorePropagatedAnnotations: true
@Search.searchable: true
define view entity Z_ViewWithSearchSupport
as select from ZI_SalesOrderItem
{
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 0.4
@Search.ranking: #HIGH
key SalesOrder,
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 0.7
@Search.ranking: #HIGH
key SalesOrderItem,
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 1.0
@Search.ranking: #MEDIUM
Product,
_Product.ProductType,
@Semantics.quantity.unitOfMeasure: 'OrderQuantityUnit'
OrderQuantity,
OrderQuantityUnit
}
Listing 13.17
CDS View with Search Annotations
At the header level, this CDS view is annotated with
@Search.searchable:true. This annotation makes CDS view
Z_ViewWithSearchSupport a data source for applying a free-text
search.
You use annotation @Search.defaultSearchElement:true for marking
those fields of the CDS view whose values are to be evaluated in the
context of a search request. In the given case, fields SalesOrder,
SalesOrderItem, and Product are annotated as search fields. When
executing a search request, these fields are included in the
evaluation logic, whereas, for example, field ProductType doesn’t
influence the search result.
[»] Use of Associated Search Fields
Besides local search fields, you can also include search fields of
associated search views into the search scope of your CDS view
by annotating the corresponding association with
@Search.defaultSearchElement:true. However, note that this might
cause unwanted side effects as well as performance and memory
issues specifically if you can’t control the settings of the associated
CDS views.
Furthermore, note that besides this explicit inclusion, there are
also implicit automated inclusions of searchable texts in the search
scope by SADL.
In many cases, search terms won’t be mapped exactly to the values
of the data sources within search requests, but a certain degree of
fuzziness will be allowed for incorporating values that are similar to
the search terms in the admissible search results. For specifying this
fuzziness, you can annotate the relevant search fields with
@Search.fuzzinessThreshold…. The permitted value range for this
fuzziness classification is between 0 and 1. The higher the specified
value is, the more exact the search term must match the compared
value to include the corresponding data record in the results list of
the search.
In Listing 13.17, field SalesOrder has the lowest fuzziness value of
0.4, whereas field SalesOrderItem has a middle value of 0.7, and
field Product has the highest value of 1. To make the effect of these
settings evident, take a look at the four sample data records from
Table 13.2, which need to be persisted in database table
ZSALESORDERITEM.
Data Record SalesOrder SalesOrderItem Product …
1
S1
000010
P1
–
2
S1
000020
P2
–
3
S2
000010
P1
–
4
S2
000020
P3
–
Table 13.2 Data Records of Database Table ZSALESORDERITEM and CDS View
Z_ViewWithSearchSupport
For performing search requests, you need to expose CDS view
Z_ViewWithSearchSupport from Listing 13.17 as an entity of an OData
service. You can achieve this by defining service definition
ZUI_ServiceWithSrchSupport from Listing 13.18.
define service ZUI_ServiceWithSrchSupport {
expose Z_ViewWithSearchSupport;
}
Listing 13.18
Service Definition
Use this service definition for creating UI service binding
ZUI_SERVICEWITHSRCHSUPPORT based on OData V4. Once created,
activate and publish the service.
Afterward, request the metadata of the OData service via the
displayed Service URL
…/sap/opu/odata4/sap/zui_servicewithsrchsupport/srvd/sap/zui_serv
icewithsrchsupport/0001 by appending /$metadata. Therein, you find
entity set Z_ViewWithSearchSupport, which is derived from the equally
named CDS view Z_ViewWithSearchSupport. It supports a free-text
search function and carries OData property <PropertyValue
Property="Searchable" Bool="true"/>. Listing 13.19 illustrates the
corresponding metadata of the OData service.
…
<EntityContainer Name="Container">
<EntitySet Name="Z_ViewWithSearchSupport"
EntityType="...Z_ViewWithSearchSupportType"/>
</EntityContainer>
...
<Annotations Target="SAP__self.Container/Z_ViewWithSearchSupport">
<Annotation Term="SAP__capabilities.SearchRestrictions">
<Record>
<PropertyValue Property="Searchable" Bool="true"/>
...
</Record>
</Annotation>
...
</Annotations>
...
Listing 13.19
OData Entity Set with Search Help Support
When executing a search request of form
…/sap/opu/odata4/sap/zui_servicewithsrchsupport/srvd/sap/zui_serv
icewithsrchsupport/0001/Z_ViewWithSearchSupport?$search=
<SearchTerm> by leveraging the OData entity set that is generated
from Listing 13.17, this leads to the results displayed in Table 13.3,
depending on applied search term <SearchTerm>.
Search Term Data Records in Search Result
S1
1, 2, 3, 4
S
1, 2, 3, 4
2
3, 4
P
–
P2
2
Search Term Data Records in Search Result
000010
1, 2, 3, 4
Table 13.3 Results of the Search Request Depending on the Search Term (Data Records
from Table 13.2)
As depicted, a search request with term S1 returns the same four
data records as a search request with S, even though S1 precisely
matches the field value of two data records only. In contrast, a
search request with search term 2 only returns the data records 3
and 4 that contain value S2 for field SalesOrder, even though the
second data record contains the number 2 in the values of fields
SalesOrderItem and Product too.
If the search is executed with search term P, the selection result
remains empty. If value P2 of field Product of the second data record
is used as a search term, this data record is transferred to the search
result due to an exact match. A search with term 000010 returns all
four data records, even though in an exact search, only data records
1 and 3 should appear in its result.
You can use ranking @Search.ranking… for specifying a relative
weight of the search results for the individual fields that are
evaluated in a search request. In the given case, values for fields
SalesOrder and SalesOrderItem, which match the search term
considering the permitted fuzziness, are to be weighted higher (HIGH)
than the corresponding values of field Product (MEDIUM).
[»] Using Search Annotations
To get transparent and predictable results for search requests, you
must adapt the relevant search fields and their properties to the
specific use cases. This holds true especially for the admissible
fuzziness, which is defined by annotation
@Search.fuzzinessThreshold, and for the weighting of the search
results, which is defined by annotation @Search.ranking. These
annotations can usually only be defined meaningfully in an
application-specific CDS view and not in a reused CDS view.
From a technical perspective, the free-text search functionality is
based on function contains of the SAP HANA database. The ABAP
infrastructure automatically applies it to a search request, taking into
account the search annotations of the CDS views.
To ensure that function contains can be used for the search fields of
a CDS view, the implementation of the CDS view is subject to
several technical restrictions. For example, fields whose values are
calculated can’t be included in a contains statement. If, for example,
field SalesOrderItem in Listing 13.17 was changed by applying a
concat operation in accordance with Listing 13.20, the search
function discussed earlier would no longer work. Instead, a runtime
error occurs when processing a corresponding search request.
...
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 0.7
@Search.ranking: #HIGH
key concat( SalesOrder, SalesOrderItem ) as SalesOrderItem,
...
Listing 13.20
Missing Support for Search Fields Whose Values Are Calculated
[ ! ] Check Search Function
In particular, for complex CDS view stacks, you should carefully
check whether a free-text search help can be offered. Refer to the
documentation of the SAP HANA contains feature at http://sprs.co/v564211 to get more information about related restrictions.
You should also test the search functionality of your OData
services. Alternatively, you can directly execute contains queries
on the SAP HANA database based on the relevant search fields.
Note that such tests are a necessary precondition but not a
guarantee for a successful application of the search function
because the occurrence of runtime errors can also depend on the
concrete data constellations.
If runtime errors occur only because of using certain fields in the
search, you should remove these from the search scope by
removing or commenting out their search annotations, as illustrated
in Listing 13.21.
To still be able to implement the required search functionality, you
can replace the affected search fields in some cases with suitable
helper fields for the search. Listing 13.21 shows an example of such
a helper field: SalesOrderItemForSearch. This helper field duplicates
the base field of key field SalesOrderItem, which is converted by a
concat operation. In addition to the required search annotations, it
also has annotation @Consumption.hidden:true. This annotation
causes the additional field only to be used as an ABAP-internal
implementation detail and prevents its exposure as a property in the
generated OData service.
...
//@Search.defaultSearchElement: true
//@Search.fuzzinessThreshold: 0.7
//@Search.ranking: #HIGH
key concat( SalesOrder, SalesOrderItem ) as SalesOrderItem,
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 0.7
@Search.ranking: #HIGH
@Consumption.hidden:true
SalesOrderItem as SalesOrderItemForSearch,
...
Listing 13.21
Functionality
Helper Field SalesOrderItemForSearch Supporting the Search
If you’ve implemented a CDS model in such a way that it doesn’t
support search functions in general, you should remove all of its
search annotations.
You can also enrich value helps with free-text search capabilities.
For example, you can equip value help CDS view Z_ValueHelpViewB
from Listing 13.2 with search annotations, as illustrated in
Listing 13.22. Note that the given example will only illustrate
technical aspects of the search help support in value helps: because
the search functionality can’t be applied successfully on the
calculated fields themselves, which value help Z_ValueHelpViewB is
based on, field FieldB4, whose value originates from the underlying
database table, is defined as a search field, even though its values
might not fit the values of the other columns.
@ObjectModel.supportedCapabilities: [#VALUE_HELP_PROVIDER]
@ObjectModel.dataCategory: #VALUE_HELP
@ObjectModel.representativeKey: 'FieldB1'
@Metadata.ignorePropagatedAnnotations: true
@Search.searchable: true
@Consumption.ranked:true
define view entity Z_ValueHelpViewB
as select from t000
{
@EndUserText.label: 'Field B1'
@Consumption.valueHelpDefault.binding.usage: #FILTER_AND_RESULT
key 'B1_1' as FieldB1,
@EndUserText.label: 'Field B2'
@Consumption.valueHelpDefault.display: true
'B2_1' as FieldB2,
'B3_1' as FieldB3,
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 0.8
@Search.ranking: #HIGH
@Consumption.hidden: true
t000.logsys as FieldB4
}
where t000.mandt = $session.client
union select from t000
{
key 'B1_2' as FieldB1,
'B2_2' as FieldB2,
'B3_2' as FieldB3,
t000.logsys as FieldB4
}
where t000.mandt = $session.client
union select from t000
{
key 'B1_3' as FieldB1,
'B2_3' as FieldB2,
'B3_3' as FieldB3,
t000.logsys as FieldB4
}
where t000.mandt = $session.client
Listing 13.22 CDS View Z_ValueHelpViewB from Listing 13.2 with Search Field and
Search Annotations
[»] Sort Order of Value Help Records
You can instruct the SADL infrastructure to consider the ranking of
the search results specified by annotation @Search.ranking by
annotating the CDS view with @Consumption.ranked :true.
However, you should consider the potential performance
implications of this functionality.
After you’ve activated search-enabled CDS view Z_ValueHelpViewB,
you’ll notice that the corresponding metadata of the value help
service of property FieldB1 from Listing 13.16 will be enhanced
automatically, as illustrated in Listing 13.23.
...
<Annotations Target="SAP__self.Container/Z_ValueHelpViewB">
<Annotation Term="SAP__capabilities.SearchRestrictions">
<Record>
<PropertyValue Property="Searchable" Bool="true"/>
...
</Record>
</Annotation>
...
</Annotations>
...
Listing 13.23 Extract of OData Metadata: Value Help of Property FieldB1 with Free-Text
Search Function
Its OData entity set Z_ValueHelpViewB now supports free-text search
requests (<PropertyValue Property="Searchable" Bool="true"/>).
In the UI, search capabilities are reflected, for example, by an
additional basic search field in value help dialogs, as illustrated by
Figure 13.10 (compare the layout with Figure 13.9).
Figure 13.10
Value Help of FieldB1 Filter with Search Field
13.3
Enterprise Search Functionality
Enterprise search models are defined as CDS views. These CDS
views incorporate all data that is relevant for processing the
envisioned search requests as well as for presenting their results.
Within the implementation of these CDS views, only a restricted set
of SQL functions may be used. For example, it’s admissible to define
simple projections of database tables and simple joins. However,
you’re not allowed to use calculated fields. Note that due to these
limitations, enterprise search models are defined separated from the
regular virtual data model (VDM) CDS views in SAP S/4HANA.
In Section 13.3.1, you learn how you can create an enterprise search
model in the ABAP Development Tools (ADT) environment. In
Section 13.3.2, we present an example of using key user tools to
adapt enterprise search models that are shipped by SAP.
13.3.1
Define Enterprise Search Models
Enterprise search models are defined by annotation
@EnterpriseSearch. model:true.
[»] Classification of Enterprise Search Models
When creating new enterprise search models in the ADT
environment, we recommend you annotate them with
@EnterpriseSearch.model:true and leverage further annotations of
domain EnterpriseSearch for capturing enterprise-specific
functionality.
In principle, enterprise search models can be declared by
annotation @EnterpriseSearch.enabled:true too. However, using
this annotation requires some more profound knowledge about the
interplay of annotations of domain EnterpriseSearch with other
domains for realizing the desired functionality. We’ll show such
correlations briefly in Section 13.3.2.
Within enterprise search CDS models, you can relate records of
multiple data sources by using left outer joins and ignoring potential
cardinality changes. The granularity of the search result is
determined by the fields listed in annotation
@EnterpriseSearch.resultItemKey. The enterprise search
infrastructure condenses the found instances accordingly. The
search logic itself is based on search fields defined by annotation
@EnterpriseSearch.freeStyleField…. Fields that are relevant for
displaying the search result are annotated with
@EnterpriseSearch.responseField…. Further annotations of domain
EnterpriseSearch allow you to refine the functionality of your
enterprise search model.
Listing 13.24 illustrates sample enterprise search CDS model
ZESH_S_SalesOrder.
@AccessControl.authorizationCheck: #CHECK
@Metadata.ignorePropagatedAnnotations: true
@EnterpriseSearch.model: true
@EnterpriseSearch.modelName: 'Sales Order'
@EnterpriseSearch.modelNamePlural: 'Sales Orders'
@EnterpriseSearch.resultItemKey: ['SalesOrder']
@EnterpriseSearch.title: { titleField: 'SalesOrder' }
@Consumption.semanticObject: 'SalesOrder'
define root view entity ZESH_S_SalesOrder
as select from zsalesorder
left outer to many join zsalesorderitem
on zsalesorderitem.salesorder = zsalesorder.salesorder
left outer to one join zproduct
on zproduct.product = zsalesorderitem.product
{
@EnterpriseSearch.freeStyleField: { withAutoCompletion: true, importance:
#HIGH }
key zsalesorder.salesorder
as SalesOrder,
key zsalesorderitem.salesorderitem
as SalesOrderItem,
@EnterpriseSearch.navigation.intentBased.semanticObject: 'Product'
@EnterpriseSearch.responseField.standard: {displayPosition: 1}
@EnterpriseSearch.freeStyleField: { withAutoCompletion: true, importance:
#MEDIUM }
zsalesorderitem.product
as Product,
@EnterpriseSearch.responseField.standard: {displayPosition: 2}
zproduct.product_type
as ProductType
}
Listing 13.24
Enterprise Search Model ZESH_S_SalesOrder
CDS view ZESH_S_SalesOrder is classified by annotation
@EnterpriseSearch. model :true as an enterprise search model. It
selects data from multiple joined database tables. Instances returned
by enterprise search are defined on the sales order headers level as
specified by annotation @EnterpriseSearch.resultItemKey :
['SalesOrder'], even though from a mere technical perspective,
CDS view ZESH_S_SalesOrder returns data records on the granularity
of sales order items. Both fields SalesOrder and Product are
evaluated during search processing
(@EnterpriseSearch.freeStyleField …). Remaining annotations of
domain EnterpriseSearch control the visualization and integration of
the enterprise search model.
13.3.2
Adapt Enterprise Search Models from SAP
The key user application Manage Enterprise Search Models allows
you to adapt enterprise search models from SAP to your needs as
well as to create enterprise search models for your own data models.
It supports you through guided interactions in maintaining the
modeling information, including required annotations. In addition, it
offers technical validations of the maintained enterprise search
models.
In the following, we focus on adapting an enterprise search model
shipped by SAP. Listing 13.25 shows CDS view ESH_S_PRODUCT,
which we’ll use as an example.
...
@ObjectModel.semanticKey: ['PRODUCT']
@ObjectModel.supportedCapabilities: [#ENTERPRISE_SEARCH_PROVIDER]
@Metadata.allowExtensions: true
@Search.searchable: true
@EnterpriseSearch.enabled: true
@Metadata.ignorePropagatedAnnotations: true
define view ESH_S_PRODUCT ...
as select from ESH_N_PRODUCT...
{
...
key PRODUCT,...
@Search.defaultSearchElement: true
CHARACTERISTIC,...
}
Listing 13.25
Enterprise Search Model ESH_S_PRODUCT
CDS view ESH_S_PRODUCT is defined as an enterprise search model by
its annotation @EnterpriseSearch.enabled:true. According to
annotation @ObjectModel. semanticKey, the selected records will be
compressed onto the granularity of field PRODUCT. Field
CHARACTERISTIC is defined by its annotation @Search.
defaultSearchElement :true as a relevant search field.
We want to disable this predefined search field. To accomplish this,
log on to your SAP S/4 HANA system. Launch the key user
application Manage Enterprise Search Models. Search for the
Products model. Click on the corresponding table record (last row in
Figure 13.11).
Figure 13.11
Search for Enterprise Search Model Products
Switch to edit mode by clicking Modify. Filter the displayed field list
by input Characteristic, as shown in Figure 13.12.
Click on field CHARACTERISTIC. Deactivate the search
functionality of this field by switching the Field Enabled toggle
shown at the bottom right of Figure 13.13.
Figure 13.12
Details of Enterprise Search Model Products
Figure 13.13
Products
Properties of Field CHARACTERISTIC of Enterprise Search Model
After saving and activating your changes using Save and Activate,
field CHARACTERISTIC is no longer relevant for the search processing.
In the background, the key user application created and activated a
CDS metadata extension, which is shown in Listing 13.26.
@Metadata.layer: #CUSTOMER
annotate view ESH_S_PRODUCT with
{
@Search.defaultSearchElement: false
...
CHARACTERISTIC;
}
Listing 13.26
CDS Metadata Extension of CDS View ESH_S_PRODUCT
This CDS metadata extension annotates field CHARACTERISTIC with
@Search. defaultSearchElement :false overruling the SAPpredefined usage of this field.
13.4
Summary
In this chapter, we discussed OData services, which are
implemented by SADL. First, we introduced CDS-based value helps.
These value helps can be defined by leveraging specific value help
annotations. Both elementary as well as collective value helps may
be defined.
You also learned about free-text search functions. They can be used
in combination with value helps too, improving the user experience.
However, the usage of the search functionality is subject to several
technical restrictions that you should carefully evaluate before
enabling it.
This also holds true for enterprise search–related functions. You
learned how enterprise search models can be defined and adapted.
In the next chapter, you’ll get an overview of the lifecycle and stability
aspects of CDS models.
14
Lifecycle and Stability
As a user of SAP software, you want to leverage the SAP
development objects in your own projects. Stability contracts
and a defined lifecycle provide a reliable foundation for your
own developments and enable SAP to make further
innovations at the same time.
The development of software only becomes efficient through the
integration and use of external components. In the case of business
software, the data models and development objects developed by
the software provider are often used. Core data services (CDS)
models allow easy reuse of data and semantics because they can be
combined in flexible ways.
This brings the classic conflict of objectives to CDS: the developer of
a development object wants or needs to further develop it, integrate
new requirements, and make innovations due to continuously
changing business requirements. The user, on the other hand,
expects stability because any change to the object used could
invalidate their own developments, so that they must make
adjustments. This is not only costly and time-critical for the user, but
could also even lead to downtime, which must be strictly avoided.
For this reason, for CDS models and other ABAP development
objects, SAP introduced stability contracts (or release contracts) for
several usage scenarios and a lifecycle that manages the validity of
a stability contract for individual models or objects. The lifecycle
guarantees periods of stability, starting with the release of an object.
The lifecycle also covers a deprecation of objects when new,
incompatibly changed versions are introduced and gives users
sufficient time for adaptations.
CDS models can be applied for various tasks. Supported capabilities
provide an overview of a model’s intended usages. All released CDS
models are annotated with their intended and supported capabilities.
Section 14.1 gives an overview of different stability contracts, and
Section 14.2 explains the lifecycle of development objects with
release and deprecation. Section 14.3 covers details of the
deprecation process, and, finally, Section 14.4 presents the current
list of possible supported capabilities.
14.1
Stability Contracts
Stability contracts specify which changes to a released development
object are considered compatible and which are not. There is no
absolute stability, and released objects can change their
compatibility. A user must accept compatible changes, for example,
a new optional field; their developments must also work error-free
with the new field. If both the provider (SAP) and the user comply
with the rules of the stability contract, regressions can be avoided
during an upgrade.
There are different stability contracts for different scenarios, each
with its own compatibility requirements. For example, adding a new
optional field to a structure is compatible and thus allowed in most
but not all stability contracts.
At the time of writing, there are five different stability contracts for
SAP development objects. Table 14.1 gives an overview of the
contracts. You’ll find documentation for stability contracts at the SAP
Help Portal of SAP Business Technology Platform (SAP BTP) at
http://s-prs.co/v564212.
Stability Scenario
Contract
Development
Objects
Used By
C0
Extension of an object
CDS, ABAP,
and ABAP
Data
Dictionary
objects
Customers
and
partners
C1
Development of new
software objects in an
ABAP system
CDS, ABAP,
and ABAP
Data
Dictionary
objects
Customers
and
partners
C2
Development of stable
external application
programming interfaces
(APIs)
CDS views
and service
bindings
Only SAP
internally
C3
Storage of configuration
settings
Database
tables and
customizing
views
Customers
and
partners
C4
Use of ABAP classes,
interfaces, and business
add-in (BAdI) definitions in
ABAP-Managed Database
Procedures (AMDP)
ABAP classes, Customers
interfaces and and
AMDP-related partners
BAdI
definitions
Table 14.1
Stability Contracts
You’ll find a rough overview of allowed or forbidden changes per
stability contract in Table 14.2. Contracts C3 and C4 are reserved for
specific scenarios and included for completeness. Generally
forbidden is a change of behavior or semantics of the object. What
this exactly means depends on the type of development object. A
CDS view in contract C1, for example, must return the same result
set for a fully qualified SQL request (i.e., without a * selection) before
and after a change if the persisted data is unchanged.
Change
C0
Rename or delete the object.
Forbidden
Rename or delete a field.
Change the technical type.
Reduce the length or the number
of decimals.
Limited
Increase a field length or expand
the value range of numbers.
Possible
Add an optional field.
Possible
Rename or delete a parameter.
Limited
N/A
Rename, delete, or essentially
change an association that is
subject to the stability contract.
Limited
N/A N/A
Add an optional association.
Possible
N/A N/A
Table 14.2
C1 C2 C3
C4
Possible and Forbidden Changes under Stability Contracts
Associations connect two objects, mainly CDS views. Their definition
depends on the properties of both objects. Therefore, an association
can only be stable if both objects, the source and the target of an
association, are stable. A stability contract Cx therefore only holds
for an association if Cx holds for both its source and its target. Only
in this case can the association be used safely.
The existence and the values of annotations in CDS models plays an
important role for their semantics and behavior. Stability contracts
therefore also control changes of annotations or even forbid their
usage. The details of what is allowed or not is specified for contracts
C0, C1, and C2 in the annotation definition (see Chapter 4,
Section 4.1). For that purpose, meta-annotations
@CompatibilityContract are used.
Stability contract C1 (and similarly C3) has special rules for
extending the length of a field. In justified cases, SAP wants to
extend fields, for example, by expanding the value range of amounts
and quantities, or increasing the length of codes, identifiers or text
fields, without having to deprecate the existing fields or development
objects. To be prepared for such cases, you should type your own
variables or data by referencing the SAP objects you’re reusing.
Contract C0 regulates the extension of SAP development objects
with custom fields, associations and other substructures of a
development object. We’ll discuss contract C0 in detail in
Chapter 15. Contract C0 can be combined with other contracts.
Stability contract C1 is the main contract for customers or partners
developing their own software in SAP S/4HANA or the SAP BTP,
ABAP environment. C1-released CDS views are the only stable SQL
access to data, as the database tables aren’t released for access.
Note that contract C1 only regulates the signature of a development
object and its exposed elements or methods, but not its inner
implementation. That way, SAP can further optimize the
implementation. SAP could, for example, migrate data to optimized
persistence models while keeping the released CDS views as a
stable way to access the data.
Stability contract C2 is used by SAP internally to support the
development of stable APIs. Development objects released under
this stability contract aren’t visible or available for custom or partner
developments. They can’t rely on the stability of these objects. If
objects are additionally released in contract C0, customers or
partners can extend them, however. Contracts C3 and C4 are only
needed for special scenarios and won’t be discussed further here.
The stability contract of a development object is displayed together
with information on its lifecycle as API state. In the ABAP
Development Tools (ADT), you can display it either by choosing API
State from the context menu of a development object(see
Figure 14.1) or by going to the Properties tab and choosing the API
State subtab (see Figure 14.2).
Whether a stability contract holds for a development object depends
on its current phase in the lifecycle, which is shown in Figure 14.2 as
Release State. This will be explained in the next section.
Figure 14.1
API State in the Context Menu
Figure 14.2
API State with Stability Contracts and Lifecycle Information
14.2
Lifecycle of Development Objects
Lifecycles for APIs with release of use and possible deprecation are
a proven method to manage their use by external developers and
software. SAP documented this lifecycle some time ago under “SAP
API Deprecation Policy” (http://s-prs.co/v564213). Customers and
partners should be able to similarly use the business-related data
models implemented as CDS views in a controlled way. Therefore,
SAP introduced another lifecycle, the SAP CDS View Deprecation
Policy (http://s-prs.co/v564214), which applies analogously to other
development objects in ABAP, ABAP Data Dictionary, or CDS.
Technically, there is a lifecycle per stability contract. In practice, at
most one of the contracts C1 to C4 is relevant for a development
object, with the contract C0 an optional addition.
The different phases in the lifecycle are called release states. In
ADT, they are shown as API State, as you can see in Figure 14.1
and Figure 14.2, shown earlier. Table 14.3 shows and explains the
different release states.
Release State
Explanation
Release State
Explanation
NOT_RELEASED
(Contract Cx not yet set, contract Cx
cannot be set)
The object isn’t stable.
It may change or even
be deleted. But it could
also be released in a
future software
version. The object
isn’t visible in custom
development
environments. This is
the default release
state of newly created
objects.
NOT_TO_BE_RELEASED
The object isn’t stable.
It’s not planned to be
released in the future.
It’s not visible in
custom development
environments.
NOT_TO_BE_RELEASED_STABLE
The object isn’t
released but kept
stable for SAP internal
purposes. It’s not
visible in custom
development
environments.
Release State
Explanation
RELEASED_WITH_FEATURE_TOGGLE The object isn’t
generally released. It’s
part of a new feature
that may be available
in a future version or
for selected pilot
customers. The object
is kept stable and is
only visible in custom
development
environments of pilots.
RELEASED
The object is available
for custom
developments. It’s kept
stable and is visible in
custom development
environments.
DEPRECATED
The object is still kept
stable like a released
object for a limited
period of time. It
shouldn’t be used for
new custom
developments, but
existing custom
developments can
continue to use it for a
limited period. It’s still
visible in custom
development
environments.
Release State
Explanation
DECOMMISSIONED
The object must not be
used anymore. It may
still be syntactically
compatible but no
longer provides its old
functionality. It’s not
visible in custom
development
environments.
Table 14.3
Release States in the Lifecycle of a Development Object
Deprecation or decommissioning of a development object can’t
happen immediately after a release. The periods shown in
Table 14.4 are respected.
Period
Relevant Release States
Duration
Lifespan
From beginning of RELEASED to
beginning of DECOMMISSIONED
At least
24
months
Deprecation From beginning of DEPRECATED to
period
beginning of DECOMMISSIONED
Table 14.4
At least
12
months
Periods in the Lifecycle
The life of a development object begins in release state
NOT_RELEASED. In this state, it’s unclear whether SAP intends to
release that object at a later time or not. In the VDM, annotations
clarify the future plans:
SAP doesn’t plan to release VDM views annotated with
@VDM.lifecycle.contract.type : #SAP_INTERNAL_API or
@VDM.private: true. They are reserved for SAP internal use, even
if their name starts with prefix I_.
SAP plans to release VDM views annotated with
@VDM.lifecycle.contract.type : #PUBLIC_LOCAL_API according to
stability contract C1.
[»] Use of Released Development Objects
In a classical installation of SAP S/4HANA or SAP S/4HANA
Cloud, private edition, a customer or partner can use all ABAP,
ABAP Data Dictionary, or CDS objects delivered by SAP for their
own developments, even those that aren’t released. But they must
also take care if incompatible changes cause problems when
upgrading to a new software version and developments are
invalidated.
For SAP S/4HANA Cloud, public edition and SAP BTP, ABAP
environment, SAP wants to exclude such issues and only allows
the use of released development objects for custom
developments. Two different scenarios are available for that:
Key user apps that guide users through predefined
implementation scenarios
Cloud development based on the full ADT
In both scenarios, objects released for stability contracts C0 and
C1 can be used. As there are technical differences between the
scenarios, the release can be limited to only one of the two. Refer
to Figure 14.2 to see an example.
14.3
Deprecation of Development Objects
The deprecation of released development objects, including VDM
views and entities, sometimes can’t be avoided. New requirements
arise, and business or technical improvements are necessary. SAP
tries to include these improvements in a compatible way into existing
objects, but this isn’t always possible, and new objects have to be
developed. SAP doesn’t want to offer outdated, even redundant
development objects as released objects—the set of released
development objects is also supposed to be the recommended set of
objects to use in custom developments. So, these outdated objects
are marked as such by setting their release state to DEPRECATED.
[ ! ] Different Kinds of Deprecation
Different kinds of deprecation exist for cloud software. Besides the
development objects discussed here, also APIs, elements of
identity and access management (IAM), or complete apps can be
deprecated. Compared with the deprecation of development
objects, these have a timely decommissioning and mostly
significantly shorter deadlines in the lifecycle.
In case of deprecation, SAP usually releases an improved successor
that can be used instead of the deprecated one. Figure 14.3 shows
an example.
The information about deprecation and a successor can be used, for
example, by the key user apps to support the migration of custom
developments to the successors.
Figure 14.3
Deprecated CDS View with Successor
For the preparation of a deprecation or for the SAP-internal
deprecation of not released VDM views, SAP uses annotations
@VDM.lifecycle.status and @VDM.lifecycle.successor. Listing 14.1
shows the SAP internal deprecation of the view already shown in
Figure 14.3.
@VDM.lifecycle.status: #DEPRECATED
@VDM.lifecycle.successor: 'I_PersonWorkAgreement_1'
define view I_PersonWorkAgreement
…
Listing 14.1
SAP-Internal Deprecation
CDS entities are tightly connected by their associations; therefore,
the deprecation of a CDS entity in stability contract C1 can impact
several other entities associated with it, as follows. In stability
contract C1, a released CDS entity A shouldn’t associate a
deprecated CDS entity B because this would mislead consumers of
A to consume the deprecated B via the association. But how can
SAP avoid such a situation, when the target entity B of the
association should be deprecated? It’s not possible to replace the
deprecated target entity B with another released one, as this is an
incompatible change. So, the only option seems to be the
deprecation of entity A as well. Unfortunately, this approach leads to
the unnecessary deprecation of various other entities.
A better solution is to deprecate the association to entity B and
introduce a new association to the successor of the deprecated
entity B. To avoid such unnecessary deprecations, SAP enabled the
deprecation of individual fields and associations of CDS entities in
stability contract C1. For released CDS models, annotation
@API.element.releaseState : #DEPRECATED can be used. The
decommissioning of an element can be expressed by annotation
value #DECOMMISSIONED. Successor elements can be specified with
@API.element.successor. For SAP-internal CDS models or the SAPinternal preparation of element deprecations, annotations
@VDM.lifecycle.status : #DEPRECATED and @VDM.lifecycle.successor
can also be used for elements. Listing 14.2 shows an example of a
deprecated association.
define view I_WorkforcePerson
...
association [1..*] to I_PersonWorkAgreement
as _PersonWorkAgreement
on $projection.Person = _PersonWorkAgreement.Person
association [1..*] to I_PersonWorkAgreement_1
as _PersonWorkAgreement_1
on $projection.Person = _PersonWorkAgreement_1.Person
...
@API.element.releaseState: #DEPRECATED
@API.element.successor: '_PersonWorkAgreement_1'
_PersonWorkAgreement,
_PersonWorkAgreement_1,
...
Listing 14.2
Deprecation of an Association with Successor
Out of consideration for possible users of deprecated development
objects, SAP has performed a decommissioning of deprecated
development objects only in rare cases and, instead, keeps
deprecated objects stable far beyond the lifecycle periods.
Unfortunately, you can’t tell from the release state today if a
deprecated object will be decommissioned immediately after the
lifecycle periods, or if it will be preserved next to its successors. This
information should be contained in the What’s New section about
the deprecation of the development object at the SAP Help Portal.
14.4
Use of CDS Models and Supported Capabilities
SAP has released many types of CDS views and entities for use in custom
developments according to stability contract C1, but not all are suited equally well for
all purposes. Some of them are suited only for a certain task, and others may have
individual limitations. It’s not reasonable, for example, to use an analytical query as a
data source for defining a custom view: analytical queries must be executed by the
analytical environment, and only the results in that environment are kept stable in an
upgrade.
SAP introduced the concept of supported capabilities to more clearly express what can
be expected from a released CDS view or entity. Annotation
@ObjectModel.supportedCapabilities in a CDS model indicates what SAP-supported
capabilities the CDS entity provides. Multiple capabilities are possible for a CDS model.
Supported capabilities are subject to stability contract C1 and must be permanently
preserved by a released CDS model.
Often, a capability is closely related to the way a CDS entity is modeled, but this is a
different, yet relevant, aspect. SAP captures this aspect by assigning a single modeling
pattern with annotation @ObjectModel.modelingPattern. Specific development support
can be offered per modeling pattern.
[+] Supported Capabilities
We recommend using released CDS models according to their supported
capabilities. This helps you choose appropriate CDS models with permanent
capabilities. When assigning supported capabilities, SAP also takes nonfunctional
aspects such as performance into account.
Depending on the scenario, key user apps propose CDS entities that support the
necessary capabilities.
The current list of supported capabilities and modeling patterns is shown in Table 14.5.
Further information is available in the SAP Help Portal at http://s-prs.co/v564215.
Name
Supported Description
Capability
(C),
Modeling
Pattern
(M)
Name
Supported Description
Capability
(C),
Modeling
Pattern
(M)
SQL_DATA_SOURCE
C
You can select data via a
SQL SELECT statement or
a path expression from
an ABAP program.
CDS_MODELING_DATA_SOURCE
C
You can use the CDS
entity as the data source
when defining a custom
CDS model with joins,
unions, and path
expressions of an
association.
CDS_MODELING_ASSOCIATION_TARGET
C
You can use the entity as
an association target
when defining a custom
CDS model.
DATA_STRUCTURE
C, M
You can use the entity as
the data structure in
ABAP RESTful
application programming
models or in ABAP.
LANGUAGE_DEPENDENT_TEXT
C, M
The entity provides texts
that are language
dependent. See
Chapter 8, Section 8.5.
VALUE_HELP_PROVIDER
C, M
You can use the entity to
offer a value help in SAP
Fiori or analytics. See
Chapter 13,
Section 13.1.1.
EXTRACTION_DATA_SOURCE
C
You can use the entity to
extract data to a remote
system, for example, to a
data warehouse.
Name
Supported Description
Capability
(C),
Modeling
Pattern
(M)
DERIVATION_FUNCTION
C, M
You can use the entity as
a derivation function in
annotation
@Consumption.derivation.
See Chapter 10,
Section 10.3.3.
PARENT_CHILD_HIERARCHY_NODE_PROVIDER
C, M
You can use the entity to
retrieve special
properties of hierarchy
nodes and execute
hierarchy navigation and
hierarchy aggregation.
See Chapter 12,
Section 12.3.
SEARCHABLE_ENTITY
C
You can execute special
search operations with
the entity. See
Chapter 13,
Section 13.2.
ANALYTICAL_QUERY
C, M
You can use the entity as
an analytical query with
an analytical frontend
such as SAP Analytics
Cloud. See Chapter 10,
Section 10.3.
ANALYTICAL_PROVIDER
C
You can build analytical
queries on top of the
entity. See Chapter 10,
Section 10.3.
ANALYTICAL_CUBE
M
The entity is developed
as an analytical cube.
See Chapter 10,
Section 10.2.3.
Name
Supported Description
Capability
(C),
Modeling
Pattern
(M)
ANALYTICAL_DIMENSION
C, M
The entity is developed
as an analytical
dimension, and you can
use the entity as an
analytical dimension of
an analytical provider.
See Chapter 10,
Section 10.2.4.
ANALYTICAL_FACT
M
This entity provides
normalized data for
analytics that can be
used in analytical cubes
or for the extraction of
data.
ANALYTICAL_PARENT_CHILD_HIERARCHY_NODE
C, M
You can use the entity as
a hierarchy in analytics.
See Chapter 12,
Section 12.2.
TRANSACTIONAL_PROVIDER
F
This entity supports
transactional access in
ABAP. See Chapter 11,
Section 11.4.
TRANSACTIONAL_QUERY
M
This entity was
developed as a
transactional query. See
Chapter 11, Section 11.5.
TRANSACTIONAL_INTERFACE
M
This entity was
developed as a
transactional interface.
See Chapter 11,
Section 11.6.
Name
Supported Description
Capability
(C),
Modeling
Pattern
(M)
OUTPUT_FORM_DATA_PROVIDER
OUTPUT_EMAIL_DATA_PROVIDER
OUTPUT_PARAMETER_DETERMINATION_DATA_SOURCE
C, M
These are applicationspecific supported
capabilities and modeling
patterns that play special
roles in output
management in SAP
S/4HANA. More
information is available at
http://s-prs.co/v529410.
SITUATION_ANCHOR
SITUATION_TRIGGER
SITUATION_DATACONTEXT
C, M
These are applicationspecific supported
capabilities and modeling
patterns that play special
roles in situation handling
in SAP S/4HANA. More
information is available at
http://s-prs.co/v529411.
KEY_USER_COPYING_TEMPLATE
F
This entity can be used
as a copy template in the
key user apps. It’s mainly
used for analytical
queries.
NONE
M
This CDS entity doesn’t
follow any specific
modeling pattern.
Table 14.5
Supported Capabilities and Modeling Patterns
Maybe you’ve noticed that there is no supported capability for the definition of service
APIs. The reason is that CDS entities delivered by SAP shouldn’t be used for that
purpose.
[+] Released CDS Views and the Definition of Services
Avoid the direct use of C1-released CDS views for the definition of services, for
example, business services of Chapter 6, or other OData services. As the C1
stability contract is intended for internal use in the ABAP system, some changes,
namely field extensions, can cause incompatible changes and necessary
adaptations of these services.
Use the released CDS views from SAP instead as data sources of your own views,
based on which you can define services. In addition, stop the propagation of
annotations with annotation @Metadata.ignorePropagatedAnnotations : true to avoid
side-effects of annotation changes.
14.5
Summary
In this chapter, we introduced the challenges in the relationship of
providers and consumers of development objects when these
objects are changed over time. We described the approach taken by
SAP to control these challenges, which is giving SAP developers
sufficient room for innovations and customers or partners sufficient
stability for their own developments. This approach is based on
stability contracts, a lifecycle with release and deprecation of
development objects, and guidance for the use of appropriate
development objects with supported capabilities.
The next chapter will discuss the same challenges and their solution
for the special case of extended CDS views and entities.
15 Extensions of CDS Views
and Other Entities
Users of business software must be able to define custom
data types and extend standard data structures and
application logic with custom data fields and logic. While
doing so, frictionless operations and software upgrades must
be ensured.
In previous chapters, you’ve implemented CDS views by leveraging
views and other development objects developed by SAP. You
haven’t yet extended SAP views and apps with your own data fields
and data types. Now imagine you need a custom field in a sales
order that isn’t available in the SAP standard, and you want to
maintain possible values and texts for the field. You’ll also want to
enter the field in standard apps and display it in reports or analyses.
It’s important that your custom field is still available and usable after
an upgrade to a new software version as well.
This requirement sounds natural but is quite complex to support by
the software provider. Already when using SAP CDS views, you
should pick released views with stability contract C1 (see Chapter 14
for an explanation), as SAP must keep them stable. Otherwise, the
next upgrade might be an unpleasant surprise. With extensions, the
situation isn’t easier, as these change and therefore depend on the
inner structure of a CDS view, while a pure C1-type usage only
consumes the exposed signature of the CDS view, not its internals.
As a solution, SAP has introduced dedicated development objects
for extensions, defined a stability contract C0 for extensions, and
prepared database tables and CDS views of application data for
extensions.
This chapter explains SAP’s preparations and how you can best use
them for your extensions. We focus on CDS views and the database
persistence but also show how to extend a transactional model.
With that, we only cover a fraction of the complete extensibility topic.
You’ll find a more comprehensive overview in the “ABAP Extensibility
Guide” at http://s-prs.co/v564216 or recent information in an SAP
blog post at http://s-prs.co/v564217.
Section 15.1 gives a brief overview of extension options in different
variants of ABAP-based products of SAP. Section 15.2 explains the
preconditions and the implementation of stable CDS extensions.
Section 15.3 finally shows the extension of a transactional
application.
15.1 Solution Variants and ABAP Language
Versions
In recent years, SAP has transformed applications and the ABAP
platform to enable them to operate in the cloud. This led to the
creation of several variants of ABAP-based SAP solutions and
different language versions of ABAP. Today, the following solution
variants can be distinguished:
SAP S/4HANA, the classical on-premise solution operated by a
customer
SAP S/4HANA Cloud, private edition, which is operated in the
cloud but technically and functionally similar to the on-premise
variant
SAP S/4HANA Cloud, public edition, which is operated in the
cloud and takes care of technical details for the customer
SAP Business Technology Platform (BTP), ABAP environment, a
cloud platform for developing and operating ABAP-based
solutions
These solutions offer quite different extension options. With SAP
S/4HANA and SAP S/4HANA Cloud, private edition, ABAP imposes
minimal restrictions for changing and extending the software.
Customers have much freedom but also full responsibility for the
consequences of these changes, especially at the next software
upgrade.
SAP S/4HANA Cloud, public edition and SAP BTP, ABAP
environment, however, want to strictly prevent issues during an
upgrade. Therefore, the extension options were restricted.
In the first versions of SAP S/4HANA Cloud, public edition,
customers had no access to the ABAP development environment,
neither in SAP GUI nor in the ABAP Development Tools (ADT).
Instead, specific key user apps guide an experienced user, the key
user, through predefined scenarios. This approach is called key user
extensibility. Figure 15.1 shows some key user apps.
Figure 15.1
Key User Apps for Custom Extensions
For the key user apps, technical approaches and guidelines for the
software development at SAP were implemented that support stable
extensions and development by cloud customers, for example, of
custom fields and custom CDS views, and avoid issues during
upgrades. You already learned about one aspect of that, the stability
contracts and the related lifecycle, in Chapter 14. We’ll introduce the
technical prerequisites for such extensions in Section 15.2. Users of
the key user apps, however, aren’t confronted with this. They define
the required extensions, and the apps handle the technical details.
For the implementation of custom logic, that is, simple code at
predefined points in the application logic (business add-ins [BAdIs]),
the key user apps need a programming language. This should be
ABAP again, but the classical ABAP can access all tables and more,
which might endanger frictionless operations. To prevent that, a new
limited language version of ABAP was developed, called ABAP
language version 2, which only allows access to released resources,
application programming interfaces (APIs), and development
objects.
In contrast to the key user apps, SAP BTP, ABAP environment,
being a development platform, offers of course a complete
development environment: ADT. To prevent any impact of custom
developments on cloud operations, again a limited language version
of ABAP is used, called ABAP language version 5, ABAP for Cloud
Development (ABAP Cloud for short). This language version still
allows the ABAP style of software development but also introduces
crucial innovations:
Only the ADT development environment is supported.
The scope of the language is reduced by removing outdated
syntax and language elements.
Legacy technologies such as Dynpro or Web Dynpro are no
longer supported. The ABAP RESTful application programming
model is supported instead (see Chapter 11).
The modification of SAP development objects is forbidden.
Only released SAP development objects can be used (see
Chapter 14).
ABAP Cloud is also available in new installations of SAP S/4HANA
Cloud, public edition. Customers can choose between the key user
apps or ABAP Cloud for developer extensibility (also called cloud
development). In both limited language versions, development
objects are released according to the stability and lifecycle concepts
that you learned in Chapter 14. Yet there are different constraints for
released development objects in key user apps and developer
extensibility. Via the different language versions, a separate release
is possible. This is indicated as visibility in the API state of a
development object, as you can see later in Figure 15.3.
The “ABAP Extensibility Guide,” mentioned earlier (http://sprs.co/v564216), explains how you can leverage key user apps of
ABAP Cloud in SAP S/4HANA Cloud, private edition, or in an onpremise variant to avoid effort during upgrade.
15.2
Stable CDS Extensions
The extension of software by customers often led to issues during
upgrades in the past, or even when applying hot fixes. Let’s consider
some of these difficulties and gradually approach a solution.
Without special precautions by the software developer, a customer
has no other chance but to modify the installed software if they want
to extend it. The next upgrade of the software will overwrite these
modifications, however, and they must be applied again. This causes
high effort for the customer. To avoid this, SAP introduced separate
extension objects for development objects that were frequently
extended. Extension objects are the customer’s responsibility and
never overwritten by an upgrade. When executing the software, a
combination of SAP development object and customer extension
object is always processed. CDS views, for example, have CDS
extensions as extension objects, which contribute custom fields and
associations to a CDS view. But what happens if SAP deletes a
development object or changes it in a way that it no longer fits the
extension object? Then the extension no longer works, causing
delays and additional effort.
To avoid these difficulties, there must be rules that control the
cooperation of standard software and extensions. For CDS views, or
more generally for current ABAP-based SAP software, this is the
stability contract C0, which was briefly introduced in Chapter 14. Its
focus is to identify extension points, that is, stable points where
extension objects are possible, to release these for extensions by
customers or partners, and to keep all relevant properties of the
extension points stable so that an extension object isn’t invalidated
by an upgrade. Details of the contract will be explained with the
following examples. The lifecycle of stability contract C0 has only two
API states currently: Released and Not Released.
Often development objects depend on each other or build on each
other, such as stacked CDS views. Extensions would have to build
on each other accordingly, for example, to propagate a custom field
from the database table through a view stack. If a developer now
changes the construction of the view stack and replaces one of the
views by a new one, the stack of extensions is interrupted. The
stability contract therefore must control dependencies but leave
enough space for innovative conversions.
15.2.1
Stable Extensions of CDS Views
Figure 15.2 shows how data of the SAP standard is extended by
preparing a database table and a CDS view for stable extensions.
You’ll implement a complete example with all preparations and
extensions in Section 15.2.2 and Section 15.2.3.
Figure 15.2
Extension of a Database Table and a CDS View
A database table isn’t a stable extension point because SAP might
change the persistence model in the future. But a data structure,
either a group of fields or the structure of a table row, could be
defined as an extension point. This extension point is represented by
an ABAP data structure, which is included by the database table and
therefore called a structure include. It will always stay connected with
the SAP data, even if the persistence model changes. Even if the
SAP data is stored alternatively in a separate database table for
transactional draft data, this table also includes the structure include.
This approach makes the structure include a stable extension point
that can be extended with custom fields by a type extension. The
custom fields thus are available in all relevant tables, and they can
automatically move with the SAP data in case of a persistence
change.
A CDS view can be extended by a CDS extension. We’ll explain how
this is done in Section 15.2.3. The CDS view often reads SAP data
via a stack of CDS views, which might change. For a stable
extension, each of the stack levels would have to be kept stable,
which usually can’t be ensured in practice.
As an alternative, CDS extensions select their data from special
CDS views, the CDS extension includes. These are CDS views that
correspond to the structure includes just introduced. They define an
extension point and are released for stability contract C0. They only
select the data’s key from the database table. CDS extensions can
add custom fields to the CDS extension include, which they select
via a stable alias name from the database table.
To make a CDS view extensible, an association to an appropriate
CDS extension include view can be defined, the extension
association. A CDS extension of the view can use the extension
association as a stable shortcut to the persistence of SAP data and
project custom fields from its type extension.
The following examples show how the just introduced development
objects are applied to an extension with custom fields.
15.2.2
Example: Stable Extension Points
In this section, we explain the preparations for stable extensions.
These are done by the software provider, that is, SAP or a
development partner.
For the extension of a database table and a CDS view, three stable
extension points are needed:
A structure include for the database table
A CDS extension include
The CDS view
We take CDS view ZI_SalesOrderItem from Chapter 1 as an example
and show how the extension points are defined. First, we prepare
the CDS view for developer extensibility. In Section 15.2.4, we’ll
discuss other extension variants.
Define a Structure ZSALESORDERITEM_EXT_INCLUDE in ADT, as shown
in Listing 15.1, which will be used as the structure include.
@EndUserText.label:
'Structure Include for Table ZSALESORDERITEM'
@AbapCatalog.enhancement.category: #EXTENSIBLE_CHARACTER_NUMERIC
@AbapCatalog.enhancement.fieldSuffix: 'ZSI'
@AbapCatalog.enhancement.quotaMaximumFields: 400
@AbapCatalog.enhancement.quotaMaximumBytes: 5000
@AbapCatalog.enhancement.quotaShareCustomer: 50
@AbapCatalog.enhancement.quotaSharePartner: 50
define structure zsalesorderitem_ext_include {
dummy_zsalesorderitem_ext_incl : dummy;
}
Listing 15.1 Structure Include ZSALESORDERITEM_EXT_INCLUDE for Table
ZSALESORDERITEM
Annotation @AbapCatalog.enhancement defines a few settings for
possible extensions:
The extension category, which you know from the ABAP Data
Dictionary.
A mandatory suffix for extension fields. This suffix is unique for a
structure include and prevents naming collisions if a database
table combines multiple structure includes.
Quota rules for extension fields to not violate technical limits of
database tables.
For technical reasons, a structure include must have a field,
dummy_zsalesorderitem_ext_incl in this case, which you can ignore.
Now release this structure for stability contract C0 by adding the
option Extend (Contract C0 with the + symbol (see Figure 15.3).
Set the Release State to Released, and select Use in Cloud
Development under Visibility.
Figure 15.3
C0 Release of the Structure Include
You can’t set the visibility Use in Key User Apps yet in ADT for
stability contract C0. SAP currently uses an internal registry
(Transaction SCFD_REGISTRY).
Now include the structure in database table ZSALESORDERITEM,, as
shown in Listing 15.2. Also check the enhancement category of the
table.
@EndUserText.label : 'Sales Order Item'
@AbapCatalog.enhancement.category :
#EXTENSIBLE_CHARACTER_NUMERIC
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #ALLOWED
define table zsalesorderitem {
key client
: abap.clnt not null;
key salesorder
: vbeln not null;
key salesorderitem : posnr not null;
...
lastchangedatetime : last_changed_date_time;
include zsalesorderitem_ext_include;
}
Listing 15.2
Database Table ZSALESORDERITEM with Structure Include for Extensions
As a next step, create CDS extension includeZE_SalesOrderItem, as
shown in Listing 15.3. Release it for stability contract C0 as well.
@AccessControl.authorizationCheck: #PRIVILEGED_ONLY
@EndUserText.label:
'Extension Include View for ZSalesOrderItem'
@AbapCatalog.extensibility: {
extensible: true,
elementSuffix: 'ZSI'
dataSources: ['Persistence'],
allowNewDatasources: false,
quota: {
maximumFields: 1000,
maximumBytes: 100000
}
}
@VDM.viewType: #EXTENSION
define view entity ZE_SalesOrderItem
as select from zsalesorderitem as Persistence
{
key Persistence.salesorder
as SalesOrder,
key Persistence.salesorderitem as SalesOrderItem
}
Listing 15.3
CDS Extension Include ZE_SalesOrderItem for ZSALESORDERITEM Data
In the virtual data model (VDM), CDS extension include views are
identified by annotation @VDM.viewType: #EXTENSION and the prefix
“E_” in their name.
The extensibility of the extension include view is controlled by
annotation @AbapCatalog.extensibility. As this extension include
view corresponds with previously introduced structure include
ZSALESORDERITEM_EXT_INCLUDE, the same suffix “ZSI” is used as
elementSuffix. The danger of a naming collision is lower for CDS
views than for database tables, but the suffix clearly shows the
relationship of fields.
An important bit of information is the stable alias name for a data
source given by dataSources: ['Persistence']. In stability contract
C0, the alias name of that data source must not be changed, and the
data source must provide the extension fields of the related structure
include at least, even if SAP would change the persistence model.
Finally, we make CDS view ZI_SalesOrderItem from Chapter 1 a
stable extension point. This requires the following:
Annotation @AbapCatalog.extensibility
Extension association _Extension
Release with stability contract C0
Complement the CDS view, as shown in Listing 15.4, and release it
with stability contract C0.
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Item'
@Metadata.ignorePropagatedAnnotations: true
@AbapCatalog.extensibility: {
extensible: true,
elementSuffix: 'ZSI',
allowNewDatasources: false,
dataSources: ['_Extension'],
quota: {
maximumFields: 1000,
maximumBytes: 100000
} }
define view entity ZI_SalesOrderItem
as select from zsalesorderitem
association to parent ZI_SalesOrder as _SalesOrder
on $projection.SalesOrder = _SalesOrder.SalesOrder
composition [0..*] of ZI_SalesOrderScheduleLine
as _ScheduleLine
association [0..1] to ZI_Product as _Product
on $projection.Product = _Product.Product
association [0..1] to ZE_SalesOrderItem as _Extension
on $projection.SalesOrder = _Extension.SalesOrder
and $projection.SalesOrderItem = _Extension.SalesOrderItem
{
...
Listing 15.4
CDS View ZI_SalesOrderItem with Extension Association
Annotation @AbapCatalog.extensibility.datasSources defines
extension association _Extension as a stable data source, indicating
that extension fields and associations of the association target, the
CDS extension include view, can be used safely. This enables a
stable extension of CDS view ZI_SalesOrderItem with custom fields
defined for structure include ZSALESORDERITEM_EXT_INCLUDE of
ZSALESORDERITEM data. The extension association itself isn’t exposed
in the CDS view, however.
In the same way, every CDS view can be defined as a stable
extension point for which an extension association to a CDS
extension include is possible, independent from how the CDS view is
constructed. In the example, only fields SalesOrder and
SalesOrderItem are needed. A CDS view could have multiple
extension associations if it combines data of different types, for
example, header and item data of the order with customer and
product data.
The developer of the CDS view can also specify other data sources
of the view (their alias names) or associations as stable data
sources. This is only reasonable, however, if the data source or the
association target is released with stability contract C1, as otherwise
the data source might not be stable. Extension include views have to
be kept stable as well, although they aren’t released. With this
approach, SAP standard fields of a stable data source also can be
used to extend a CDS view. Note that the naming rules for custom
fields must be applied because SAP might add the standard field in a
later release, which would lead to a naming collision.
For an analytical query, the extension via an extension association
isn’t possible as all dimension fields and measures are selected from
the analytical cube view. Yet if the cube is a stable data source of the
query and released with a C0 contract, you can first extend the cube
and then the query. Alternatively, you could define a custom query.
In our example, the definition of further data sources for extension
fields is forbidden by annotation allowNewDatasources: false. Further
data sources could negatively impact the transactional properties or
the performance of the CDS view, so this can be separately defined.
15.2.3
Example: Extension with Custom Fields
In this section, we explain the implementation of a stable extension
by a developer from a customer or a partner.
The target is to extend database table ZSALESORDERITEM and CDS
view ZI_SalesOrderItem with a custom field. This field will specify a
processing priority for the sales order item. Before implementing the
extension, a list of possible priorities and their descriptions is
needed. The easiest way to implement that is a new domain in the
ABAP Data Dictionary.
Let’s start by defining the necessary ABAP Data Dictionary objects
and activating them:
Domain ZZPRIORITY with data type CHAR; length 1; fixed values A, B,
C; and related texts Highest Priority, Important, Standard
Data element ZZPRIORITY with this domain, label Priority, and
short text Processing Priority
Data element ZZPRIORITYTEXT with domain TEXT60 and the same
label and short text
Now define CDS views for the priority values and descriptions as
given in Listing 15.5 and Listing 15.6.
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Processing Priority'
@ObjectModel.representativeKey: 'Priority'
define view entity ZI_Priority as select from dd07l
association [0..*] to ZI_PriorityText as _Text
on $projection.Priority = _Text.Priority
{
@ObjectModel.text.association: '_Text'
key cast( dd07l.domvalue_l as zzpriority ) as Priority,
_Text
}
where
dd07l.domname = 'ZZPRIORITY'
and dd07l.as4local = 'A'
and dd07l.as4vers = '0000'
Listing 15.5
CDS View ZI_Priority for the Priority Values
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Priority Description'
@ObjectModel.dataCategory: #TEXT
@ObjectModel.representativeKey: 'Priority'
define view entity ZI_PriorityText as select from dd07t
association [1..1] to ZI_Priority as _Priority
on $projection.Priority = _Priority.Priority
association [0..1] to I_Language as _Language
on $projection.Language = _Language.Language
{
@ObjectModel.foreignKey.association: '_Language'
@Semantics.language: true
key cast( dd07t.ddlanguage as spras preserving type )
as Language,
@ObjectModel.foreignKey.association: '_Priority'
@ObjectModel.text.element: ['PriorityText']
key cast( dd07t.domvalue_l as zzpriority ) as Priority,
@Semantics.text: true
cast( dd07t.ddtext as zzprioritytext preserving type )
as PriorityText,
_Priority,
_Language
}
where dd07t.domname = 'ZZPRIORITY'
and dd07t.as4local = 'A'
and dd07t.as4vers = '0000'
Listing 15.6
CDS Text View ZI_PriorityText for Priority Descriptions
[ ! ] Selection of Domain Fixed Values
Developer extensibility in a cloud system doesn’t allow access to
tables DD07L and DD07T, which contain all domain fixed values and
texts. As an alternative, you can use CDS views
DDCDS_CUSTOMER_DOMAIN_VALUE and DDCDS_CUSTOMER_DOMAIN_VALUE_T
to read this information for custom domains, more precisely for
domains in a customer software component.
Figure 15.4 shows the extension points defined in the last section:
Structure include ZSALESORDERITEM_EXT_INCLUDE
CDS extension include ZE_SalesOrderItem
CDS view ZI_SalesOrderItem
These will be extended now.
Figure 15.4
Extension Points and Extensions
First, define type extension ZSALESORDERITEM_EXT_PRIO in ADT as a
structure, as shown in Listing 15.7.
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
extend type zsalesorderitem_ext_include with
zsalesorderitem_ext_prio
{
zzpriorityzsi : zzpriority;
}
Listing 15.7 Type Extension ZSALESORDERITEM_EXT_PRIO for Structure Include
ZSALESORDERITEM_EXT_INCLUDE
The type extension extends the structure include and through that
also database table ZSALESORDERITEM.
Use the suffix “ZSI” for the name of your extension field as specified
in structure include ZSALESORDERITEM_EXT_INCLUDE (refer to
Listing 15.1). If you also consider the general naming rules, your
custom field gets the name “ZZPriorityZSI”.
[ ! ] Rules for Avoiding Naming Collisions
Always use a prefix for the names of custom fields, custom
associations, or other extension elements to avoid collisions with
existing or future names in the SAP standard. Associations may
have an underscore (_) before the prefix. Customer extensions will
use the prefix ZZ, and partner extensions will use their specific
ABAP namespace.
Now define a CDS extension by creating data definition
ZX_E_SALESORDERITEM_PRIO in ADT as given in Listing 15.8.
extend view entity ZE_SalesOrderItem with
association [0..1] to ZI_Priority as _ZZPriorityZSI
on $projection.ZZPriorityZSI = _ZZPriorityZSI.Priority
{ @ObjectModel.foreignKey.association: '_ZZPriorityZSI'
Persistence.zzpriorityzsi as ZZPriorityZSI,
_ZZPriorityZSI
}
Listing 15.8 CDS Extension ZX_E_SALESORDERITEM_PRIO of CDS Extension
Include ZE_SalesOrderItem
In a CDS extension, you can create a new association and expose it.
Annotations for extension elements are possible as well.
Note that different syntax variants for CDS extensions exist,
depending on the type of the extended CDS entity:
ABAP Data Dictionary-based CDS view: extend view
CDS view entities: extend view entity
CDS abstract entities: extend abstract entity
CDS custom entities: extend custom entity
Finally, you can extend CDS view ZI_SalesOrderItem with CDS
extension ZX_SALES_ORDERITEM_PRIO, as shown in Listing 15.9.
extend view entity ZI_SalesOrderItem with
{ @ObjectModel.foreignKey.association: '_ZZPriorityZSI'
_Extension.ZZPriorityZSI,
_Extension._ZZPriorityZSI
}
Listing 15.9 CDS Extension ZX_SALES_ORDERITEM_PRIO of CDS View
ZI_SalesOrderItem
As the CDS view of the example stops the propagation of
annotations, you must annotate the foreign key association again in
the CDS extension. With this, our example of a stable CDS view
extension with a custom field is complete. To display the CDS view
together with its extensions, position the cursor in the ADT editor on
the view name and press (F2) to get to the screen shown in
Figure 15.5.
Figure 15.5
Extended CDS View
SAP internally uses CDS extensions for industry solutions or in
country-specific extensions. These are identified by annotation
@VDM.viewExtension: true and a prefix “X_” in their name.
15.2.4
CDS Extensions in Product Variants
We prepared the example for the previous two sections based on
CDS view ZI_SalesOrderItem for use with ABAP Cloud and extended
it accordingly. This approach is available in SAP BTP, ABAP
environment and in SAP S/4HANA Cloud, public edition with
developer extensibility.
In the Open ABAP Development Object dialog of ADT, you can
select the CDS views and other objects released for extensions with
ABAP Cloud by using filter api:extend_in_cloud_development, as
shown in Figure 15.6.
Figure 15.6
ADT Dialog with a Filter for Extensible Objects
Key user apps are available in all product variants. The release for
key user extensibility is visible in ADT (see Figure 15.7). Detailed
information on key user extensibility isn’t stored in the CDS view,
currently, but in a separate repository.
Figure 15.7
Release for Extensions in Key User Apps
The key user apps use the same technical objects for extending
CDS views with a structure include, a CDS extension include, and
the CDS view with the extension association as stable extensions
points. But the apps generate and manage the type extensions and
CDS extensions, which is much more convenient for a key user.
If you develop extensions for an SAP S/4HANA installation or for
SAP S/4HANA Cloud, private edition, you can also benefit from the
stability contract C0 by using released objects and stable extension
points where possible.
15.3
Extensions of Transactional Models
In the following section, we’ll now look at the extensibility of
transactional models and their entities.
15.3.1
Add Fields to an Entity
We’ve already learned how to extend fields on CDS entities. The
same can be done on transactional CDS entities and when reading
via SQL. Depending on the implementation via EML, this also works
out of the box. Of course, it should then also be possible to edit
these fields. This often works automatically as well, if the
implemented logic supports this and works with CORRESPONDING, for
example.
In our example, this is the case, so we extend two fields in the sales
order item in the complete stack (up to the user interface [UI]
projection). To do this, we add another field to the extension from
Section 15.2.4 (see Listing 15.10).
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
extend type zsalesorderitem_ext_include with
zsalesorderitem_ext_prio
{
zzpriorityzsi : zzpriority;
zzpriority2zsi : zzpriority;
}
Listing 15.10
Enhancement of the Extension Include of the Database Table
If the associated draft table also contains the structure include, as it
should, then this is also expanded accordingly.
Next, we extend the CDS views in the transactional stack. Here we
assume that these views are prepared for extensions (refer to
Section 15.2.3), their primary data source is stable, and they have
been released according to stability contract C0 (see Listing 15.11
and Listing 15.12).
extend view entity ZR_SalesOrderItem with
{
zsalesorderitem.zzpriorityzsi as ZZPriorityZSI,
zsalesorderitem.zzpriority2zsi as ZZAdditionalPriorityZSI
}
Listing 15.11
Enhancement of the Basic CDS Entity
extend view entity ZR_SalesOrderItemTP with
{
ZR_SalesOrderItem.ZZPriorityZSI,
ZR_SalesOrderItem.ZZAdditionalPriorityZSI
}
Listing 15.12
Enhancement of the Transactional CDS Entity
Because the draft table of the underlying entity also contains the
extension fields from the structure include, the behavior definition
remains error-free.
Finally, to bring the fields into the OData service for the UI, we
extend the corresponding projection view (see Listing 15.13).
Similarly, the fields could also be added to the behavior interface or
the OData service for a Web API.
extend view entity ZC_SalesOrderItemTP with
{
ZR_SalesOrderItemTP.ZZPriorityZSI,
ZR_SalesOrderItemTP.ZZAdditionalPriorityZSI
}
Listing 15.13
Enhancement of the CDS Projection View
Now both fields are already present in the UI and can be
personalized in the list. To display them by default, we also extend
the UI annotations accordingly via a metadata extension (see
Listing 15.14), which you also already know.
@Metadata.layer: #CUSTOMER
annotate view ZC_SalesOrderItemTP with
{
@UI.lineItem: [{position: 80, importance: #HIGH}]
@UI.fieldGroup: [{qualifier: 'GeneralData',
position: 80, importance: #HIGH }]
ZZPriorityZSI;
@UI.lineItem: [{position: 90, importance: #HIGH}]
@UI.fieldGroup: [{qualifier: 'GeneralData',
position: 90, importance: #HIGH }]
ZZAdditionalPriorityZSI;
}
Listing 15.14
Metadata Extension
When editing, you’ll notice that the first extension field also works in
the draft and is saved into the active persistence when the draft is
activated, and the value is present after saving. For the second field,
the values are saved into the draft tables, but when you activate the
draft, the values are lost. This is because we’ve defined an alias for
this field in the CDS view, and the implemented logic with
CORRESPONDING isn’t sufficient here.
To achieve this, we now also need to extend the behavior definition
and ensure the correct mapping of this one field. As a prerequisite
for extending a behavior definition, it must allow extensions (just like
CDS entities and tables). This is done via the extensible key word at
the level of the behavior definition as well for all entities that can be
extended. For the special case of mapping to the database table, the
mapping itself must also be extensible.
In our example, we add this capability for all entities, as shown in
Listing 15.15.
define behavior for ZR_SalesOrderItemTP alias SalesOrderItem
…
extensible
{
…
mapping for zsalesorderitem
corresponding extensible;
}
Listing 15.15
Enhance Behavior Definition
Now we can create an extension for the behavior definition. To do
this, select New Behavior Extension in the context menu of the
behavior definition that will be extended. In the dialog, assign a
Name and a Description for the extension (see Figure 15.8).
Optionally, you can also specify a behavior interface (BO Interface).
This is important if, for example, you’re working in the ABAP
language version for ABAP Cloud development and can only use
released objects. Because only behavior interfaces are usually
released, only this view is available for the extension when you
specify it.
In the extension, we can now define the mapping accordingly so that
the second field is also taken over and saved correctly (see
Listing 15.16).
extend behavior for SalesOrderItem
{
field ( features : instance ) ZZAdditionalPriorityZSI;
extend mapping for zsalesorderitem
{
ZZAdditionalPriorityZSI = zpriority2zsi;
}
}
Listing 15.16
Mapping for Behavior Extension
Figure 15.8
ADT: Create Behavior Extension
Accordingly, in the extension, we can also use any functionality of
the behavior definition for our own fields, such as specific field
controls, as also shown in Listing 15.16.
Much more is possible in the behavior extension, but let’s remain
with the simple field extension for now.
15.3.2
Add Application Logic
Usually, we don’t just want to add fields that can be edited and
saved, but we also want to use these fields. To add application logic,
BAdIs of the application always help in the appropriate places.
However, in the case of a behavior definition of type MANAGED, there
are even more and better possibilities. Here, the extension can also
add its own determinations and validations, provided this is again
allowed in the behavior definition, via the following addition:
extensible { with determinations on modify;
with determinations on save;
with validations on save; }
For extensions of determinations and validations, the question
immediately arises about the determine actions and especially the
special draft determine action Prepare. Therefore, at least this should
also be defined in such a way that it can be extended. This must be
done explicitly via the following:
draft determine action Prepare extensible {…}
Here you should also remember to extend the corresponding side
effect definition as well, if available.
In our example, we now add a validation for the extension fields and
include it in the Prepare action. Because we also must implement
something in the extension, it now also requires the specification of a
corresponding implementation class (see Listing 15.17).
extension implementation in class zbp_r_salesordertp_extension unique;
extend behavior for SalesOrder
{
extend draft determine action Prepare
{
validation SalesOrderItem~VerifyPriority;
}
side effects
{
determine action Prepare
executed on field CreationDateTime
affects field _Item.ZZPriorityZSI,
field _Item.ZZAdditionalPriorityZSI;
}
}
extend behavior for SalesOrderItem
{
validation VerifyPriority on save
{ field ZZPriorityZSI, ZZAdditionalPriorityZSI; }
}
Listing 15.17
Behavior Definition Extension: Validation
[+] Extend Side Effects
Currently, it’s not possible to extend only the global side effect in
the syntax. Therefore, as a workaround, we use a nonmodifiable
field as a trigger.
15.3.3 Extend Action and Function Parameters and
Results
Just like the entities of a behavior definition, the abstract CDS
entities can also be extended, which are used as parameter and
result data types of actions and functions. Again, in some cases, and
with existing CORRESPONDING-logic, everything works automatically, but
rather not that often as for the real entities. Usually, coding is needed
here to react to the parameters or to extend the result. Examples for
this include the incoming parameters check and their influence on
the implementation and the correct completion of the result. Here the
application must provide BAdIs because there are currently no other
extension possibilities.
In our example, we have a simple case that works out of the box if
we stick to the same field names. We extend the deep result data
type of action GetSalesOrder and the data type for the sales order
item accordingly with our extension field (see Listing 15.18).
extend abstract entity ZD_SalesOrderGetItemResult with
{
zzpriorityzsi : zzpriority;
}
Listing 15.18
Extension of Function Result Data Type
The result of the function is now one field richer, and this is already
correctly filled by the existing implemented logic. So, no BAdI or
similar is necessary in this case.
15.3.4
Extend Behavior
As already stated in Section 15.3.1, it’s possible to add many more
features to a behavior definition. In principle, the complete
functionality described in Chapter 11 is available. Thus, as already
mentioned, field control can be defined and implemented, new
actions or functions with all their features can be added, mappings
and side effects can be defined, and so on.
With feature controls for fields or actions as well as their authority
checks, only our own fields and actions are available in the signature
of the extension implementation. If actions check their own
authorization objects and don’t delegate them to the standard
implementation via authorization: update, for example, these
authorization objects should also be included to the privileged
authorization context. This is done via the following:
with privileged mode disabling <AuthorizationContext>;
define authorization context <AuthorizationContext>
{ '<AuthorizationObject>'; … }
By adding authorization objects to the privileged authorization
context, our own authorization context is automatically extended
accordingly. If, in exceptional cases, an authorization may not be
privileged, our own authorization context can be extended
specifically as follows:
extend own authorization context
{ '<AuthorizationObject>'; … }
Events can also be defined in a behavior extension. The MANAGED
events described in Chapter 11 are recommended here. Nothing
else is necessary for this.
However, there are also use cases for defining your own events. To
trigger these at the right place, a corresponding exit is necessary in
the late save phase. This can be a BAdI or—in the case of the
MANAGED implementation type—a separate additional save
implementation. This in turn must be allowed in the base behavior
definition via the following:
extensible { with additional save; }
If this is the case, the known syntax can now also be used in the
extension of an entity, and a corresponding handler method can be
implemented. The event can then be triggered in this method, for
example.
As an example, we’ll now show some of the possibilities using our
own new action. For this, we define an action that should set our
second extension field (see Listing 15.19). Accordingly, we set this to
not changeable and define the action with the known syntax. In
addition, we define the corresponding page effect for the action for
the correct behavior of the UI.
extend behavior for SalesOrderItem
{
action ( authorization : instance, features : instance,
precheck ) SetAdditionalPriority;
…
field ( readonly ) ZZAdditionalPriorityZSI;
side effects { action SetAdditionalPriority
affects field ZZAdditionalPriorityZSI; }
}
Listing 15.19
Behavior Extension: Add Action
After implementing the action as well as the desired features, such
as authorizations, feature control, or precheck, the action can
already be used.
Accordingly, we now want to offer this action in the UI. To do this, we
must now create an extension to the behavior projection and expose
the action accordingly (see Listing 15.20).
extension for projection;
extend behavior for SalesOrderItem
{
use action SetAdditionalPriority;
}
Listing 15.20
Behavior Projection: Add Action
The action is now also available automatically in the OData service.
Finally, we add more UI annotations to the metadata extension to
make the action visible and usable (see Listing 15.21).
@Metadata.layer: #CUSTOMER
annotate view ZC_SalesOrderItemTP with
{
@UI.lineItem:
[{ position: 80, importance: #HIGH},
{ type: #FOR_ACTION, invocationGrouping: #CHANGE_SET,
dataAction: 'SetAdditionalPriority',
label: 'Set Additional Priority' }]
@UI.fieldGroup:
[{ qualifier: 'GeneralData', position: 80,
importance: #HIGH },
{ type: #FOR_ACTION, invocationGrouping: #CHANGE_SET,
dataAction: 'SetAdditionalPriority',
label: 'Set Additional Priority' }]
ZZPriorityZSI;
…
}
Listing 15.21
Metadata Extension: Add Action
This means that the action now appears as a button in the UI and
can be executed accordingly.
15.3.5
Add Composition Child Entity
A bigger enhancement is the addition of a separate transactional
entity. This extension option is currently only possible for the MANAGED
implementation type. The prerequisite is the option to add an
association of type composition in the underlying CDS data model.
This is done using the following annotation:
@AbapCatalog.extensibility.allowNewCompositions: true
Nothing more is needed, except, of course, the option in the
behavior definition to make extensions to the corresponding entity
we’ve already set up.
To add an entity, we first define a database table and the associated
CDS view for it. The association to the parent entity is important for
the transactional CDS view. As an example, we want to add an entity
in the sales order header with a list of coupon codes (see
Listing 15.22).
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales Order Discount Codes'
define view entity ZR_SalesOrderDiscountCodeTP
as select from zsalesorderdis
association to parent ZR_SalesOrderTP as _SalesOrder on $projection.SalesOrder =
_SalesOrder.SalesOrder
{
key salesorder
as SalesOrder,
key discountuuid
as DiscountUUID,
discount_code
as DiscountCode,
@Semantics.user.createdBy: true
createdbyuser
as CreatedByUser,
@Semantics.systemDateTime.createdAt: true
creationdatetime
as CreationDateTime,
@Semantics.user.lastChangedBy: true
lastchangedbyuser as LastChangedByUser,
@Semantics.systemDateTime.lastChangedAt: true
lastchangedatetime as LastChangeDateTime,
_SalesOrder
}
Listing 15.22
Define Extension Entity
Now, to add this entity to the composition tree, we add a composition
as an extension to the CDS view of the sales order header in
Listing 15.23.
extend view entity ZR_SalesOrderTP with
composition [0..*] of ZR_SalesOrderDiscountCodeTP as _SalesOrderDiscountCode
{
_SalesOrderDiscountCode
}
Listing 15.23
Composition Association to Extension Entity
Now we can define and integrate the entity as transactional in the
behavior definition as well. To do this, we define the behavior like
any other entity, and we have the complete set of features available.
One difference is that only anonymous dependencies can be defined
for the dependent functionalities such as lock, ETag, authorization,
and change documents. This keeps it transparent to the extension
whether, for example, the authorization master is the direct parent
entity or it’s another ancestor entity up to the root entity. This
modeling ensures that the extensible application can change and
adapt the model in a later release in a compatible way keeping
existing extensions consistent and working.
In addition, the new composition must also be defined in the
behavior extension (see Listing 15.24).
extend behavior for SalesOrder
{
…
association _SalesOrderDiscountCode { create; with draft; }
…
}
define behavior for ZR_SalesOrderDiscountCodeTP alias SalesOrderDiscountCode
persistent table zsalesorderdis
draft table zsalesorderdisd
lock dependent
etag master LastChangeDateTime
authorization dependent
changedocuments dependent
{
update;
delete;
association _SalesOrder { with draft; }
field ( readonly ) SalesOrder;
field ( readonly, numbering : managed ) DiscountUUID;
field ( readonly ) CreatedByUser;
field ( readonly ) CreationDateTime;
field ( readonly ) LastChangedByUser;
field ( readonly ) LastChangeDateTime;
mapping for zsalesorderdis corresponding
{
DiscountCode = discount_code;
}
}
Listing 15.24
Behavior Definition for the Extension Entity
Because the model also supports draft, we need to create a draft
table for our extension entity as well (preferably via quick fix as
usual) and store it. In addition, it’s important to add the draft to the
new associations.
To use the new entity not only via EML, it can be added analogously
also in the behavior interface or in behavior projections. We limit
ourselves here to the projection for the UI because this covers the
complete functionality.
First, we need to extend the data model accordingly here as well and
add the composition in a CDS extension of the projection view to a
projection view of the extension entity (see Listing 15.25).
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales Order Discount Codes'
@Metadata.allowExtensions: true
define view entity ZC_SalesOrderDiscountCodeTP
as projection on ZR_SalesOrderDiscountCodeTP
{
key SalesOrder,
key DiscountUUID,
DiscountCode,
CreatedByUser,
CreationDateTime,
LastChangedByUser,
LastChangeDateTime,
_SalesOrder : redirected to parent ZC_SalesOrderTP
}
Listing 15.25
Projection Behavior for the Extension Entity
It’s important not to forget the additions redirected to parent and
redirected to composition child (see Listing 15.26).
extend view entity ZC_SalesOrderTP with
{
_SalesOrderDiscountCode : redirected to composition child
ZC_SalesOrderDiscountCodeTP
}
Listing 15.26
Composition Association to Extended Entity
To ensure that the new entity also offers the defined operations of
the behavior definition, these must also be added to the projection
behavior definition or its extension (see Listing 15.27).
extension for projection;
extend behavior for SalesOrder
{
use association _SalesOrderDiscountCode { create; with draft; }
}
…
define behavior for ZC_SalesOrderDiscountCodeTP
{
use association _SalesOrder { with draft; }
use update;
use delete;
}
Listing 15.27
Behavior Projection Extension
The new entity is now present in the projection, but not yet in the
OData service and its metadata. To achieve this, we also need to
extend the service definition accordingly. Again, it’s necessary to
allow the extension as follows:
@AbapCatalog.extensibility.extensible: true
If the service definition is extensible, we can create an extension to
the service definition. To do this, select New Service Definition from
the context menu on the service definition to be extended and assign
a Name and Description (see Figure 15.9). The Source Type is
already suggested as an extension by the reference.
Figure 15.9
ADT: Create Service Definition Extension
In the service definition extension, we now expose our new entity
according to the familiar syntax (see Listing 15.28).
extend service ZUI_SalesOrderManage with {
expose ZC_SalesOrderDiscountCodeTP as ZZSalesOrderDiscountCode;
}
Listing 15.28
Service Definition Extension
Now the new entity also appears in the OData service and can be
used in the UI. To display it in its own facet in the sales order header,
for example, we still add the UI metadata in a metadata extension
(see Listing 15.29).
@Metadata.layer: #CUSTOMER
annotate view ZC_SalesOrderTP with
{
@UI.facet: [
{
id:
'SalesOrderDiscountCodes',
purpose: #STANDARD,
type:
#LINEITEM_REFERENCE,
label:
'Discount Codes',
position: 40,
targetElement: '_SalesOrderDiscountCode'
}]
_SalesOrderDiscountCode;
}
Listing 15.29
Metadata Extension with New UI Facet
To complete the example, all we need now is the metadata extension
of our own new entity (see Listing 15.30).
@Metadata.layer: #CUSTOMER
@UI.headerInfo.typeName: 'Sales Order Discount Code'
@UI.headerInfo.typeNamePlural: 'Sales Order Discount Codes'
@UI.headerInfo.title.label: 'Sales Order Discount Code'
annotate view ZC_SalesOrderDiscountCodeTP with
{
@UI.facet: [{
id:
'SalesOrderDiscountCodeGeneralData',
purpose: #STANDARD,
type:
#FIELDGROUP_REFERENCE,
label:
'General Data',
targetQualifier: 'GeneralData',
position: 10
},
{
id:
'SalesOrderDiscountCodeAdminData',
purpose: #STANDARD,
type:
#FIELDGROUP_REFERENCE,
label:
'Administrative Data',
targetQualifier: 'AdminData',
position: 20
}]
@UI.hidden:true
SalesOrder;
@UI.hidden:true
DiscountUUID;
@UI.lineItem: [{position: 10, importance: #HIGH}]
@UI.fieldGroup: [{qualifier: 'GeneralData', position: 10, importance: #HIGH }]
DiscountCode;
@UI.fieldGroup: [{qualifier: 'AdminData', position: 110, importance: #LOW }]
CreatedByUser;
@UI.fieldGroup: [{qualifier: 'AdminData', position: 111, importance: #LOW }]
CreationDateTime;
@UI.fieldGroup: [{qualifier: 'AdminData', position: 112, importance: #LOW }]
LastChangedByUser;
@UI.fieldGroup: [{qualifier: 'AdminData', position: 113, importance: #LOW }]
LastChangeDateTime;
}
Listing 15.30
Metadata Extension for UI Annotations of the New Entity
Using Preview in the service binding for the UI service, we can now
see our new entity in the launched SAP Fiori elements application;
edit it in draft; and create, modify, and delete instances for it.
15.4
Summary
In this chapter, we discussed extension options for database tables
and CDS entities in ABAP-based solution variants of SAP S/4HANA
and SAP BTP, which are available in key user apps and for
developer extensibility. We explained the technology that ensures
stable extensions even in case of software upgrades, including the
new ABAP language version ABAP Cloud. Complete examples
covering database tables, CDS entities, and transactional models
concluded the chapter.
The next chapter introduces a method for defining automated tests
for CDS views by substituting their data sources in a controlled test
environment as well as defining automated tests for behavior
definitions and event handlers.
16
Automated Testing
This chapter describes how to develop automated tests for
core data services (CDS) views and how to develop unit
tests for ABAP applications that use CDS views as data
sources for selection requests. Furthermore, it explains how
you can automatically test the logic of your transactional
applications.
CDS views implement main parts of the data provisioning logic of
modern ABAP applications in which complex application logic can be
integrated into the data selection and thus be delegated to the
database for efficient processing. Defining dedicated tests for critical
CDS views becomes an essential measure for quality assurance. In
particular, if the logic of the individual CDS views is complex or if a
large number of CDS views are incorporated in a CDS view stack
(making it difficult to get an overview of its overall functionality), you
should create automated tests. These checks will allow you to
ensure a proper implementation of the desired functionality in the
short term and protect your investments by revealing unwanted
regression issues in the long term. Similarly, nontrivial application
logic in ABAP, which is based on CDS models, also must be tested.
[+] Use Test Automation
Automated tests help you efficiently identify functional regression
issues that may occur during maintenance activities. You should
therefore invest in adequate test automation.
Section 16.1 provides you with information about how you can test
the logic of your applications processed on the database level.
Section 16.2 focuses on the verification of transactional logic, which
you implement in your ABAP RESTful application programming
model-based applications.
16.1
Test Logic of Data Selections
In this section, you’ll learn how you can test the logic of your CDS
views and data selections in ABAP. It starts by providing an overview
of the test double framework in Section 16.1.1, which you can use to
create automated tests. Section 16.1.2 introduces a test sample by
which we’ll explain the use of the test double framework in more
detail. In this context, we show you how to test the implementations
of your CDS models in Section 16.1.3. Furthermore, you’ll learn how
you can test your ABAP logic, which selects data from CDS models,
in Section 16.1.4. We’ll conclude this section by explaining how you
can leverage the code generation options of the ABAP Development
Tools (ADT) environment for implementing your tests in
Section 16.1.5.
16.1.1
Fundamentals of the Test Double Framework
The test double framework enables you to define a stable bed of test
data for intended, repeatable test executions. From a technical
perspective, this is achieved by replacing the original data sources of
the CDS views to be tested or the CDS views themselves with
suitable proxy objects, called test doubles, within the test executions.
Figure 16.1 shows schematically a possible use case for the CDS
test double framework.
Figure 16.1
Use Case for the CDS Test Double Framework Using Test Doubles
In this example, CDS view D to be tested is decoupled from its direct
data sources, CDS views A and C. Instead of these original base
CDS views, substituting test doubles are introduced in the CDS view
stack. These test doubles are used as the sole data sources of
tested CDS view D during test execution; that is, the logic
implemented in CDS views A, B, and C isn’t taken into account.
Consequently, the depicted test of CDS view D only validates its own
logic. You can fill the substituting data sources with any data you
like. This way, you can not only define reproducible data records for
your test but also adjust them flexibly to meet your test requirements.
The main advantage of using the test double framework is that none
of the productive CDS views to be tested, the productive ABAP
implementations, nor the contents of the underlying database tables
of the applications have to be changed for enabling the envisioned
tests. All the changes, which are required for running the tests, are
only reflected in the implementations of the test logic themselves.
In addition, the changes made at runtime are limited to the context of
the executed test. From a technical perspective, they are completely
isolated from other ABAP runtime sessions, meaning you can run
the same test in parallel and also use the productive application
without the corresponding ABAP transactions affecting one another.
In accordance with the use cases, we’ll discuss two specializations
of the test double framework:
CDS test double framework
The CDS test double framework supports a test automation of the
logic, which is implemented in your CDS views.
ABAP SQL test double framework
The ABAP SQL test double framework supports a test automation
for your ABAP implementations that selects data from CDS views
(and other data sources, e.g., ABAP Data Dictionary tables) via
the ABAP SQL interface.
In any case, the corresponding test logic is implemented as an
ABAP unit test.
16.1.2
Test Sample Overview
In subsequent discussions, we refer to the cube CDS view of sales
order items ZI_SalesOrderItemCube from Figure 16.2.
Details of all the involved CDS views and the used CDS role of the
test sample are depicted in Listing 16.1 through Listing 16.5.
Listing 16.1 shows the definition of product CDS view ZI_Product,
which acts as a data source of the cube view.
define view entity ZI_Product
as select from zproduct
{
key product as Product,
product_type as ProductType
}
Listing 16.1
CDS View ZI_Product
Figure 16.2
CDS Views of the Test Sample
Listing 16.2 illustrates CDS view ZI_SalesOrder, which represents
the header of the sales order document. It acts as a data source of
the cube view too.
define view entity ZI_SalesOrder
as select from zsalesorder
{
key salesorder
as SalesOrder,
salesordertype as SalesOrderType
}
Listing 16.2
CDS View ZI_SalesOrder
Listing 16.3 shows CDS view ZI_SalesOrderItem, which represents
the item of the sales order document. It also acts as the primary data
source of the aforementioned cube view.
define view entity ZI_SalesOrderItem
as select from zsalesorderitem
{
key salesorder
as SalesOrder,
key salesorderitem
as SalesOrderItem,
product
as Product,
@Semantics.amount.currencyCode: 'TransactionCurrency'
netamount
as NetAmount,
transactioncurrency as TransactionCurrency,
creationdate
as CreationDate
}
Listing 16.3
CDS View ZI_SalesOrderItem
Listing 16.4 shows cube CDS view ZI_SalesOrderItemCube. This CDS
view joins the data of sales order item ZI_SalesOrderItem with the
data of associated sales order header ZI_SalesOrder and with data
of associated product ZI_Product. In addition, it applies a currency
conversion.
@AccessControl.authorizationCheck: #CHECK
define view entity ZI_SalesOrderItemCube
with parameters
@Consumption.defaultValue: 'EUR'
P_DisplayCurrency : vdm_v_display_currency
as select from
ZI_SalesOrderItem as ITEM
left outer to one join ZI_SalesOrder as SO
on SO.SalesOrder = ITEM.SalesOrder
left outer to one join ZI_Product as PROD
on PROD.Product = ITEM.Product
{
key ITEM.SalesOrder,
key ITEM.SalesOrderItem,
ITEM.Product,
SO.SalesOrderType,
PROD.ProductType,
@Semantics.amount.currencyCode: 'TransactionCurrency'
ITEM.NetAmount,
ITEM.TransactionCurrency,
$parameters.P_DisplayCurrency as DisplayCurrency,
@DefaultAggregation: #SUM
@Semantics.amount.currencyCode: 'DisplayCurrency'
currency_conversion(
amount
=> ITEM.NetAmount,
source_currency
=> ITEM.TransactionCurrency,
target_currency
=> $parameters.P_DisplayCurrency,
exchange_rate_date => ITEM.CreationDate,
exchange_rate_type => 'M',
error_handling
=> 'FAIL_ON_ERROR',
round
=> 'X',
decimal_shift
=> 'X',
decimal_shift_back => 'X'
)
as NetAmountInDisplayCurrency
}
Listing 16.4
CDS View ZI_SalesOrderItemCube
Cube CDS view ZI_SalesOrderItemCube is protected by a CDS role
that has the same name. Listing 16.5 shows the definition of this
CDS role.
@MappingRole: true
define role ZI_SalesOrderItemCube {
grant select on ZI_SalesOrderItemCube
where ( SalesOrderType ) =
aspect pfcg_auth ( V_VBAK_AAT,
AUART,
ACTVT = '03' );
}
Listing 16.5
16.1.3
CDS Role ZI_SalesOrderItemCube
Test Implementations of CDS Views
Before you start implementing your tests, you should spend some
time on their designs. With a proper test design, you can start your
implementation; we'll discuss the main steps for implementing your
tests. However, there are a few particularities you have to consider,
which we'll also cover in the following sections. These comprise
testing CDS access controls, testing conversion functions, and
handling null values.
Create Test Design
Developing automated tests for CDS views requires a custom
design. In the design process, it’s particularly important to clarify
which aspects of the functionality (implemented by the individual
CDS views) will be verified. For example, the focus of a test could
reside on checking a single case statement. However, the test could
also aim at validating the overall functionality and interactions of a
large number of interdependent CDS views of a CDS view stack.
[»] Select CDS Views to Be Tested
A test automation with the test double framework should be
considered specifically if a CDS view, including its data sources,
contains complex logic.
The modeling of simple projection views or the use of standard
SQL functions, such as the summation (sum) of aggregated data
records, usually doesn’t require the implementation of a test
double framework-based test.
Note that the test double framework helps you implement
functional tests. It’s not suitable for implementing performance
tests due to its significant changes of the standard selection logic
of the CDS views to be tested.
In principle, the CDS test double framework can be used for realizing
any decoupling of the CDS views to be tested, both from their direct
and indirect data sources. However, the chosen decoupling option
must always wrap up all the cut-off data sources. For example, if you
want to decouple a CDS view that selects from two data sources,
you have to define test doubles for both data sources.
Figure 16.3 shows you several permitted decoupling options for a
CDS view to be tested. The displayed decoupling options are
explained in more detail next.
Figure 16.3
Exemplary Decoupling Options for a CDS View to Be Tested
In decoupling option 1, the CDS view to be tested is decoupled from
all its direct data sources. This approach is suitable if you want to
solely check the logic implemented within the tested CDS view itself.
You can vary the data provided by the test doubles for simulating all
the data constellations, which are required for achieving high test
coverage. For example, you could define the test data in such a way
that you can execute all when branches of a case statement.
In decoupling option 2, all database tables consumed in the CDS
view logic are replaced by test doubles. This approach enables you
to define an integration test that validates the complete logic of the
CDS view stack. Such a test is particularly suitable for finding
regression problems that may occur when maintaining the tested
CDS view or one of its constituents. For example, you can use your
automated test to ensure that the overall functionality of the tested
CDS view is preserved, even though its underlying CDS views are
changed or even replaced throughout its lifecycle.
In decoupling option 3, certain subtrees within the CDS view stack
are substituted by test doubles. This procedure is particularly
suitable if the cut-off subtrees are tested individually for themselves.
Implement ABAP Unit Tests
As already mentioned, you can only use the test double functionality
within ABAP unit tests.
To implement an ABAP unit test, you should create a global test
class, in which you then include one or more local unit test classes.
Listing 16.6 shows an example global test class
ZTC_ZI_SALESORDERITEMCUBE.
CLASS ztc_zi_salesorderitemcube DEFINITION
PUBLIC
FINAL
CREATE PUBLIC
FOR TESTING.
ENDCLASS.
CLASS ztc_zi_salesorderitemcube IMPLEMENTATION.
ENDCLASS.
Listing 16.6
Definition and Implementation of a Global Test Class
[+] Granularity of Unit Test Classes
You should implement a separate local unit test class for each
CDS view to be tested and for each selected decoupling option.
Within the test class, you can check individual aspects of the CDS
logic by implementing corresponding test methods.
The local unit test classes usually contain the methods, which are
listed in Listing 16.7. We’ll take a closer look at these methods in the
rest of this section.
"!@testing zi_salesorderitemcube
CLASS ltc_zi_salesorderitemcube
DEFINITION FINAL FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA: go_cds_test_environment TYPE REF TO
if_cds_test_environment.
CLASS-METHODS: class_setup.
CLASS-METHODS: class_teardown.
METHODS: setup.
METHODS: <test_select_1> FOR TESTING.
...
METHODS: <test_select_n> FOR TESTING.
ENDCLASS.
Listing 16.7
Definition of a Local Unit Test Class
[»] Assign Test Classes to CDS Views
You can formally assign a test class to its tested CDS view. To do
so, you have to add comment "!@testing <NameOfTestedCDSView>
in front of the definition of the local test class, as illustrated in
Listing 16.7. Comment "!@testing zi_salesorderitemcube
specifies that test class ltc_zi_salesorderitemcube validates CDS
view ZI_SalesOrderItemCube.
To use the CDS test double framework, you must first initialize it in
your test implementation. This is done in the class setup method of
your unit test class by calling method
CL_CDS_TEST_ENVIRONMENT=>CREATE. You specify the CDS view to be
tested as well as the test doubles, which will replace the original data
sources. Furthermore, you should buffer the test environment of the
CDS test double framework, that is, the test context created during
test initialization, in a global static variable such as
GO_CDS_TEST_ENVIRONMENT for subsequent accesses.
Listing 16.8 shows an example in which the test double framework is
initialized for testing CDS view ZI_SalesOrderItemCube from
Listing 16.4. Therein test doubles are specified for underlying
database tables ZSALESORDERITEM and ZSALESORDER and for CDS view
ZI_Product (refer to Figure 16.2).
METHOD class_setup.
DATA: lt_stub TYPE if_cds_test_environment=>ty_qlast_doubles.
lt_stub = VALUE #(
( name = 'ZSALESORDERITEM' type ='TABLE' )
( name = 'ZSALESORDER'
type ='TABLE' )
( name = 'ZI_PRODUCT'
type ='CDS_VIEW' )
).
cl_cds_test_environment=>create(
EXPORTING
i_for_entity
= 'ZI_SALESORDERITEMCUBE'
i_dependency_list = lt_stub
RECEIVING
r_result
= go_cds_test_environment ).
ENDMETHOD.
Listing 16.8
Class Setup Method: Initialization of the CDS Test Double Framework
The setup method is used to prepare the test environment for each
execution of a single test method. As a rule, you should delete all
data records of the test doubles that have already been created in
your ABAP session. This can be achieved by calling method
IF_CDS_TEST_ENVIRONMENT->CLEAR_DOUBLES in accordance with
Listing 16.9.
METHOD setup.
go_cds_test_environment->clear_doubles( ).
ENDMETHOD.
Listing 16.9
Setup Method: Prepare Test Execution
At the very beginning of the individual test methods, which
0
You can add this document to your study collection(s)
Sign in Available only to authorized usersYou can add this document to your saved list
Sign in Available only to authorized users(For complaints, use another form )