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: 
frank_essenberger1
Participant

Introduction


Version 3 of the SAP Cloud SDK brings a powerful new feature: middlewares. Middleswares allow you to add arbitrary code to the HTTP request built by the SAP Cloud SDK. You can have multiple middlewares added to your request all doing a small task. The use cases are very diverse:

  • You could adjust header fields of the request config.

  • You could change the response payload after the request resolves.

  • You could introduce a cache for HTTP requests.

  • ...


In particular you can add middlewares increasing the resilience of your HTTP requests. Version 3 comes with a few default middlewares offering resilience features like timeout, circuit breaker and retries.

In this blog post we only show some examples how to add resilience to a typed client generated with the SAP Cloud SDK. For a full documentation please have a look at the resilience documentation or watch the community call on the resilience topic.

Adding a Timeout


Assume you develop a customer facing application. The application uses generated OData clients to fetch data from different systems:
import { myService } from './generated-client';

const { myApi } = myService();
const myData = await myApi.getAll()
.middleware()
.execute(myDestination);

The data retrieval works most of the time but sometimes the system responds slowly. You would like to add a timeout to those slow requests and request the data from a alternative faster system. Just add the timeout middleware:
import { myService } from './generated-client';
import { timeout } from '@sap-cloud-sdk/resilience';

const { myApi } = myService();
let myData = [];
try {
myData = myApi.getAll()
.middleware(timeout())
.execute(myDestination);
catch(e){
myData = myApi.getAll()
.middleware(timeout())
.execute(myFallbackDestination);
}

Adding a Retry


After you added a timeout you realize that the target system and network introduce some flakiness and a timeout is not enough. You can not really fix the problem, because it is out of scope for your application. So you decide to add a retry to mitigate the problem:
import { myService } from './generated-client';
import { timeout } from '@sap-cloud-sdk/resilience';
import { retry } from '@sap-cloud-sdk/resilience';

const { myApi } = myService();
let myData = [];
try{
myData = myApi.getAll()
.middleware(retry(), timeout())
.execute(myDestination);
catch(e){
myData = myApi.getAll()
.middleware(retry(), timeout())
.execute(myDestination);
}

Per default the retry middleware reexecutes the request up to three times with an exponentially growing time between the requests. This made the requests stable and the users of your application are happy.

Note that the SAP Cloud SDK interacts with SAP BTP services like the XSUAA or destination service. The requests sent during those interactions have a meaningful resilience added per default and you do not have to worry about these. The added middlewares only apply to the request reaching your destination.

Adding a Circuit Breaker


The SAP Cloud SDK offers also a circuit breaker middleware. If added, requests are monitored over a time window. If a certain error threshold is reached, the circuit breaker opens. From that moment on, all requests are blocked by the circuit breaker and the system gets a break to recover.

After a while the circuit breaker will gradually close again to let some requests through. If these requests work, it will completely close again and requests can flow as before. The circuit breaker is added as follows:
import { myService } from './generated-client';
import { timeout } from '@sap-cloud-sdk/resilience';
import { circuitBreakerHttp } from '@sap-cloud-sdk/resilience';

const myData = myApi.getAll()
.middleware([circuitBreakerHttp(), timeout()])
.execute(myDestination);

The SAP Cloud SDK creates one circuit breaker instance per system (represented by the base URL of the destination) and tenant (SAP BTP account). Each instance records the request statistics separately. Therefore circuit breakers are isolated on a per system and tenant basis.

The Order Matters


Middlewares are added from right to left to the initial function. This is in line with the function composition methods offered by Rambda or lodash. For the order of the middleware from the previous example
myApi.getAll()
.middleware(retry(), timeout())
.execute(myDestination);

this means that the timeout is wrapped first around the initial HTTP request. This composed function function is then retried up to three times in case the request fails. If you would reverse the order:
myApi.getAll()
.middleware(timeout(), retry())
.execute(myDestination);

the initial HTTP request would be retried three times and the whole process would be wrapped in a  timeout. If you talk about a timeout for a request you in general assume a timeout for the individual request, but to limit the overall time is sometimes also relevant. Assume you want a 5 second timeout per request and a 10 second timeout for everything including the retries you would use:
myApi.getAll()
.middleware(timeout(10000), retry(), timeout(5000))
.execute(myDestination);

So there is no right or wrong order, it all depends what you want to achieve with your resilience middlewares.

The Default Resilience Middlewares


Since the order of middlewares is a tricky topic, the SAP Cloud SDK offers a default resilience middleware containing a timeout and circuit breaker. We recommend to add this middleware to your request:
import { myService } from './generated-client';
import { resilience } from '@sap-cloud-sdk/resilience';

const [circuitBreaker, timeout] = resilience();

const { myApi } = myService();
myApi.getAll().middleware(resilience()).execute(myDestination);

Note that you can also configure resilience to include a retry if you need it:
type ResilienceOptions = {
// default is true with 10 seconds timeout.
// A number indicates the timeout in milliseconds.
timeout?: boolean | number;
// default is true.
circuitBreaker?: boolean;
// default is false. A number indicates the number of retries.
retry?: boolean | number;
};

const [retry, circuitBreaker, timeout] = resilience({
retry: true
});

const [retry5times, timeout5Seconds] = resilience({
timeout: 5000,
curcuitBreaker: false,
retry: 5
});

The order should work in most cases. A timeout is added for each request first. If the circuit breaker opens it would block the retries as well.

Do Your Own Thing


The idea behind middlewares is that you can easily build your own logic. For example, we have not yet published a fallback middleware which executes a request to a different location in case of failures. You can find a simple version of such a fallback middleware in our samples repository along with other examples of custom middlewares increasing resilience.

If you have implemented a middleware which could be useful for other people as well feel free to contribute it to the SAP Cloud SDK. We are open source and contributions, feedback and feature requests are always welcome.
5 Comments