InversifyJS icon indicating copy to clipboard operation
InversifyJS copied to clipboard

Help needed with request scope

Open zveljkovic opened this issue 7 years ago • 2 comments

Expected Behavior

I expect easy access to request scope bound variables or at least some help in this issue.

Current Behavior

Little hard to get it working in some frameworks. Express based frameworks have a problem with code binding types in middlewares as with few clever timeouts we can create race conditions. Example below...

Possible Solution

I have made a simple demo using Hapi with child containers instead of requestScope. https://github.com/zveljkovic/hapi-inversify-request-scope

Steps to Reproduce (for bugs)

I didn't quite understand the solution for accessing the request based data as explained in https://github.com/inversify/InversifyJS/issues/381#issuecomment-342145761 so if you can please comment on the code in https://github.com/zveljkovic/hapi-inversify-request-scope on best practices for this kind of requirement

Context

When building with express based libraries (typescript-rest) I need easy way to extract some request information and use it inside controllers, services, repositories. Examples are requestTraceId, userContext, or tenantModel in multitenant app. Tenant is best example as it is figured out in middlewares before controllers and currently I assign it to request object. In controller i pass it (tenant) to service, then service calls repository function and passes it again. It would be best to have a DI framework do all of the instantiation and passing it around.

Failing example - race condition

InitializeRequestDataMiddleware.ts

export async function InitializeRequestDataMiddleware(req: any, res: Response, next: NextFunction) {
	req.traceId = Utils.generateTrackingId('TR');

	console.log('Rebinding RequestTraceId to ', req.traceId);
	if (myContainer.isBound(TYPES.RequestTraceId)) {
		myContainer.rebind(TYPES.RequestTraceId).toDynamicValue((context) => req.traceId).inRequestScope();
	} else {
		myContainer.bind<string>(TYPES.RequestTraceId).toDynamicValue((context) => req.traceId).inRequestScope();
	}
	setTimeout(() => { next(); }, 5000);  // NOTE #1
}

HomeController.ts

@Path('/')
@injectable()
export class HomeController {  // NOTE #2
	public requestTraceId: string;
	constructor(@inject(TYPES.RequestTraceId) requestTraceId: string,) {
		this.requestTraceId = requestTraceId;
	}

	@Get
	public async home(): Promise<{status: string, quote: string, test: any, zest: string}> {
		console.log('Before timeout', this.requestTraceId);
		return new Promise<{test: any}>((resolve, reject) => {
			setTimeout(() => {
				console.log('After timeout', this.requestTraceId);  // NOTE #3
				resolve({
					test: this.requestTraceId,
				});
			}, 2000);
		});
	}
}

Note 1: I have put this timeout to represent some delays in some other middlewares before controller action is activated. This also helps me to launch few requests in parallel. Note 2: Controller gets resolved after middlewares next function is called Note 3; This log will show duplicated requestTraceId if there are two paralel requests. First req will assign value to global container and stop at first timeout (from Note #1). 2nd req will override the value and stop at same timeout. Then both controllers are afterwards initiated with the value from the 2nd request.

zveljkovic avatar Sep 06 '18 00:09 zveljkovic

I"m trying to get request scope as well but without success. Thought it was just .inRequestScope() like doc says

iuribrindeiro avatar Sep 06 '18 13:09 iuribrindeiro

I have the same problem. The example from the documentation doesn't seem to work. Any updates?

sudograce avatar Sep 27 '21 18:09 sudograce