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

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
MattHarding
Active Contributor
0 Kudos
Nice post for an important topic (though risky in many scenarios to let a user overwrite a recent update, even if they made the update themselves)

But I have a quick question that I still don't know the official answer to about one of your comments in the code...e.g.

//Refresh entity....

Is there a nice way to implement this without refreshing everything in your odata model (including drop downs)?

All ways I've discovered are a little hacky in my opinion because of the smarts behind odata caching entities. e.g. a read will not necessarily retrieve from the backend if there is a local cached version already. I had to hack it a little to ensure in my app, that when switching objects, the eTag was getting updated if they had not navigated to it for a long time.

Cheers,

Matt
maxrupp
Explorer
0 Kudos
Hi Matt!
Did you check the refresh method of the ODataModel?
This will check all bindings and updates the controls if data has been changed. There is also a ForceUpdate parameter provided.
MattHarding
Active Contributor
0 Kudos
Hi Max,

I did try these and this is what I was referring to above as refresh everything. In other words, these are the brute force refresh everything methods which even remove your existing drop down entity sets that may have already loaded...e.g. Use this and be careful as it breaks existing bindings against comboboxes as an example.

Maybe Odata v4 will fix everything 😉

Cheers,

Matt
0 Kudos
Hi Matt,

Incase you still haven't found the solution.....

Did you have a look at Delta Query? May be this suits your need..!

Here is one blog I came across....not sure if you have already tried this:  https://blogs.sap.com/2013/09/30/how-to-implement-basic-delta-query-support-in-sap-netweaver-gateway...
0 Kudos
I just couldn't leave your website before suggesting that I actually enjoyed the usual information a person provide to your visitors? Is gonna be back steadily to inspect new posts
TimMuchena
Participant
0 Kudos
Hi

Thanks for the excellent blog.

Does etags work when the same object being changed in Fiori is changed via GUI tcode?

 

Kind regards
former_member267571
Discoverer
0 Kudos
Hi Timothy,

I guess it depends on how the ETag is implemented. If something like "last changed on" is used as the ETag property (and "last changed on" is also updated by SAPGUI) it should work.

Best Regards
Nils
TimMuchena
Participant
0 Kudos

Hi Nils

Thanks for your response.

I was actually referring to the lock taking effect on the GUI side. When you launch a GUI tcode to update the same object being updated in Fiori, does the lock stop the GUI update?

 

Thanks

michael_dohse2
Explorer
0 Kudos

Hi Timothy

If the Sap Gui Transaction does consider the defined ETag (I would wonder if it does), then it might stop the update.

I am not aware of any SAP Gui transaction (Standard) which is implementing ETags.

Regards

Michael

former_member267571
Discoverer

Hi everyone,

as ETags are supposed to be used in addition to database (enqueue) locks, this scenario should still work.

So if there is a modifying request from the UI, the backend should follow this sequence:

  1. acquire database lock on the object to be modified
  2. check DB ETag vs. Request ETag
    1. if DB ETag != ETag in the request => reject change
    2. if DB ETag == ETag in the request => do change on the DB
  3. unlock database object

The SAPGUI transaction will instead hold the database lock the entire time (while in edit mode), so the incoming request from a parallel UI5 app would be stopped at step 1 already. No lock can be acquired because the SAPGUI transaction is holding it.

Steps 1/3 in the sequence above are mandatory, otherwise a parallel request could go unnoticed (race condition). Also it solves the SAPGUI interoperability issue that Timothy originally asked about.