Cloud Application Programming Model (CAP) is a set of languages, libraries, and tools that are used to create robust services and applications for businesses on SAP Business Technology Platform (BTP). It leads developers down a “golden path” of tried-and-true best practices and a plethora of out-of-the-box solutions to recurring tasks.
A CAP application typically provides services defined in CDS models and served by NodeJS and JAVA runtimes. They represent a domain’s behavioral aspects in terms of exposed entities, actions, and events.
In this blogpost, we will see how a CAP based service can be enriched with additional functional or business capabilities in addition to CRUD operations using diverse set of annotations.
Overview of Annotations
An annotation enriches a service definition with metadata and provides semantical information. Annotations are prefixed with @ and can be placed before a definition, after the defined name or at the end of a definition as shown below.
@before entity Foo @inner { @before simpleElement @inner : String @after; @before structElement @inner {/* elements */ }. }
Multiple annotations can be placed in each spot separated by whitespaces or enclosed in @(...)
and separated by comma. It is also possible to add annotations in a separate file using annotate
directive.
annotate Foo with @(readonly: true); annotate Foo:simpleElement with @readonly;
All annotations are mapped to EDMX outputs i.e. OData metadata as a Term with either a primitive value, a record value, or collection values.
<Annotations Target="MyService.Customers">
<Annotation Term="Common.Label" String="Customer"/>
<Annotation Term="Common.ValueList">
<Record Type="Common.ValueListType">
<PropertyValue Property="Label" String="Customers"/>
<PropertyValue Property="CollectionPath" String="Customers"/>
</Record>
</Annotation>
</Annotations>
You can find more information about this here.
Glossary of Annotations [part -1]
Access Control Annotations
⇒[ @readonly ] :
It is used to protect against write or delete operations and can be used for both entities and elements of an entity.
annotate Customers with @readonly;
Here, Customers can only be read. Create, Update & Delete operations are prohibited. This adds following semantical information to service metadata:
<Annotations Target="Service.EntityContainer/Customers">
<Annotation Term="Capabilities.DeleteRestrictions">
<Record Type="Capabilities.DeleteRestrictionsType">
<PropertyValue Property="Deletable" Bool="false"/>
</Record>
</Annotation>
<Annotation Term="Capabilities.InsertRestrictions">
<Record Type="Capabilities.InsertRestrictionsType">
<PropertyValue Property="Insertable" Bool="false"/>
</Record>
</Annotation>
<Annotation Term="Capabilities.UpdateRestrictions">
<Record Type="Capabilities.UpdateRestrictionsType">
<PropertyValue Property="Updatable" Bool="false"/>
</Record>
</Annotation>
</Annotations>
Hence @readonly is equals to @Capabilities: {Insertable: false, Updatable: false, Deletable: false};
When a create/update/delete operation is made, an error will be returned as response with error code 405 – Method not allowed.
annotate Customers:creditCardNo with @readonly;
It adds following details to the service metadata:
<Annotations Target="Service.Customers/creditCardNo">
<Annotation Term="Core.Computed" Bool="true"/>
</Annotations>
If a CREATE or UPDATE operation provides value for such fields like creditCardNo, values are silently ignored.
⇒[ @insertonly ] :
Simillar to @readonly, Only CREATE operation is allowed on entity which is annotated with @insertonly. @insertonly annotation can only be annotated with an entity. All other operations like READ, UPDATE, DELETE are prohibited.
annotate Customers with @readonly;
⇒[ @restrict / @requires ] :
By default CAP based services do not have any access control. To protect your resources according to business requirements, annotations @restrict and @requires can be used. These annotations can be used to define authorizations by choosing proper heirarchy level.
For example, you can define restriction on service level which will apply to all entities in the service. It is also possible to define restrictions on an entity or an instance of an entity.
Authorization is a complex and large topic. Hence this is not covered in this blogpost. More information can be found on this page.
Input Validation Annotations
⇒[ @mandatory ] :
Checks are made for nonempty input on elements designated with @mandatory. Trimmed empty string, null are considered empty input and not allowed. However, for an integer element, 0 is considered nonempty input and allowed. If an empty input is provided, then error response is returned with code 400 – Bad Request.
annotate Customers:creditCardNo with @mandatory;
Same functionality can also be achieved using OData Field Control annotations as below:
annotate Customers:creditCardNo with @Common: {FieldControl : #Mandatory};
⇒[ @assert.unique ] :
It is used to enforce uniquenes check on CREATE and UPDATE operations. Uniqness can be defined using one or more elements of the entity. It is also possible to define multiple uniqueness checks for one entity.
This annotation can only be used for entities which results in table in SQL Databases. Note that, it is added to DB schema CDS definition as it creates a unique constraint in DB.
Let’s look at an example:
schema.cds
namespace db;
using { cuid } from '@sap/cds/common';
@assert.unique.name:[firstName, lastName]
entity Customers : cuid {
email : String;
firstName : String;
lastName : String;
}
DB Table definition in SQL (After Compilation)
CREATE TABLE db_Customers (
ID NVARCHAR(36) NOT NULL,
email NVARCHAR(5000),
firstName NVARCHAR(5000),
lastName NVARCHAR(5000),
PRIMARY KEY(ID),
CONSTRAINT db_Customers_name UNIQUE (firstName, lastName)
);
So annotation @assert.unique.name:[firstName, lastName] becomes:
in SQL: CONSTRAINT db_Customers_name UNIQUE (firstName, lastName)
in HANA: UNIQUE INVERTED INDEX db_Customers_name ON db_Customers (firstName, lastName)
If uniquness check fails, then error response is returned with message “Entity already exists” with code 400 – Bad Request.
⇒[ @assert.integrity ] :
During CREATE and UPDATE operations, referential integrities are checked for all managed association to-one and composition to-one. It means, if an entry in associated / composed to one entity does not exist, then operation is rejected. Also DELETE operations are rejected if it would result in dangling references.
cds.env.features.assert_integrity
global configuration is used to switch on/off this feature. More information is available at following pages: Configurations, Integrity Constraints.
One of the way to switch this feature on is providing following details in .cdsrc.json .
{
"features": {
"assert_integrity": "db"
}
}
Let’s see an example
|
|
By default, @assert.integrity is set to true. If the feature is switched on in global configuration and in industries entity, ‘RE’ instance is not available, then the above call will result in an error with message ‘Foreign key constraint violation’. You can switch for a particular entity by annotating it with @assert.integrity: false even though globally feature is on.
⇒[ @assert.format ] :
It is used to specify a regular expression string that all string input must match.
annotate Customers {
creditCardNo @assert.format: '^[0-9]{16}$';
firstName @assert.format: '^([a-z]|[A-Z]){1}';
};
In above example, crediCardnNo element is checked for 16 character Numeric string and firstName is checked if first letter is a character.
^ : regular expression should be checked from start of the string
$ : regular expression should be checked till end of the string
[0-9]: range of numbers
{16} : 16 characters are to be checked with previous expression
| : either or expression is chcked with string input
⇒[ @assert.range ] :
It is used to check if input is in between the range min ≤ input ≤ max
and can be defined for Integer, Date, Decimal or String as shown below:
entity Customers : cuid, managed {
email : String;
name : String;
dateOfBirth : Date @assert.range: [ '2018-10-31', '2019-01-15' ];
age : Integer @assert.range: [ 18, 100 ];
priority : String @assert.range enum { high; medium; low; };
}
Conclusion
In above example and explanations, we explored that how easy it is to add additional behaviors (access control and input validations) to CAP bases services using annotations.
In [Part – 2] Enrich CAP based Services with Behaviors using Annotations blog post we will see more about Service/API Annotations, Persistence Annotations, OData 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.