Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
maxrupp
Explorer
17,295

Introduction


How to handle transactional operations in a “stateless world" like SAPUI5? This is a central question which should be addressed in every architecture design phase. One possible scenario I want to cover in this blog is the optimistic locking approach with ETag handling:

Within an update operation, the backend will not actual lock the data. Instead, the data will be checked before updating and it ensures, that the operation (CREATE, UPDATE) will be done on the latest version of the entity.

ETags in a request sequence


After a single read of an entity, the backend service transforms the so called ETag via HTTP response to the client. Once the UI requests an UPDATE, the ETag value must be send back to the server via the IF-Match Header.

Before executing any operation, the backend service compares the transmitted value with the current value of the database. If they match, the operation will be executed. Otherwise an HTTP Error (412 – Precondition failed) will be thrown. Due to this mechanism, the client gets a notification, that a “newer” version of the entity exists.

If it is acceptable for a given client to overwrite any version of the Entry in the server, then the value “*” may be used instead. In that case, there won’t be a precondition check in the backend.

Scenario


SAP Gateway and UI5 supports ETag handling. There are some great blog postings about how to configure your SAP Gateway to implement the precondition check.

In this tutorial, I want to focus on the UI handling for optimistic locking.

Our example is based on a demo Service (ZSEPM_C_SALESORD_UPDATE_ENTITY). The backend configuration is covered in this tutorial from Andre Fischer.

The good news is: If the backend configuration is done, there is no additional effort within the UI in terms of keeping the ETag or sending it back to the backend. The oData model takes care of this!

However… we should consider an appropriate user guidance: We want to notify the User if an update fails. Then there should be a notification, that a more recent version would be available. The user gets the opportunity to refresh to the most recent version or overwrite the backend entry with his changes.

UI5 Application


The first screen of our demo app displays a table of sales orders.



By selecting an item, there will be a navigation to the detail page, where the property “SalesOrderText” can be updated.



Our view is straight forward: A Simple form with some fields and a button to trigger the update event:
<f:SimpleForm id="SalesOrder" title="Sales Order {SalesOrder}" editable="true" Layout="ResponsiveGridLayout" singleContainerFullSize="false">
<f:toolbar>
....
</f:toolbar>
<f:content>
<Label text="Text"/>
<Input value="{SalesOrder_Text}" editable="{detailView>/editMode}"/>
<Label text="Net Amount"/>
<Input value="{NetAmountInTransactionCurrency}" editable="false"/>
<Label text="Gross Amount"/>
<Input value="{GrossAmountInTransacCurrency}" editable="false"/>
</f:content>
</f:SimpleForm>

I want to focus on the update operation within the detail page. The onSave function looks like this:
onSave: function(oEvent) {
this._update();
},

_update: function() {
var sPath = that.getView().getBindingContext().getPath();
var oSalesOrder = that.getView().getBindingContext().getObject();

this._updateSalesOrder(sPath, oSalesOrder)
.then(function() {
MessageToast.show("Success...");
})
.catch(function(oError) {
//Error handling...
});
},

The function _updateSalesOrder returns a promise to retrieve the result of the oData update:
_updateSalesOrder: function(sPath, oSalesOrder, bForceUpdate) {
var oDataModel = this.getView().getModel();
return new Promise(function(resolve, reject) {
oDataModel.update(sPath, oPlan, {
success: resolve,
error: reject
});
});
},

So far, nothing special...

But If the update fails due to the precondition error, we want a dialog (sap.m.dialog) appearing on the screen
.catch(function(oError) {
// Error handling
//open Dialog if Precondition failed
if (oError === "412") {
that._openDialog();
}
});



The appropriate coding for the dialog looks like this:
_openDialog: function() {
var that = this;
var dialog = new Dialog({
title: 'Confirm',
type: 'Message',
content: new Text({
text: 'There is a more recent version available. Do you want to refresh or overwrite the backend entry?'
}),
beginButton: new Button({
text: 'Overwrite',
press: function() {
that._update(true);
dialog.close();
}
}),
endButton: new Button({
text: 'Refresh',
press: function() {
//Refresh entity....
dialog.close();
}
}),
afterClose: function() {
dialog.destroy();
}
});
dialog.open();
},

By submitting the change, our update function will be called again.

Here, we want to ignore the precondition check. Therefore we must add an additional parameter to our update function (bForceUpdate).

Once this value is true, we will set the parameter “eTag” of the oData model manually:

mParameters.eTag = "*";

 

For this we also have to adjust _updateSalesOrder.  At the end these two functions look like this.
_update: function(bForceUpdate) {
var that = this;
var sPath = that.getView().getBindingContext().getPath();
var oModel = that.getView().getModel();
var oSalesOrder = that.getView().getBindingContext().getObject();

this._updateSalesOrder(sPath, oSalesOrder, bForceUpdate)
.then(function() {
MessageToast.show("Success...");
})
.catch(function(oError) {
// Error handling
//open Dialog if Precondition failed
if (oError === "412") {
that._openDialog();
}
});
},

_updateSalesOrder: function(sPath, oSalesOrder, bForceUpdate) {
var oDataModel = this.getView().getModel();
var oPromise = new Promise(function(resolve, reject) {
var mParameters = {
success: resolve,
error: function(oError) {
reject(oError.statusCode);
}
};
if (bForceUpdate) {
mParameters.eTag = "*";
}

oDataModel.update(sPath, oSalesOrder, mParameters);
});
return oPromise;
},

Conclusion


Etag handling is very well supported in SAP Gateway and UI5. The ETag value is cached within the entity of the oData model. It will be handed over to the backend with every request. The Gateway framework takes care of the whole precondition checks.

With this "out of the box" support, there are just some small UI adoptions necessary in order to setup a complete optimistic locking approach for transactional apps.
10 Comments