Interceptor
TLDR An "interceptor" is a pattern which enables different operations to act in sequence on the same object.
Problem/context
There are senarios when we need to run multiple process in series on an object and either make a decision or transform the object. In these senarios, the processes may also need to be stateful.
An example of this senario is when handling requests in a webserver: we may want to perform authentication, request-throttling and authorization in that order for all requests.
Concept
The core idea is to have a sequence of processes. Each process is defined in a class, allowing state to be kept per-process. There is also a class which orchestrates the processes and handles errors/early returns from the sequence.
Key characteristics
- There is a common interceptor interface which new interceptor classes must implement.
- There is an orchestrator class which contains the sequence of interceptors and which invokes them in sequence.
- There are a common set of responses that interceptors can return to allow the sequence to continue or which trigger an early return from the orchetrator.
Examples
An interceptor chain which processes a web request.
// Note: several class definitions have been left out for brevity
/**
* Usage
*/
// 1. define interceptors
const interceptors = [
new LoggingInterceptor(),
new GlobalThrottlingInterceptor({
throttlingPeriodInMillis: 1000
throttlingRequestLimit: 5
})
]
// 2. initialize the orchestrator
const orchestrator = new InterceptorOrchestrator({interceptors})
// 3. proccess requests
orchestrator.run(new Request())
/**
* Definitions
*/
class InterceptorOrchestrator {
private interceptors: Interceptor[];
constructor({ interceptors }: { interceptors: Interceptor[] }) {
this.interceptors = interceptors
}
run(request: Request): AppResponse {
for(let interceptor in interceptors) {
try {
const interceptorResponse = interceptor.run(request);
// early return
if(!interceptorResponse.success) {
return interceptorResponse;
}
} catch(e) {
Logger.error(`Internal error when processing request. | Request: ${request} | Error: ${e}`)
return new InternalErrorResponse();
}
}
}
}
interface Interceptor {
run: (request: Request) => AppResponse
}
// Debug-logs the request
class LoggingInterceptor {
run(request: Request) {
Logger.debug(request)
return new SuccessResponse();
}
}
// Keeps track of all requests in a time period
// and triggers early return if the request count is too high.
class GlobalThrottlingInterceptor {
const requestCount = 0;
const throttlingRequestLimit;
constructor({throttlingRequestLimit, throttlingPeriodInMillis}: AppConfig) {
this.throttlingRequestLimit = throttlingRequestLimit
setTimeout(() => {
this.requestCount = 0;
}, throttlingPeriodInMillis)
}
run(request: Request) {
this.requestCount++;
if(requestCount > throttlingRequestLimit) {
return new ThrottledResponse();
}
return new SuccessResponse();
}
}