-
-
Notifications
You must be signed in to change notification settings - Fork 34
Open
Description
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:
- No one should be able access a singleton instance of EntityManager except for the Mikro module itself.
- If there is ever a need to access the application-level singleton, it should be done through some obvious abstraction like
orm.getGlobalEntityManager()
- No magic should be involved with request scopes such as node.js domains (deprecated) or pulling things from global statics. Given that a service does not know if it needs to be ran in a transaction or not, guidance/best practice should be to always pass an instance of
EntityManager
to your downstream services via function arguments - The Mikro NestJS module should provide interceptors and decorators for making request scoped work easier (i'm using this for both REST and GraphQL endpoints), example:
EntityManagerInterceptor.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql';
import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
export default class EntityManagerInterceptor implements NestInterceptor {
constructor(private em: EntityManager) {}
async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<any>> {
context['em'] = this.em.fork();
return next.handle().pipe(
mergeMap(async (output) => {
await context['em'].flush();
return output;
})
);
}
}
RequestEntityManager.ts
export default createParamDecorator((data: unknown, context: ExecutionContext) => {
if (!context['em']) {
throw new Error('Could not find a request-scoped EntityManager');
}
return context['em'];
});
// graphql
@Mutation()
myMutation(@RequestEntityManager() em: EntityManager){}
// rest
@Get()
async myEndpoint(@RequestEntityManager() em: EntityManager){}
micalevisk, bartvanremortele, AdriVanHoudt and cyrildewit
Metadata
Metadata
Assignees
Labels
No labels