CircuitBreaker usage
The eridu-tech/circuit-breaker component provides a way for managing circuit-breaker independent of underlying platform or storage.
Initial configuration
To begin using the CircuitBreakerFactory class, you'll need to create and configure an instance:
import { TimeSpan } from "eridu-tech/time-span";
import { MemoryCircuitBreakerStorageAdapter } from "eridu-tech/circuit-breaker/memory-circuit-breaker-storage-adapter";
import { DatabaseCircuitBreakerAdapter } from "eridu-tech/circuit-breaker/database-circuit-breaker-adapter";
import { CircuitBreakerFactory } from "eridu-tech/circuit-breaker";
const circuitBreakerFactory = new CircuitBreakerFactory({
// You can provide default settings
// You can choose the adapter to use
adapter: new DatabaseCircuitBreakerAdapter({
adapter: new MemoryCircuitBreakerStorageAdapter(),
}),
});
Here is a complete list of settings for the CircuitBreakerFactory class.
CircuitBreaker basics
Creating a circuit-breaker
const circuitBreaker = circuitBreakerFactory.create("resource");
Using the circuit-breaker
// The function will only be called when the circuit-breaker is in closed state or half open state.
await circuitBreaker.runOrFail(async () => {
// Call the external service
});
Note the method throws an error when the circuit-breaker is in open state or isolated state.
You can provide synchronous or asynchronous Invocable<[], TValue | Promise<TValue>> as values for the runOrFail method.
Applying circuit-breaker on certiain errors
class ErrorA extends Error {}
const circuitBreaker = circuitBreakerFactory.create("resource", {
errorPolicy: ErrorA,
});
await circuitBreaker.runOrFail(async () => {
// Call the external service
});
Setting circuit-breaker triggers
By default the the circuit-breaker will treat errors and slow calls as failures. You can explicitly set ths option.
The CIRCUIT_BREAKER_TRIGGER.BOTH will treat error and slow calls as failures.
import { CIRCUIT_BREAKER_TRIGGER } from "eridu-tech/circuit-breaker/contracts";
const circuitBreaker = circuitBreakerFactory.create("resource", {
trigger: CIRCUIT_BREAKER_TRIGGER.BOTH,
});
await circuitBreaker.runOrFail(async () => {
// Call the external service
});
The CIRCUIT_BREAKER_TRIGGER.ONLY_ERROR will treat only errors as failures.
import { CIRCUIT_BREAKER_TRIGGER } from "eridu-tech/circuit-breaker/contracts";
const circuitBreaker = circuitBreakerFactory.create("resource", {
trigger: CIRCUIT_BREAKER_TRIGGER.ONLY_ERROR,
});
await circuitBreaker.runOrFail(async () => {
// Call the external service
});
The CIRCUIT_BREAKER_TRIGGER.ONLY_SLOW_CALL will treat slow calls as failures.
import { CIRCUIT_BREAKER_TRIGGER } from "eridu-tech/circuit-breaker/contracts";
const circuitBreaker = circuitBreakerFactory.create("resource", {
trigger: CIRCUIT_BREAKER_TRIGGER.ONLY_SLOW_CALL,
});
await circuitBreaker.runOrFail(async () => {
// Call the external service
});
Setting the slow call threshold
You can set custom slow call threshold that will be used when treating slow calls as failures.
import { TimeSpan } from "eridu-tech/time-span";
const circuitBreaker = circuitBreakerFactory.create("resource", {
trigger: TimeSpan.fromSeconds(1),
});
await circuitBreaker.runOrFail(async () => {
// Call the external service
});
Reseting the circuit-breaker
You can reset circuit-breaker state to the closed state manually.
await circuitBreaker.reset();
Isolating the circuit-breaker
You can manually hold circuit-breaker in open state until reseted.
await circuitBreaker.isolate();
Checking circuit-breaker state
You can get the circuit-breaker state by using the getState method, it returns CircuitBreakerState.
import { CIRCUIT_BREAKER_STATE } from "eridu-tech/circuit-breaker/contracts";
const state = await circuitBreaker.getState();
if (state === CIRCUIT_BREAKER_STATE.CLOSED) {
console.log("The service is up and running without problems");
}
if (state === CIRCUIT_BREAKER_STATE.OPEN) {
console.log("The service is down or degraded and you need to wait");
}
if (state === CIRCUIT_BREAKER_STATE.HALF_OPEN) {
console.log(
"Proping to check if the server is up and running or down / degraded",
);
}
if (state === CIRCUIT_BREAKER_STATE.ISOLATED) {
console.log("The service is held in open state manually until reseted");
}
CircuitBreaker instance variables
The CircuitBreaker class exposes instance variables such as:
const circuitBreaker = circuitBreakerFactory.create("resource");
// Will return the key of the circuit-breaker which is "resource"
console.log(circuitBreaker);
Patterns
Serialization and deserialization of circuit-breakers
circuit-breakers can be serialized, allowing them to be transmitted over the network to another server and later deserialized for reuse.
This means you can, for example, acquire the circuit-breaker on the main server, transfer it to a queue worker server, and release it there.
In order to serialize or deserialize a circuit-breaker you need pass an object that implements ISerderRegister contract like the Serde class to CircuitBreakerFactory.
Manually serializing and deserializing the circuit-breaker:
import { RedisCircuitBreakerAdapter } from "eridu-tech/circuit-breaker/redis-circuit-breaker-adapter";
import { CircuitBreakerFactory } from "eridu-tech/circuit-breaker";
import { Serde } from "eridu-tech/serde";
import { SuperJsonSerdeAdapter } from "eridu-tech/serde/super-json-serde-adapter";
const serde = new Serde(new SuperJsonSerdeAdapter());
const redisClient = new Redis("YOUR_REDIS_CONNECTION");
const circuitBreakerFactory = new CircuitBreakerFactory({
// You can laso pass in an array of Serde class instances
serde,
adapter: new RedisCircuitBreakerAdapter({ database: redisClient }),
});
const circuitBreaker = circuitBreakerFactory.create("resource");
const serializedCircuitBreaker = serde.serialize(circuitBreaker);
const deserializedCircuitBreaker = serde.deserialize(circuitBreaker);
When serializing or deserializing a circuit-breaker, you must use the same Serde instances that were provided to the CircuitBreakerFactory. This is required because the CircuitBreakerFactory injects custom serialization logic for ICircuitBreaker instance into Serde instances.
Note you only need manuall serialization and deserialization when integrating with external libraries.
As long you pass the same Serde instances with all other components you dont need to serialize and deserialize the circuit-breaker manually.
import { RedisCircuitBreakerAdapter } from "eridu-tech/circuit-breaker/redis-circuit-breaker-adapter";
import type { ICircuitBreaker } from "eridu-tech/circuit-breaker/contracts";
import { CircuitBreakerFactory } from "eridu-tech/circuit-breaker";
import { RedisPubSubEventBusAdapter } from "eridu-tech/event-bus/redis-pub-sub-event-bus-adapter";
import { EventBus } from "eridu-tech/event-bus";
import { Serde } from "eridu-tech/serde";
import { SuperJsonSerdeAdapter } from "eridu-tech/serde/super-json-serde-adapter";
const serde = new Serde(new SuperJsonSerdeAdapter());
const redis = new Redis("YOUR_REDIS_CONNECTION");
type EventMap = {
"sending-circuit-breaker-over-network": {
circuitBreaker: ICircuitBreaker;
};
};
const eventBus = new EventBus<EventMap>({
adapter: new RedisPubSubEventBusAdapter({
client: redis,
serde,
}),
});
const circuitBreakerFactory = new CircuitBreakerFactory({
serde,
adapter: new RedisCircuitBreakerAdapter({ databsae: redis }),
eventBus,
});
const circuitBreaker = circuitBreakerFactory.create("resource");
// We are sending the circuitBreaker over the network to other servers.
await eventBus.dispatch("sending-circuit-breaker-over-network", {
circuitBreaker,
});
// The other servers will recieve the serialized circuitBreaker and automattically deserialize it.
await eventBus.addListener(
"sending-circuit-breaker-over-network",
({ circuitBreaker }) => {
// The circuitBreaker is deserialized and can be used
console.log("CIRCUIT_BREAKER:", circuitBreaker);
},
);
Further information
For further information refer to eridu-tech/circuit-breaker API docs.