In my previous blog post [Part – 1] Enrich CAP based Services with Behaviors using Annotations, We explored how to enrich CAP based services with access control and input validation related behavioral capabilities using annotations.
In this blog post, We will see about Service/API Annotations, Persistence Annotations, OData Annotations to alter behavior of services.
Glossary of Annotations [part -2]
Service / APIs Annotations
⇒[ @cds.autoexpose ] :
An entity annotated with @cds.autoexpose is automatically exposed in services if it is associated by other entities which are explicitly exposed in service. Let’s look at a simple example.
schema.cds | service.cds |
|
|
In above example, Companies is explicitly provided in Service. Also Companies is associated to Industries and Industries entity is annotated with @cds.autoexpose. Hence Industries is also added to the services. However only READ operation is allowed on Industries. If you want to make Industries write enabled as well, then it needs to be explicitly exposed in Service.
This is particularly helpful in case of Code Lists that is used in UI.
⇒[ @cds.api.ignore ] :
This annotation is used to supress unwanted entity fields. Note that, It only supresses regular properties and does not supress navigation property.
From Example 1, if info is annotated with @cds.api.ignore then it is ommited from the service and if industry is annotated with @cds.api.ignore then industry_id (foregin key) is omitted. However industry navigation property is still available in the service as shown below.
<EntityType Name="Companies">
<Key>
<PropertyRef Name="ID"/>
</Key>
<Property Name="ID" Type="Edm.Guid" Nullable="false"/>
<Property Name="name" Type="Edm.String" MaxLength="100"/>
<NavigationProperty Name="industry" Type="Service.Industries">
<ReferentialConstraint Property="industry_id" ReferencedProperty="id"/>
</NavigationProperty>
<Property Name="industry_id" Type="Edm.String" MaxLength="3"/>
</EntityType>
⇒[ @cds.query.limit ] :
READ operation on an entity provides 1000 entries at maximum. if more than 1000 entries are there, then it can be retrieved using $skiptoken. However this can be influenced using @cds.query.limit annotation. It has 2 properties: default and max.
default: defines the number of items that are provided if no $top was specified.
max: defines the maximum number of items that are provided, regardless of $top
@cds.query.limit.default: 20
@cds.query.limit.max: 100
service Service @(path: '/browse'){
entity Customers as projection on db.Customers;
entity CustomerAddresses as projection on db.Addresses;
@cds.query.limit: { default: 10, max: 200 }
entity Companies as projection on db.Companies;
}
In above example, 20 entries are provided by default for read operations on Customers and CustomerAddresses, but only 10 entries are provided for Companies. The maximum number of entries that can be retrieved for Customers and CustomerAddressed is 100. However, 200 entries for Companies can be retrieved at once.
Note: The closest limit applies, that means, an entity-level limit overrides that of its service, and a service-level limit overrides the global setting.
⇒[ @odata.etag ] :
CAP runtimes support optimistic concurrency control and caching techniques using ETags. An ETag identifies a specific version of a resource found at a URL. When updating, deleting, or invoking the action associated with an entity, you use the ETag value in an If-Match or If-None-Match header.
The value of an ETag element should uniquely change with each update per row. The modifiedAt element from the managed aspect is a good candidate.
annotate Service.Companies:modifiedAt with @odata.etag;
Use exclusive locks when reading entity data with the intention to update it in the same transaction and you want to prevent the data to be read or updated in a concurrent transaction.
Use shared locks if you only need to prevent the entity data to be updated in a concurrent transaction, but don’t want to block concurrent read operations.
For exclusive locks or shaed locks, it has to be handled with custom logic using .forUpdate() and .forShareLock() method.
⇒[ @cds.search ] :
CAP runtimes provide out-of-the-box support for search of a given text in all textual elements of an entity. This search is done by making a READ operation using $search query parameter.
GET .../Custoemrs?$search=Sheldon
By default all elements of type String of an entity are searched. To deviate from this defaulr behavior, @cds.search annotation is used.
Search in Certain Element Only:
annotate Customers with @cds.search: {firstName, lastName};
CAP runtime only searches the input in firstName and lastName field of Customers entity.
Exclude Elements from Being Searched:
annotate Customers with @cds.search: {creditCardNo: false};
CAP runtime searches all String elements of Customers entity except creditCardNo element.
Persistence Annotations
⇒[ @cds.persistence.exists ] :
This annotation for an entity tells the compiler this entity is already exist in database.
⇒[ @cds.persistence.skip ] :
This annotation for an entity tells the compiler, this entity shall not exist in database at all.
⇒[ @cds.on.insert / @cds.on.update ] :
These two annotations are used to signify elements to be auto-filled by CAP run time upon insert and update.
entity Processes : cuid {
name : String(255);
scheduledAt : Timestamp @cds.on.insert: $now;
scheduledBy : String(255) @cds.on.insert: $user;
changedAt : Timestamp @cds.on.insert: $now @cds.on.update: $now;
changedBy : String(255) @cds.on.insert: $user @cds.on.update: $user;
}
In above example, scheduledAt, scheduldBy, changedAt, changedBy field are auto filled by CAP runtime based on the pseudo-variable $user and $now.
$now : current server time in UTC
$user : current user’s ID as obtained from the authentication middleware
Note:
- Data can be filled with initial data through .csv files
- Data can also be set explicitly in custom handlers.
OData Annotations
⇒[ @odata.Type, @odata.Precision, @odata.Scale] :
Using these annotations, mapping between CDS built-in type and OData EDM type can be overriden. Let’s look at couple of examples:
entity Process {
@odata.Type:'Edm.String'
key ID : UUID;
@odata: {Type: 'Edm.Decimal', Precision: 7, Scale: 'variable' }
value: String(17);
}
Elements of type UUID maps to Edm.GUID by default and OData standard has restrictive rules for GUID values like only hyphenated strings are allowed. This restrictions can confilict with existing data. So @odata.type annotation can be used to override the mapping and indicate that element is a string.
Simillarly @odata.Type, @odata.Precision and @odata.Scale can be used to change the mapping between string and decimal as shown in above example.
⇒[ @odata.singleton, @odata.singleton.nullable] :
A singleton is a special one-element entity introduced in OData V4. It can be addressed directly by its name from the service root without specifying the entity’s keys
service MyService {
@odata.singleton
entity firstProcess as select from Processes order by scheduledOn;
}
As mentioned above, singletons are accessed without specifying keys in the request URL. They can contain navigation properties, and $expand query option is also supported as shown below.
GET …/firstProcess
GET …/firstProcess/scheduledOn
GET …/firstProcess?$expand=association
It is also possible to update a singleton. However a singleton can not be created.
PATCH/PUT …/firstProcess
{name: “New value”}
Deletion of a singleton is possible only if a singleton is annotated with @odata.singleton.nullable
Conclusion
In above example and explanations, we explored that how easy it is to add or modify behaviors of CAP bases services using annotations.
More information about cloud application programming model can be found here. You can follow my profile to get notification of the next blog post on CAP. Please feel free to provide any feedback you have in the comments section below and ask your questions about the topic in sap community using this link.