forked from SAP-samples/cloud-sdk-js
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: d068544 <[email protected]>
- Loading branch information
1 parent
8940540
commit da1cca8
Showing
9 changed files
with
189 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"$schema": "http://json.schemastore.org/prettierrc", | ||
"singleQuote": true, | ||
"filepath": "*.ts", | ||
"trailingComma": "none", | ||
"arrowParens": "avoid", | ||
"endOfLine": "lf" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# SAP Cloud SDK for JS Resilience examples | ||
|
||
## Description | ||
|
||
This folder contains a few simples examples about resilience and the SDK. | ||
The examples are using well established libraries and are tested for you. | ||
They are meant as a blueprint to illustrate how resilience can be achieved, but there are many other ways out there which could be more fitting in your use case. | ||
|
||
- In `src/circuit-breaker.ts` the opossum circuit breaker is wrapped around a request. | ||
- In `src/retry.ts` the async-retry wrapper is used to retry failed requests. | ||
|
||
There is always a `*.spec.ts` file next to the examples which shows the actual execution and that the libraries show the expected behaviour. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,27 @@ | ||
import { BusinessPartner, businessPartnerService } from '@sap/cloud-sdk-vdm-business-partner-service'; | ||
import { BusinessPartner } from '@sap/cloud-sdk-vdm-business-partner-service'; | ||
import CircuitBreaker from 'opossum'; | ||
import { createLogger } from '@sap-cloud-sdk/util'; | ||
import { destinationName } from './test-util'; | ||
import { getAllBusinessPartner } from './test-util'; | ||
|
||
const logger = createLogger('circuit-breaker'); | ||
|
||
function getAllBusinessPartner(top: number): Promise<BusinessPartner[]>{ | ||
return businessPartnerService().businessPartnerApi.requestBuilder().getAll().top(top).execute({ destinationName }); | ||
} | ||
|
||
const breakerOptions = { | ||
timeout: 3000, | ||
errorThresholdPercentage: 80, | ||
resetTimeout: 30000 | ||
timeout: 3000, | ||
errorThresholdPercentage: 80, | ||
resetTimeout: 30000 | ||
}; | ||
|
||
// Create a new circuit breaker around the asyn method | ||
const breaker = new CircuitBreaker(getAllBusinessPartner,breakerOptions); | ||
// Create a new circuit breaker around the async method | ||
const breaker = new CircuitBreaker(getAllBusinessPartner, breakerOptions); | ||
|
||
// In the fallback you can put some logic to be executed if one system is not available. | ||
breaker.fallback(()=>logger.warn('Request failed to many times. Circuit breaker is open.')); | ||
// In the fallback you can put some logic to be executed if the breaker is open. | ||
breaker.fallback(() => | ||
logger.warn('Request failed to many times. Circuit breaker is open.') | ||
); | ||
|
||
// Execute the asyn method passing arguments if needed | ||
export function getAllBusinessPartnerWithCircuitBreaker(top: number): Promise<BusinessPartner[]>{ | ||
return breaker.fire(top); | ||
// Execute the async method passing arguments if needed | ||
export async function getAllBusinessPartnerWithCircuitBreaker( | ||
top: number | ||
): Promise<BusinessPartner[]> { | ||
return breaker.fire(top); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,58 @@ | ||
import { unmockAllTestDestinations } from '@sap-cloud-sdk/test-util'; | ||
import nock from 'nock'; | ||
import { getAllBusinessPartners, getAllBusinessPartnerWithRetry } from './retry'; | ||
import { destinationUrl, mockDestination, mockGetAllRequest } from './test-util'; | ||
|
||
describe('retry',()=>{ | ||
beforeAll(()=>{ | ||
mockDestination(); | ||
}); | ||
|
||
beforeEach(()=>{ | ||
nock(destinationUrl) | ||
.get(/.*/) | ||
.times(2) | ||
.reply(500); | ||
|
||
mockGetAllRequest(1); | ||
}); | ||
|
||
afterEach(()=>{ | ||
nock.cleanAll(); | ||
}); | ||
|
||
afterAll(()=>{ | ||
unmockAllTestDestinations(); | ||
}); | ||
|
||
it('fails without the retry.',async ()=>{ | ||
await expect(getAllBusinessPartners(1)()).rejects.toThrowError(`get request to ${destinationUrl}/sap/opu/odata/sap/API_BUSINESS_PARTNER failed!`); | ||
}); | ||
|
||
it('resolves with retry',async ()=>{ | ||
// The mock will return after two failed attempts | ||
const actual = await getAllBusinessPartnerWithRetry(1); | ||
expect(actual.length).toBe(1); | ||
}); | ||
import { createLogger } from '@sap-cloud-sdk/util'; | ||
import { getAllBusinessPartnerWithRetry } from './retry'; | ||
import { | ||
destinationUrl, | ||
getAllBusinessPartner, | ||
mockDestination, | ||
mockGetAllRequest | ||
} from './test-util'; | ||
|
||
describe('retry', () => { | ||
beforeAll(() => { | ||
mockDestination(); | ||
}); | ||
|
||
beforeEach(() => { | ||
nock(destinationUrl).get(/.*/).times(2).reply(500); | ||
|
||
mockGetAllRequest(1); | ||
}); | ||
|
||
afterEach(() => { | ||
nock.cleanAll(); | ||
}); | ||
|
||
afterAll(() => { | ||
unmockAllTestDestinations(); | ||
}); | ||
|
||
it('rejects without the retry.', async () => { | ||
await expect(getAllBusinessPartner(1)).rejects.toThrowError( | ||
`get request to ${destinationUrl}/sap/opu/odata/sap/API_BUSINESS_PARTNER failed!` | ||
); | ||
}); | ||
|
||
it('resolves with retry', async () => { | ||
// The mock will return after two failed attempts | ||
const actual = await getAllBusinessPartnerWithRetry(1); | ||
expect(actual.length).toBe(1); | ||
}); | ||
|
||
it('does not retry on 403 error', async () => { | ||
nock.cleanAll(); | ||
nock(destinationUrl).get(/.*/).times(1).reply(401); | ||
|
||
const logger = createLogger('retry'); | ||
const spy = jest.spyOn(logger, 'warn'); | ||
await expect(getAllBusinessPartnerWithRetry(1)).rejects.toThrowError( | ||
`get request to ${destinationUrl}/sap/opu/odata/sap/API_BUSINESS_PARTNER failed!` | ||
); | ||
|
||
expect(spy).toHaveBeenCalledTimes(1); | ||
expect(spy).toHaveBeenCalledWith( | ||
'Request failed with status 401 - no retry necessary.' | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,31 @@ | ||
import { BusinessPartner, businessPartnerService } from '@sap/cloud-sdk-vdm-business-partner-service'; | ||
import { BusinessPartner } from '@sap/cloud-sdk-vdm-business-partner-service'; | ||
import retry from 'async-retry'; | ||
import { destinationName } from './test-util'; | ||
import { createLogger } from '@sap-cloud-sdk/util'; | ||
import { getAllBusinessPartner } from './test-util'; | ||
|
||
export function getAllBusinessPartners(top: number): () => Promise<BusinessPartner[]>{ | ||
return ()=> businessPartnerService().businessPartnerApi.requestBuilder().getAll().top(top).execute({ destinationName }); | ||
} | ||
const logger = createLogger('retry'); | ||
|
||
const options = { | ||
retries : 2, | ||
minTimeout: 500 | ||
retries: 2, | ||
minTimeout: 500 | ||
}; | ||
|
||
export function getAllBusinessPartnerWithRetry(top: number): Promise<BusinessPartner[]>{ | ||
return retry(getAllBusinessPartners(top),options); | ||
// Create a wrapper passing arguments | ||
export async function getAllBusinessPartnerWithRetry( | ||
top: number | ||
): Promise<BusinessPartner[]> { | ||
// Wrap the retry block around the async function. | ||
return retry(async bail => { | ||
try { | ||
const bps = await getAllBusinessPartner(top); | ||
return bps; | ||
} catch (error) { | ||
// Use the bail() method to stop the retries for some cases | ||
if (error.cause.response.status === 401) { | ||
logger.warn('Request failed with status 401 - no retry necessary.'); | ||
bail(error); | ||
} | ||
throw error; | ||
} | ||
}, options); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,33 @@ | ||
import { setTestDestination } from '@sap-cloud-sdk/test-util'; | ||
import nock from 'nock'; | ||
import { | ||
BusinessPartner, | ||
businessPartnerService | ||
} from '@sap/cloud-sdk-vdm-business-partner-service'; | ||
|
||
export const destinationName = 'MyDestination'; | ||
export const destinationUrl ='http://some.url.com'; | ||
export const destinationUrl = 'http://some.url.com'; | ||
|
||
export function mockDestination(): void{ | ||
setTestDestination({ name: destinationName,url:destinationUrl }); | ||
export function mockDestination(): void { | ||
setTestDestination({ name: destinationName, url: destinationUrl }); | ||
} | ||
|
||
export const sampleResponse = { | ||
d: { | ||
results: [{ BusinessPartner:1234 }] | ||
} | ||
d: { | ||
results: [{ BusinessPartner: 1234 }] | ||
} | ||
}; | ||
|
||
export function mockGetAllRequest(times: number): void{ | ||
nock(destinationUrl) | ||
.get(/.*/) | ||
.times(times) | ||
.reply(200,sampleResponse); | ||
export function mockGetAllRequest(times: number): void { | ||
nock(destinationUrl).get(/.*/).times(times).reply(200, sampleResponse); | ||
} | ||
|
||
export async function getAllBusinessPartner( | ||
top: number | ||
): Promise<BusinessPartner[]> { | ||
return businessPartnerService() | ||
.businessPartnerApi.requestBuilder() | ||
.getAll() | ||
.top(top) | ||
.execute({ destinationName }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters