Skip to content

Commit 44e6174

Browse files
committed
fix: resolve circuit breaker bug
1 parent 74c581f commit 44e6174

File tree

5 files changed

+111
-23
lines changed

5 files changed

+111
-23
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { describe, test, expect } from 'vitest';
2+
import { CircuitBreakerBuilder } from './circuit-breaker.builder';
3+
4+
describe('CircuitBreakerBuilder', () => {
5+
test('should not apply circuit breaker if option is not given', async () => {
6+
// given
7+
let executeCount = 0;
8+
const executor = async (): Promise<Response> => {
9+
executeCount++;
10+
throw new Error('error');
11+
};
12+
const request = new CircuitBreakerBuilder().build(executor);
13+
14+
// when
15+
for (let i = 0; i < 10; i++) {
16+
await request().catch(() => 0);
17+
await new Promise((resolve) => setTimeout(resolve, 5));
18+
}
19+
20+
// then
21+
expect(executeCount).toBe(10);
22+
});
23+
24+
test('should apply circuit breaker if option is given', async () => {
25+
// given
26+
let executeCount = 0;
27+
const executor = async (): Promise<Response> => {
28+
executeCount++;
29+
throw new Error('error');
30+
};
31+
const request = new CircuitBreakerBuilder({
32+
errorThresholdPercentage: 50,
33+
resetTimeout: 1000,
34+
}).build(executor);
35+
36+
// when
37+
for (let i = 0; i < 10; i++) {
38+
await request().catch(() => 0);
39+
await new Promise((resolve) => setTimeout(resolve, 5));
40+
}
41+
42+
// then
43+
expect(executeCount).toBe(1);
44+
});
45+
});
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import CircuitBreaker from 'opossum';
22

33
export class CircuitBreakerBuilder {
4-
constructor(private readonly options?: CircuitBreaker.Options) {}
4+
constructor(readonly options?: CircuitBreaker.Options) {}
55

6-
async build(executor: () => Promise<Response>): Promise<Response> {
6+
build(
7+
executor: (...args: never[]) => Promise<any>,
8+
): (...args: never[]) => Promise<any> {
79
if (this.options == null) {
8-
return await executor();
10+
return executor;
911
}
1012

11-
return await new CircuitBreaker(executor, this.options).fire();
13+
const circuitBreaker = new CircuitBreaker(executor, this.options);
14+
15+
return async (...args) => {
16+
return await circuitBreaker.fire(...args);
17+
};
1218
}
1319
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { describe, test, expect } from 'vitest';
2+
import { CircuitBreaker } from './circuit-breaker.decorator';
3+
import { CIRCUIT_BREAKER_METADATA } from './constants';
4+
import { type CircuitBreakerBuilder } from '../builders/circuit-breaker.builder';
5+
6+
describe('CircuitBreaker', () => {
7+
test('should set circuit breaker metadata', () => {
8+
// given
9+
class TestService {
10+
@CircuitBreaker({ resetTimeout: 1000 })
11+
async request(): Promise<string> {
12+
return '';
13+
}
14+
}
15+
16+
// when
17+
const result: CircuitBreakerBuilder = Reflect.getMetadata(
18+
CIRCUIT_BREAKER_METADATA,
19+
TestService.prototype,
20+
'request',
21+
);
22+
23+
// then
24+
expect(result.options).toEqual({ resetTimeout: 1000 });
25+
});
26+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { type Options } from 'opossum';
2+
import { CIRCUIT_BREAKER_METADATA } from './constants';
3+
import { CircuitBreakerBuilder } from '../builders/circuit-breaker.builder';
4+
import { type AsyncFunction } from '../types';
5+
6+
export const CircuitBreaker =
7+
(options: Options) =>
8+
<P extends string>(target: Record<P, AsyncFunction>, propertyKey: P) => {
9+
Reflect.defineMetadata(
10+
CIRCUIT_BREAKER_METADATA,
11+
new CircuitBreakerBuilder(options),
12+
target,
13+
propertyKey,
14+
);
15+
};

lib/supports/node-fetch.injector.ts

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -55,27 +55,23 @@ export class NodeFetchInjector implements OnModuleInit {
5555

5656
httpRequestBuilder.setBaseUrl(baseUrl);
5757

58-
wrapper.instance[methodName] = async (...args: never[]) =>
59-
await circuitBreaker
60-
.build(
61-
async () =>
62-
await this.httpClient.request(
63-
httpRequestBuilder.build(args),
64-
httpRequestBuilder.options,
65-
),
66-
)
67-
.then(async (response) => {
68-
if (responseBodyBuilder != null) {
69-
const res = await response.json();
58+
wrapper.instance[methodName] = circuitBreaker.build(
59+
async (...args: never[]) =>
60+
await this.httpClient
61+
.request(httpRequestBuilder.build(args), httpRequestBuilder.options)
62+
.then(async (response) => {
63+
if (responseBodyBuilder != null) {
64+
const res = await response.json();
7065

71-
return responseBodyBuilder.build(
72-
res,
73-
this.httpInterfaceConfig?.transformOption,
74-
);
75-
}
66+
return responseBodyBuilder.build(
67+
res,
68+
this.httpInterfaceConfig?.transformOption,
69+
);
70+
}
7671

77-
return await response.text();
78-
});
72+
return await response.text();
73+
}),
74+
);
7975
});
8076
}
8177

0 commit comments

Comments
 (0)