const { ServiceProvider } = require('@adonisjs/fold')
class MyProvider extends ServiceProvider {
register () {
// register bindings
}
boot () {
// optionally do some initial setup
}
}
module.exports = MyProvider
You are viewing the legacy version of AdonisJS. Visit https://adonisjs.com for newer docs. This version will receive security patches until the end of 2021.
So far we learned how to bind dependencies to the IoC container.
In this guide, we take a step further to learn about service providers and how to distribute packages that play nicely with the AdonisJs ecosystem.
We know that ioc.bind
method can be used to register bindings. However, we’re yet to define where to call this method.
This is where service providers come into play. Service providers are pure ES6 classes with lifecycle methods that are used to register and bootstrap bindings.
For example:
const { ServiceProvider } = require('@adonisjs/fold')
class MyProvider extends ServiceProvider {
register () {
// register bindings
}
boot () {
// optionally do some initial setup
}
}
module.exports = MyProvider
The register
method is used to register bindings, and you should never try to use any other binding inside this method.
The boot
method is called when all providers have been registered, and is the right place to use existing bindings to bootstrap application state.
For example, adding a view global:
boot () {
const View = this.app.use('Adonis/Src/View')
View.global('time', () => new Date().getTime())
}
Let’s see how we can wrap an existing npm package in a service provider.
Do not wrap packages like lodash in a servive provider since it can be used directly and doesn’t require any setup process.
|
All application specific providers live inside the providers
directory in the root of your app:
├── app
└── providers
└── Queue
└── index.js
└── Provider.js
└── start
We are going to wrap bee-queue as a provider.
Here is a set of principles we want to follow:
The end-user should not have to worry about configuring the queue provider.
All configuration should live inside the config/queue.js
file.
It should be simple enough to create new queues with a different configuration.
Let’s implement the wrapper inside the providers/Queue/index.js
file:
'use strict'
const BeeQueue = require('bee-queue')
class Queue {
constructor (Config) {
this.Config = Config
this._queuesPool = {}
}
get (name) {
/**
* If there is an instance of queue already, then return it
*/
if (this._queuesPool[name]) {
return this._queuesPool[name]
}
/**
* Read configuration using Config
* provider
*/
const config = this.Config.get(`queue.${name}`)
/**
* Create a new queue instance and save it's
* reference
*/
this._queuesPool[name] = new BeeQueue(name, config)
/**
* Return the instance back
*/
return this._queuesPool[name]
}
}
module.exports = Queue
The above class has only one method called get
, which returns an instance of the queue for a given queue name.
The steps performed by the get
method are:
Look for an instance of a given queue name.
If an instance does not exist, read the configuration using the Config Provider.
Create a new bee-queue
instance and store inside an object for future use.
Finally, return the instance.
The Queue
class is pure since it does not have any hard dependencies on the framework and instead relies on Dependency Injection to provide the Config Provider.
Now let’s create a service provider that does the instantiation of this class and binds it to the IoC container.
The code lives inside providers/Queue/Provider.js
:
const { ServiceProvider } = require('@adonisjs/fold')
class QueueProvider extends ServiceProvider {
register () {
this.app.singleton('Bee/Queue', () => {
const Config = this.app.use('Adonis/Src/Config')
return new (require('.'))(Config)
})
}
}
module.exports = QueueProvider
Note that this.app
is a reference to the ioc
object, which means instead of calling ioc.singleton
, we call this.app.singleton
.
Finally, we need to register this provider like any other provider inside the start/app.js
file:
const providers = [
path.join(__dirname, '..', 'providers', 'Queue/Provider')
]
Now, we can call use('Bee/Queue')
inside any file in your application to use it:
const Queue = use('Bee/Queue')
Queue
.get('addition')
.createJob({ x: 2, y: 3 })
.save()
The bee queue provider we created lives in the same project structure. However, we can extract it into its own package.
Let’s create a new directory with the following directory structure:
└── providers
└── QueueProvider.js
├── src
└── Queue
└── index.js
└── package.json
All we did is move the actual Queue
implementation to the src
directory and rename the provider file to QueueProvider.js
.
Also, we have to make the following changes:
Since Queue/index.js
is in a different directory, we need to tweak the reference to this file inside our service provider.
Rename the Bee/Queue
namespace to a more suited namespace, which has fewer chances of collision. For example, when creating this provider for AdonisJs, we will name it as Adonis/Addons/Queue
.
const { ServiceProvider } = require('@adonisjs/fold')
class QueueProvider extends ServiceProvider {
register () {
this.app.singleton('Adonis/Addons/Queue', () => {
const Config = this.app.use('Adonis/Src/Config')
return new (require('../src/Queue'))(Config)
})
}
}
module.exports = QueueProvider
Do not include @adonisjs/fold as a dependency for your provider, as this should be installed by the main application only. For testing, you can install it as a dev dependency.
|
AdonisJs officially uses japa to write provider tests, though you can use any testing engine you want.
Setting up japa is straightforward:
> npm i --save-dev japa
Create the tests inside the test
directory:
> mkdir test
The tests can be executed by running the test file using the node
command:
> node test/example.spec.js
To run all your tests together, you can use japa-cli
:
> npm i --save-dev japa-cli
And run all tests via:
> ./node_modules/.bin/japa
Why not install @adonisjs/fold
as a dependency?
This requirement is so the main application version of @adonisjs/fold
is always installed for your provider to use. Otherwise, each provider will end up shipping its own version of the AdonisJS IoC container. If you have ever worked with gulp, they also recommend (p:14) not to install gulp as a dependency when creating plugins.