Dynamically inject repository in generic services with nestjs - dependency-injection

I am building an authorization service for my nestjs app.
For every protected resource on my app (Media, Game, ...), I have a *RoleEntity associated (MediaRoleEntity, GameRoleEntity, ...) that defines what a user can do with a specific resource. Each one of this *RoleEntity implements RoleEntityInterface:
export interface RoleEntityInterface<T extends ResourceEntity> {
resource: T;
user: UserEntity;
role: string;
}
Each protected entity (MediaEntity, GameEntity, ...) extends ResourceEntity.
Now I want to build a generic provider RoleService, responsible for database interaction:
#Injectable()
export class RoleService<T extends ResourceEntity> {
readonly roleRepository: Repository<RoleEntityInterface<T>>;
async read(roleDto: Partial<RoleDto<T>>): Promise<RoleEntityInterface<T>> {
return this.roleRepository.findOne({
where: { ...roleDto },
});
}
async create(roleDto: RoleDto<T> | RoleDto<T>[]): Promise<void> {
await this.roleRepository.insert(roleDto);
}
}
And I want inject this service in guards, interceptors...
Problem, I don't know how to do that, more precisely:
How can I dynamically inject the roleRepository ? (I imagine some kind of factory has to be involved.)
REAL USE CASE
I want to be able to protect resources with a guard:
#Injectable()
export class RoleGuard<T extends ResourceEntity> implements CanActivate {
constructor(
private authService: AuthService,
private roleService: RoleService<T>,
private readonly reflector: Reflector,
) {}
...
}
Now in a controller, when I use
#Role('Admin')
#UseGuards(RoleGuard<MediaEntity>)
Get()
...
It would be perfect if the whole thing magically works :), ie correct roleService with correct roleRepository are properly injected.
I am completely new to nestjs and typescript (and never played with angular neither) so maybe the whole approach is wrong..

Related

Access to signalr hub from other part of my application

I am writing a blazor server web app.
I have wrote this very basic signalr hub:
using Microsoft.AspNetCore.SignalR;
public class MyHub: Hub
{
public override Task OnConnectedAsync()
{
...
return base.OnConnectedAsync();
}
public Task my_function()
{
...
}
....
}
It works fine at this step.
Now, i want to access to this hub from another part of my blazor application:
public class AnotherClass
{
private readonly IHubContext<MyHub> _hub;
public AnotherClass(IHubContext<MyHub> hub)
{
_hub = hub;
}
...
public void another_function()
{
_hub.Clients.All.SendAsync(...); // <- This line works fine
(_hub as MyHub).my_function(); // <- Does not work
}
}
As you can see, i am working with dependency injection to access to my hub object.
I have read a lot of documentation, including Microsoft's official documentation.
Everybody says we have to work with IHubContext type in this kind of dependency injection.
So _hub object does not know my_function method...
I have tried a lot of things:
Cast _hub to MyHub
Declare a IMyHub interface and work with IMyHub in dependency injection
etc.
Nothing works...
My question is: How can i call my_function from AnotherClass object ?
Thanks a lot
Here's how I did it.
Create an interface for your hub:
public interface IMyHub
{
Task MyFunction(int parameter);
}
Then your hub implements the interface like so:
public class MyHub : Hub<IMyHub>
{
public async Task MyFunction(int parameter)
{
// do stuff
}
}
Then inject the hub into other classes like so:
private readonly IHubContext<MyHub, IMyHub> _myHub;
And you can invoke your function via:
_myhub.Clients.All.MyFunction(someInt);
This is based on the Strongly Typed Hubs and Send Messages From Outside a Hub documentation.
Functions you add to your IMyHUb interface don't necessarily have to have implementation code in the MyHub class. For instance, with
public interface IMyHub
{
Task MyFunction(int parameter);
Task MySecondFunction();
}
in your other class you can invoke
_myhub.Clients.All.MySecondFunction();
without any new code in the MyHub class.
How can i call my_function from AnotherClass object ?
You can't. The Hub is only called in response to a client invocation. There is no way to call a hub method manually.
If there is logic you want to run both inside the hub and outside the hub, you will need to refactor your code so it is some shared class that the Hub and AnotherClass can access.

How to build an HttpInterceptor Service in Angular Dart?

I am trying to port the code of an Angular 2 app to Angular Dart, so that I can reuse the business logic in Flutter.
My app makes use of HttpInterceptors for Error handling and server authorization.
In typescript I would inject a simple service :
#Injectable({
providedIn: 'root'
})
export class InterceptorService implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return;
}
constructor(private auth: AuthService) { }
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {}
}
But the Dart API does not seem to have an HttpInterceptor class. Does one have to extend the HttpClient class in order to do so ?
I have looked at this S.O question but it dates back from 5 years ago, the way to do it has probably changed quite a bit in the meantime.
Turns out there are a couple ways you can achieve this in Angular Dart / Flutter.
Some third party libraries built on top of dart http provide convenience methods for Http interception (DIO, Http Interceptor). However, both handle interception at the client level.
I decided to go with the Dart Http library, using the recommended method :
class UserAgentClient extends http.BaseClient {
final String userAgent;
final http.Client _inner;
UserAgentClient(this.userAgent, this._inner);
Future<StreamedResponse> send(BaseRequest request) {
request.headers['user-agent'] = userAgent;
return _inner.send(request);
}
}

NestJs module depending on provider, ServiceLocator?

Goal
I am working in a module similar to SpringActuator (I need /health, /info, /metrics, etc), I need the module to be reused across several apps, but the logic to determine if the app is healthy or to get the app info is defined at the application itself, I want this actuator module to be agnostic of the application. So I need the application to import the module passing a provider that exists already at the application context.
Implementation
I used the ModuleRef (with strict option) to create something like a ServiceLocator, so that any app dependency could be resolved during module initialization.
The actuator module is imported like this
imports: [ActuatorModule.forRoot({ actuatorToken: ApplicationActuator })]
The ApplicationActuator is the class knowing if the app is healthy or not. This provider could have another dependencies internally (nested)
The ActuatorModule uses a factory approach to provide the service locator(FactoryHelper)
providers: [
{
provide: 'ActuatorFactoryHelper',
useFactory: (moduleRef: ModuleRef) => new FactoryHelper(moduleRef, options.actuatorToken),
inject: [ModuleRef],
}
]
And finally my service uses the FactoryHelper like this
get actuator(): Actuator {
return this.factoryHelper.instance;
}
constructor(#Inject('ActuatorFactoryHelper') private readonly factoryHelper: FactoryHelper<Actuator>) {}
This is how the FactoryHelper looks like
export class FactoryHelper<T> implements OnModuleInit {
instance: T;
constructor(
private readonly moduleRef: ModuleRef,
private readonly token: Type<any> | string | symbol,
) {}
onModuleInit() {
this.instance = this.moduleRef.get(this.token, { strict: false
});
}
}
Question
I read in another threads that having a module depending on a provider is a bad practice, Is it a bad practice? what issues could I face? Is it a easier way of doing it?
Notes
I tried using custom providers (useFactory) but nested dependencies got not resolved (they are not visible to the actuator module).
Thanks in advance.

DI constructor with optional parameters

My constructor has optional parameters and they seem to mess with the predominant way of doing DI.
constructor(public name:string, public age?:number, private _service:Service);
Typescript understandably doesn't like that I put a non optional parameter behind an optional one, furthermore the service doesn't get injected when the optional parameter isn't set. How do I solve that? I can't put it somewhere else in the constructor since I would be expected setting the service manually.
Is there something like field injection?
#Inject() private _service:Service;
constructor(public name:string, public age?:number);
Should I replace the optional parameters with default values? Any other suggestions?
EDIT:
As discussed below, I tried to inject a service into an object that isn't created by Angular's DI. This doesn't work. Since I can't create this class (model) using DI I now pass the service manually from the class that instantiates this objects.
Just add the #Optional() decorator before the constructor parameter that should only be injected if there was a provider registered.
import { Optional } from '#angular/core';
constructor(public name:string, #Optional() public age:number)
If I understand correctly what you are trying to do, you need a service factory. See here: https://angular.io/docs/ts/latest/guide/dependency-injection.html#factory-provider
Basically,
class MyClass {
constructor(public name:string, private _service:Service, public age?:number){}
}
And then
let myFactory = (_service: Service) => {
return isAgeOk ? new MyClass(name, _service, age) : new MyClass(name, _service);
};
And then you should provide your service like this:
providers: [{ provide: MyClass, useFactory: MyFactory, deps: [Service]}]

Dropwizard and Guice: injecting Environment

I am currently building a Dropwizard + Guice + Jersey-based application where the database access is being handled by JDBI for the time being.
What I am trying to achieve is to have your typical enterprise architecture, where Resources access Service classes accessing a DAO class that in turn accesses the database. It would be nice to get all this wired up in a proper DI way, although I guess I can build my object graph in the run() method of the application if all else fails.
So, I'm running into this problem that has been mentioned here before: Getting a DBIFactory requires both the Environment and the Configuration, which somehow need to be available at the time when Guice does its injection magic and not at run()-time.
Being a Dropwizard and Guice noob, what I've managed to put together so far is that I need a Provider for my DAO objects, something to the tune of
public class UserDAOProvider implements Provider<UserDAO> {
#Inject
Environment environment;
#Inject
Configuration configuration;
#Override
public UserDAO get() {
final DBIFactory factory = new DBIFactory();
final (MyConfiguration) config = (MyConfiguration) configuration;
DBI jdbi = null;
try {
jdbi = factory.build(environment, config.getDataSourceFactory(),
"mysql");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return jdbi.onDemand(UserDAO.class);
}
}
Registering this as a singleton provider should let me then inject the UserDAO into my Services.
Now, how do we actually get the environment injected into the Provider? Currently I am stuck at Guice complaining about not finding a suitable constructor for the Environment, so it is trying to instantiate it and not grab it from Dropwizard itself.
It seems like this is doable; there is the dropwizard-guice package whose DropWizardEnvironmentModule is, I think, what I need. But I feel like I'm just missing some piece of the puzzle here for an understanding of how to put things together. I've not managed to find a complete working example so far...
I had the same issue as OP but using Hibernate rather than JDBI. My simple solution is applicable to JDBI, though - just switch DBIFactory for SessionFactory.
First add an injection provider for a singleton SessionFactory in your Guice module:
public class MyModule extends AbstractModule {
private SessionFactory sessionFactory;
#Override
protected void configure() {
}
#Provides
SessionFactory providesSessionFactory() {
if (sessionFactory == null) {
throw new ProvisionException("The Hibernate session factory has not yet been set. This is likely caused by forgetting to call setSessionFactory during Application.run()");
}
return sessionFactory;
}
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
}
You need to set the singleton SessionFactory from your application's run() method. In your case, using JDBI, this is where you would create and configure your DBIFactory before handing it over to the Guice module:
public void run(MyConfiguration configuration, Environment environment) {
myModule.setSessionFactory(hibernateBundle.getSessionFactory());
...
}
Now SessionFactory can be injected wherever it is needed. I now use implicit binding for my DAO classes by just annotating the constructor with #Inject and injecting the SessionFactory singleton. I don't explicitly create providers for DAO classes:
#Singleton
public class WidgetDAO extends AbstractDAO<App> {
#Inject
public WidgetDAO(SessionFactory factory) {
super(factory);
}
public Optional<Widget> findById(Long id) {
return Optional.fromNullable(get(id));
}
...
}
Now I can inject my DAO singleton instances into resources:
#Path("/widgets")
#Produces(MediaType.APPLICATION_JSON)
public class WidgetsResource {
private final WidgetDAO widgetDAO;
#Inject
public WidgetsResource(WidgetDAO widgetDAO) {
this.widgetDAO = widgetDAO;
}
...
}
Note that this approach follows the Guice recommendation of injecting direct dependencies only. Don't try to inject Envrionment and Configuration just so that you can create a DBI factory - inject the prebuilt DBI factory itself.
This is how I use Guice with Dropwizard. Inside your run() method add the line
Guice.createInjector(new ConsoleModule());
You cannot inject Environ
Create the class ConsoleModule
public class ConsoleModule extends AbstractModule {
//configuration and env variable declaration
public ConsoleModule(ConsoleConfiguration consoleConfig, Environment env)
{
this.consoleConfig = consoleConfig;
this.env= env;
}
protected void configure()
{
//You should not inject Configuration and Environment in your provider since you are mixing
//dropwizard framework stuff with Guice.Neverthless you will have to bind them in the below order
bind(Configuration.class).toInstance(consoleConfig.class);
bind(Environment.class).toInstance(env.class);
bind(UserDAO.class).toProvider(UserDAOProvider.class).in(Singleton.class);
}
}
We have the same configuration (dw-jdbi-guice) and also an abstract 'base' Application class which complicates things even more.
Since a lot of things happen during run method, and many things depend on the configuration objects we ended up creating the injector in the run method. But since we need objects from bootsrap also (e.g. ObjectMapper), we ended up having a List<Module> field in the Application class. Not the prettiest solution but can handle variety of scenarios.

Resources