From 74c581f5f388c17f7bde112f019b0bb70ff8a163 Mon Sep 17 00:00:00 2001 From: Jake Son Date: Sat, 13 May 2023 21:02:30 +0900 Subject: [PATCH] feat: add circuit breaker support --- lib/builders/circuit-breaker.builder.ts | 13 +++++++++++++ lib/decorators/constants.ts | 1 + lib/supports/node-fetch.injector.ts | 17 +++++++++++++++-- lib/types/http-interface-config.ts | 2 ++ 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 lib/builders/circuit-breaker.builder.ts diff --git a/lib/builders/circuit-breaker.builder.ts b/lib/builders/circuit-breaker.builder.ts new file mode 100644 index 0000000..fa0a105 --- /dev/null +++ b/lib/builders/circuit-breaker.builder.ts @@ -0,0 +1,13 @@ +import CircuitBreaker from 'opossum'; + +export class CircuitBreakerBuilder { + constructor(private readonly options?: CircuitBreaker.Options) {} + + async build(executor: () => Promise): Promise { + if (this.options == null) { + return await executor(); + } + + return await new CircuitBreaker(executor, this.options).fire(); + } +} diff --git a/lib/decorators/constants.ts b/lib/decorators/constants.ts index 879ad70..cea56a5 100644 --- a/lib/decorators/constants.ts +++ b/lib/decorators/constants.ts @@ -6,3 +6,4 @@ export const REQUEST_BODY_METADATA = Symbol('REQUEST_BODY_METADATA'); export const REQUEST_FORM_METADATA = Symbol('REQUEST_FORM_METADATA'); export const REQUEST_HEADER_METADATA = Symbol('REQUEST_HEADER_METADATA'); export const RESPONSE_BODY_METADATA = Symbol('RESPONSE_BODY_METADATA'); +export const CIRCUIT_BREAKER_METADATA = Symbol('CIRCUIT_BREAKER_METADATA'); diff --git a/lib/supports/node-fetch.injector.ts b/lib/supports/node-fetch.injector.ts index b4243af..26a4546 100644 --- a/lib/supports/node-fetch.injector.ts +++ b/lib/supports/node-fetch.injector.ts @@ -1,9 +1,11 @@ import { Injectable, type OnModuleInit } from '@nestjs/common'; import { DiscoveryService, MetadataScanner } from '@nestjs/core'; import { type InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; +import { CircuitBreakerBuilder } from '../builders/circuit-breaker.builder'; import { type HttpRequestBuilder } from '../builders/http-request.builder'; import { type ResponseBodyBuilder } from '../builders/response-body.builder'; import { + CIRCUIT_BREAKER_METADATA, HTTP_EXCHANGE_METADATA, HTTP_INTERFACE_METADATA, RESPONSE_BODY_METADATA, @@ -43,14 +45,25 @@ export class NodeFetchInjector implements OnModuleInit { return; } + const circuitBreaker: CircuitBreakerBuilder = + Reflect.getMetadata(CIRCUIT_BREAKER_METADATA, prototype, methodName) ?? + new CircuitBreakerBuilder( + this.httpInterfaceConfig?.circuitBreakerOption, + ); const responseBodyBuilder: ResponseBodyBuilder | undefined = Reflect.getMetadata(RESPONSE_BODY_METADATA, prototype, methodName); httpRequestBuilder.setBaseUrl(baseUrl); wrapper.instance[methodName] = async (...args: never[]) => - await this.httpClient - .request(httpRequestBuilder.build(args), httpRequestBuilder.options) + await circuitBreaker + .build( + async () => + await this.httpClient.request( + httpRequestBuilder.build(args), + httpRequestBuilder.options, + ), + ) .then(async (response) => { if (responseBodyBuilder != null) { const res = await response.json(); diff --git a/lib/types/http-interface-config.ts b/lib/types/http-interface-config.ts index b7598fd..d2e3cbf 100644 --- a/lib/types/http-interface-config.ts +++ b/lib/types/http-interface-config.ts @@ -1,8 +1,10 @@ import { type ClassTransformOptions } from 'class-transformer'; +import type CircuitBreaker from 'opossum'; import { type HttpClient } from './http-client.interface'; export interface HttpInterfaceConfig { timeout?: number; httpClient?: HttpClient; transformOption?: ClassTransformOptions; + circuitBreakerOption?: CircuitBreaker.Options; }