This blog post introduces the transactional object models in Core Data Services (CDS).
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.
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.
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 below.
@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
}
It already contains a child association to the sales order item, which is defined in the next listing.
@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
}
This listing shows the sales order item that has associations to both the sales order header and the schedule lines defined in the listing below. That listing 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
}
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.
Read access is controlled via the well-known access control. As we define the transactional layer on top of our basic layer, we can simply inherit the access control from the underlying CDS entities. The listing below shows the inherited access control of the sales order header.
11
@EndUserText.label: 'Sales Order'
@MappingRole: true
define role ZR_SalesOrderTP {
grant
select
on
ZR_SalesOrderTP
where
inheriting conditions from entity
ZI_SalesOrder;
}
The access control for the sales order item is shown here.
@EndUserText.label: 'Sales Order Item'
@MappingRole: true
define role ZR_SalesOrderItemTP {
grant
select
on
ZR_SalesOrderItemTP
where
inheriting conditions from entity
ZI_SalesOrderItem;
}
The access control of the sales order schedule line is shown in the next listing.
@EndUserText.label: 'Sales Order Schedule Line'
@MappingRole: true
define role ZR_SalesOrderScheduleLineTP {
grant
select
on
ZR_SalesOrderScheduleLineTP
where
inheriting conditions from entity
ZI_SalesOrderScheduleLine;
}
Editor’s note: This post has been adapted from a section of the book Core Data Services for ABAP by Renzo Colle, Ralf Dentzer, and Jan Hrastnik.