Service Providers
Understand the purpose and how to use the Athenna service providers.
Introduction
Service providers are the central place of all Athenna application bootstrapping. Your own application, as well as all of Athenna core services, are bootstrapped via service providers.
But what do we mean by "bootstrapped"? In general, we mean registering things, including registering service container bindings, retry strategies for your http requests, booting your FakeApi. Service providers are the central place to configure your application.
If you open the .athennarc.json
file included with Athenna, you will see a
providers
array. These are all the service provider classes that will be
loaded for your application. By default, a set of Athenna core service
providers are listed in this array. These providers bootstrap the core
Athenna components, such as the http, route, controllers and others.
In this overview, you will learn how to write your own service providers and register them with your Athenna application.
If you would like to learn more about how Athenna works internally, check out the application lifecycle documentation section.
Writing service providers
All service providers extend the ServiceProvider
class. Most service providers
contain a register
and a boot
method. Within the register
method, you should
only bind things into the service container. We will check lately why this
behavior. To create a new provider, use the following command:
node artisan make:provider AppProvider
The register
method
As mentioned previously, within the register
method, you should only bind
things into the service container. You should never attempt to register any
piece of functionality within the register
method. Otherwise, you may
accidentally use a service provided by a service provider which has
not loaded yet.
Let's take a look at a basic service provider. Within any of your service
provider methods, you always have access to the this.container
property
which provides access to the service container:
import { ServiceProvider } from '@athenna/ioc'
import { AppHelper } from '#src/helpers/AppHelper'
export default class AppProvider extends ServiceProvider {
public register() {
this.container.singleton('App/Helpers/AppHelper', AppHelper)
}
}
This service provider only defines a register
method, and uses that method to
define an implementation of AppHelper
in the service container. If you're
not yet familiar with Athenna service container, check out its documentation.
The boot
method
So, what if we need to register a dependency that needs another dependency
within our service provider? This should be done within the boot
method.
This method is called after all other service providers have been registered,
meaning you have access to all other services that have been registered by
the framework:
import { ServiceProvider } from '@athenna/ioc'
import { AppHelper } from '#src/helpers/AppHelper'
export default class AppProvider extends ServiceProvider {
public boot() {
const appService = this.container.safeUse('App/Services/AppService')
const appHelper = new AppHelper(appService)
this.container.instance('App/Helpers/AppHelper', appHelper)
}
}
The shutdown
method
The shutdown
method will be called when the application is going down for
some reason. This method is extremely useful for graceful shutdown the
application, and it's used in providers such as HttpServerProvider
and
DatabaseProvider
:
import { ServiceProvider } from '@athenna/ioc'
export default class AppProvider extends ServiceProvider {
public async shutdown() {
const appHelper = this.container.use('App/Helpers/AppHelper')
if (!appHelper) {
return
}
await appHelper.close()
}
}
Registering providers
All service providers are registered in the .athennarc.json file
. This file
contains a providers
array where you can list the class names of your service
providers. By default, a set of Athenna core service providers are listed in
this array. These providers bootstrap the core Athenna components, such as
the http, route, services and others.
If you are using the make:provider
command to create providers, Athenna
will automatically register the provider for you in this array, but if you
might need to register it manually, just add the path to it to the array:
{
"providers": [
// Other service providers...
"#src/providers/AppProvider"
]
}
Choosing applications
In some cases you want that your provider only runs for a determined type of
Athenna applications. For example, I have a MockedDbProvider
that connects
with some database if I don't have an Artisan command that do some work
inside this database, why I would need to run this MockedDbProvider
when
running Artisan application and commands?
To solve this problem you can use the environment
getter to choose the
applications that could run this provider:
import { ServiceProvider } from '@athenna/ioc'
export default class MockedDbProvider extends ServiceProvider {
public get environment() {
return ['http', 'repl']
}
}
Now when running Artisan application and commands, MockedDbProvider
will be
ignored.
The following environments are available by default in Athenna at this moment:
- http
- repl
- console
- cron
You could also create your own environments. In your
Path.bin('main.ts')
./bin/main.ts
environments
option when calling Ignite.load()
method:
import { Ignite } from '@athenna/core'
const ignite = await new Ignite().load(import.meta.url, {
environments: ['myEnv']
})
await ignite.httpServer()
All of your environments will be merged with Athenna default ones when running
your application. This means that in the example above, when running your
http server, the providers bootstrapped will be only the ones that got the
environments getter returning ['*']
and ['myEnv', 'http']
.