Concepts: Providers (Core)
This section discusses providers, how they work, and what IS dependency injection within the Sodacore framework.
Introduction
Providers are a fundamental concept in Sodacore that faciliate the ability to inject dependencies within your application. ANY class can be a provider, and all that means is that the framework will manage instantiating the class for you, and will also manage any injected dependencies that the class may have.
Dependency Injection
The way dependency injection works in Sodacore is slightly different than most, I went for a simple-to-use global container, called the Registry
which is a core package @sodacore/registry
and is used throughout the entire framework for handling interactions between various components. We also support dependency injection using strings
, so you can inject potentially a reactive variable if you wanted or access a plugin's config values.
Most classes that the framework works with are registered within the Registry
and therefore can be injected, need access to the HTTP service? Just inject it.
export class YourClass {
@Inject() private httpService!: HttpService;
}
Note: YES, injection works in ANY class, BUT, be aware that the
@Inject()
decorator uses a lazy getter, so if you try to access the injected property in the constructor, and the framework has not initialised that property yet, then it will be undefined! - Use Hooks if you want something to happen after construction.
Another note: Also very important, as noted below, but the
Registry
is a key-value store, so when we store a class into the registry we shall use the class name as the key, so when it comes to provider names - try not to clash with what's already in there. Don't worry too much though, the Registry will throw an error if you try to register a class with the same name, or use a string key that already exists, but just so you're aware.
What should I put in a provider?
Well that's very much up to you, in general; but I have an example from the Sodacore project itself. The @sodacore/ws
package provides a WsConnectionsProvider
which you can utilise in controllers, services, pretty much anywhere, to manage your WebSocket connections, for example, you may want to broadcast a message to all connected clients, you can do that easily by injecting the WsConnectionsProvider
and calling the broadcast
method. The internal WebSocket service will actually inject the same provider (remember it's an instance managed by the framework) and will use it to track and manage connections.
Another example within the @sodacore/discord
package is the OAuthProvider
which, injecting the Discord config, creates a set of simple to use methods for SSO/OAuth2 flows with Discord so you can easily support logging in via Discord within your platform.
Essentially what I am trying to say is that providers are a great way to encapsulate reusable logic that can be used across multiple parts of your application, and usually a provider only makes sense when you want a single instance of a class to be used across your application.
Registering providers
Using providers is dumb simple;
import { Provide } from '@sodacore/di';
@Provide()
export class MyProvider {
// ...
}
And that's it! The class will be picked up automatically by the framework's autoloader, registered within the Registry
and upon the init
lifecycle hook, the class will be instantiated and ready for use.
Using providers
Again, dumb simple;
import { Controller, Get } from '@sodacore/http';
import { Inject } from '@sodacore/di';
import { MyProvider } from '../providers/my-provider';
@Controller('/super/secret')
export class SuperSecretController {
@Inject() private myProvider!: MyProvider;
@Get('/')
public haveSecret() {
return this.myProvider.hasSecret();
}
}
Note: The registry is a key-value store, so when we store a class into the registry we shall use the class name as the key, so in the above example, if you were to use
import type { MyProvider } from '../providers/my-provider';
TypeScript will strip the import out, and therefore the class will not be there, and therefore after compilation, the registry will essentially be given@Inject() private myProvider!: undefined;
and therefore you will get an error when trying to access the property. To get around this don't useimport type
when importing classes that you want to use with dependency injection.