Overview
Dear Community, In the dynamic landscape of SAP Business Technology Platform (BTP), developers often encounter intricate challenges that demand innovative solutions. This blog delves into a very recent scenario encountered in a customer's specific requirement for efficiently handling long-running operations within a single-page Fiori application deployed on SAP BTP using Cloud Application Programming.
In modern enterprise applications, handling long-running operations asynchronously is crucial to maintain responsiveness and user satisfaction. SAP BTP offers robust capabilities for managing such scenarios, but custom solutions are sometimes necessary.
Reason for this Topic:
This blog explores the necessity and implementation of custom schedulers within SAP BTP, particularly when dealing with long-running operations that exceed typical response time expectations. It addresses scenarios where customers face challenges due to stringent performance requirements or the native SAP BTP Job scheduling services are not in scope.
The journey begins with a deep dive into the limitations of standard synchronous processing, the standard gateway timeout of 30sec for SAP Fiori applications and the absence of SAP BTP's native job scheduling services in certain customer environments.
Problem:
Imagine a scenario where a single-page Fiori application on SAP BTP needs to initiate operations that can take longer than 30 seconds sometimes. The Fiori Gateway timeout imposes a standard limit, compelling APIs to respond within this timeframe to prevent timeouts and ensure uninterrupted user interaction. Traditional synchronous approaches would result in poor user experience or even gateway timeouts. Additionally, without SAP BTP's built-in job scheduling service, customers are left seeking alternative solutions. The challenge of ensuring seamless execution of critical tasks without compromising system responsiveness. This necessitates exploring alternative strategies to maintain application performance while handling complex, time-consuming operations effectively.
Approach:
To illustrate this, let’s consider a requirement: initiating an HTTP request from a Fiori application that triggers a database operation taking more than 30 seconds. The challenge is to respond within 20 seconds with a status 202 (Accepted) while allowing the operation to continue in the background. Subsequently, upon completion of background thread, logging the results back to a persistence layer. Typical gateway timeout for Fiori screen is 30 sec. There are ways in which we can work with gateway window’s as well. (Will be explained later).
We have 2 scenarios in this situation:
In the above use case – the location is not returned in response header. We can work in the other way as well here to return the location of response in header of the response – which should be called again to check the status of the long running operation’s execution and the result.
*Typically, for executing background tasks with long running operations the SAP BTP Job scheduling service would be the easiest and most promising solution.
Solution:
The first challenge in above scenario is to send response based on the conditions mentioned below.
If the Response from API is within 20 sec
- The response is transmitted on the screen as expected.
If the Response from API is taking longer than 20 sec
- The custom response is transmitted to avoid timeout and poor user experience
- The DB operations are updated with logs after completion of parallel promise.
To address this issue, we can leverage a strategy of using asynchronous functions and a combination of Promise, setTimeout, and async/await to achieve the desired behavior for POC.
Consider the below code snippet:
// Simulated function that takes a long time (more than 30 seconds)
async function longRunningOperation() {
// Simulate a long running process
return new Promise((resolve, reject) => {
setTimeout(async () => {
// Simulated result after 35 seconds
// resolve({ message: 'Long-running operation completed!' });
return_res = await next(); //SELECT.from(Books);
// Updating the Job Logs Table upon execution
zupdate_res = await INSERT.into(Books_logs).entries(
zpayload
);
resolve(return_res);
}, 35000); // 35 seconds
});
}
This above function is an async function to simulate long running operation. Inside, it creates a Promise that resolves after a window of 35 seconds using setTimeout.
Now comes the main try-catch block:
try {
// Start the long-running operation
const result = longRunningOperation();
// Send success response if operation completes within timeout
// Set a timeout to respond with 202 after 20 seconds
const timeoutPromise = new Promise((resolve, reject) => {
setTimeout(() => {
// you can use cds.context.http.res.status(202);
resolve({ status: 202, message: 'Operation in progress.' });
}, 20000); // 20 seconds
});
// Race between the long operation promise and timeout promise
// lets goooo....
const final_result = await Promise.race([result, timeoutPromise]);
// Check which promise resolved
if (final_result.length > 0) {
// Operation completed within 20 seconds
console.log('Operation completed:', final_result.message);
// cds.context.http.res.status(200);
return final_result;
} else {
// Respond with 202 after 20 seconds
console.log('Operation in progress.');
// cds.context.http.res.status(202);
req.notify("Your request seems to be taking longer than expected. It will be executed in background!!")
// return final_result;
}
} catch (error) {
// Handle error (not expected in this demo since longRunningOperation always resolves)
console.error('Error:', error);
req.notify("Your request seems to be taking longer than expected. It will be executed in background!!");
}
Key highlights of the above snippet:
‘const result = longRunningOperation()’: This initiates the long-running operation. longRunningOperation() is assumed to be a function that returns a Promise which resolves when the operation completes. (Could be within 20 sec or more than 20 sec)
‘const timeoutPromise = new Promise((resolve, reject) => { ... })’: This creates a Promise using setTimeout that resolves with a status 202 and a message ('Operation in progress.') after 20 seconds.
Promise.race :
‘const final_result = await Promise.race([result, timeoutPromise])’: This uses Promise.race() to race between result (the long-running operation promise) and timeoutPromise. Whichever Promise settles first determines the value of final_result.
Then we can handle the final_result accordingly.
If longRunningOperation responds back first --> then we return the response as expected.
If the timeoutPromise responds back first --> then we return a status code 202 along with a pop up message. In the later case the long running operation keeps executing as expected on a parallel thread.
Notes:
Simulated Run of the POC:
CASE I:
Expected outcome is a pop-up message on the UI screen after 20 seconds. Indicating the processing is in progress and taking longer than expected time. We would be able to see the results in the Job Logs application.
After 20 sec:
After 35 sec we can check the Job Logs Application – which will fetch the logs from DB table which got updated by the long running operation.
Case II:
Expected outcome is the data which was expected as response on the UI screen after 10 seconds. Indicating the processing was completed within 20 seconds and long running operation won the race between 2 promises.
Alternatives:
While exploring I checked out that the gateway timeout for Fiori can be extended and timeouts can be configured in both managed and standalone approaches (Using MTA properties and destination properties or through your code). There are certain standards which we should adhere to even while manipulating these timeout windows. I will discuss this on another blog post in detail along with the pros and cons.
Conclusion:
By leveraging custom schedulers and asynchronous processing techniques within SAP Business Technology Platform (BTP), we can effectively manage long-running operations while adhering to Fiori Gateway timeout windows. This approach not only ensures that APIs respond within the stipulated window but also enhances application resilience and user experience.
The solution presented in this blog addresses the practical challenges faced by developers in scenarios where operations from Fiori may exceed typical response times. It emphasizes the importance of asynchronous handling, promises capabilities and decoupled execution logic, enabling seamless integration with external schedulers as well without native BTP scheduling service.
*We can also create an entity in CAP which will be responsible for flushing the records of past 7 days from the Jobs DB table. Ensuring the retention of up to 7 days for logs.
Happy Learning!!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
8 | |
6 | |
5 | |
5 | |
4 | |
4 | |
4 | |
3 | |
3 |