-
-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Alternative NestJS Approach #44
Comments
In v5 there will be a validation for working with the global EM's identity map aware API. I was originally thinking of making it opt-in, mainly because I was lazy to rewrite all the tests, but if we make it configurable via env vars, I could at least disable that globally there :D
Good idea.
v5 uses ALS instead of domain API. I will definitely keep it there as it is very convenient and universal. On the other hand, this is optional, you can work without this magic if you don't like it.
Sure, we can add the proposed Will move this to the nest repository to track it there. |
somehow, i completely missed the ALS announcement in v14, looks neat but may impact performance at scale (probably not a concern for most projects) that being said, would the ALS implementation account for transactions? eg: i usually put my orchestration at the controller/resolver level, and wrap all the service calls into a transaction. it isn't clear in the case of nested services (or perhaps in some peoples case, nested transactions if supported by the db) how that implementation would work. other question w/r/t ALS, if I have a web request come in, and i run to different service calls with two different transactions like so, does it break (probably not the best example, but i'm more-so poking at if there are parallel promises using entity manager)? @Get()
endpoint(){
await Promise.all([
this.service.a(),
this.service.b();
];
} |
ALS is actually very fast, compared to the domain API. When I was perf testing this last time, there was quite a small penalty for ALS, few percent, maybe up to 10-20%, but I don't really remember now.
ALS is actually used internally to hold the transaction context in v5 - again opt in, you can still use the EM from parameter explicitly, only if you work with the global instance the ALS is checked (this works based on the
Each transaction has its own transaction context (own EM fork), including the nested ones.
Using |
ALS makes sense, your pretty much use it everywhere or not
can you expand on that? |
I mean you can't flush in parallel (for that you need forks with their own UoW instance), for reading it should be fine. |
Just tossing this here in case anyone finds it useful. import type { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import type { EntityManager } from '@mikro-orm/core';
import { MikroORM, RequestContext } from '@mikro-orm/core';
import type { AsyncLocalStorage } from 'async_hooks';
import { Observable } from 'rxjs';
interface ExposedRequestContextInterface {
createContext: (em: EntityManager) => RequestContext;
storage: AsyncLocalStorage<RequestContext>;
}
const ExposedRequestContext = RequestContext as unknown as ExposedRequestContextInterface;
/**
* Because the MikroORM RequestContext only works as a middleware for HTTP Traffic, this is a
* monkey patch to inject context at the interceptor level
*/
@Injectable()
export class MikroOrmInterceptor implements NestInterceptor {
constructor(private readonly orm: MikroORM) {}
public intercept(_context: ExecutionContext, next: CallHandler): Observable<unknown> {
const requestContext = ExposedRequestContext.createContext(this.orm.em);
return new Observable((subscriber) => {
const subscription = ExposedRequestContext.storage.run(requestContext, () =>
next.handle().subscribe(subscriber)
);
return () => {
subscription.unsubscribe();
};
});
}
} It of course has the limitation (being at the interceptor level) of not affecting guards, but for my use case I actually don't need that as all my guards are at the gateway level which issue request to other services which would do their business logic past any interceptors. A note on a PR I may look into introducing, the |
I've implemented a NestJS interceptor to flush the EM automatically upon request completion. It leverages @B4nan does that look good or may I run into problems ? @Injectable()
class DatabaseInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler<any>,
): Observable<any> {
return next.handle().pipe(
tap(() => {
return RequestContext.currentRequestContext()?.em?.flush();
}),
);
}
} |
Not entirely sure how this works, I remember |
My RxJS is very rusty. This update seems to be correct. @Injectable()
class DatabaseInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler<any>,
): Observable<any> {
return next.handle().pipe(
mergeMap(async (response) => {
await RequestContext.currentRequestContext()?.em?.flush();
return response;
}),
);
}
} |
In moving to a persist/flush model system like Mikro, I find it important to prevent developers on my team from making mistakes like using some global entity manager with a persisted identity map across requests. At the moment, it is very easy to inject Mikro into any service, controller, etc without realizing the repercussions. At a high level, these are my requirements:
orm.getGlobalEntityManager()
EntityManager
to your downstream services via function argumentsEntityManagerInterceptor.ts
RequestEntityManager.ts
The text was updated successfully, but these errors were encountered: