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 eco-system.
We know that ioc.bind
method can be used to register bindings. However, there are no clear guidelines on where to call this method.
It is where service providers comes into the picture. Service providers are simple 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 the binding, and you should never try to use any other binding inside this method.
The boot
method is called when all providers have been registered. It is the right time to use existing bindings to bootstrap some 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 to a service provider. All application specific providers live inside the providers
directory in the root of your app.
Do not wrap packages like lodash inside a provider, since it can be used directly and doesn’t require any setup process that can be abstracted.
|
├── app
└── providers
└── Queue
└── index.js
└── Provider.js
└── start
We are going to wrap bee-queue as a provider. Below is the set of principles we want to follow.
End-user should not have to worry about configuring the queue provider.
All configuration should live inside config/queue.js
file.
It should be simple enough to create new queues with a different configuration.
Let’s create implementing the wrapper inside 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 only has one method called get
, which returns an instance of the queue for a given queue name.
Following are the steps performed by the get method
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 rely on Dependency Injection to provide the Config provider.
Now let’s create a service provider who does the instantiation of this class and binds it to be 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
The 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 of your application and use it as follows.
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 struture. However, we can extract it into it’s own package.
Let’s create a new directory with following directory structure.
└── providers
└── QueueProvider.js
├── src
└── Queue
└── index.js
└── package.json
All we did is moved the actual Queue
implementation to the src
directory and renamed the provider file to QueueProvider.js
Also we have to make following changes
Since the Queue/index.js
is in a different directory, we need to tweak the reference of this file inside our service provider.
Rename Bee/Queue
namespace to a more suited namespace, which has less changes 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('Bee/Queue', () => {
const Config = this.app.use('Adonis/Src/Config')
return new (require('../src/Queue'))(Config)
})
}
}
module.exports = QueueProvider
Make sure you do not include @adonisjs/fold as a dependency for your provider. This should be installed by the main app only. For testing you can install it as a dev dependency.
|
You can use any testing engine you want. However we officially use japa as the testing engine to write tests for any providers.
Setting up japa is simple as shown below.
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 node
command.
node test/example.spec.js
But of course, you want to run all the test files together and for that you can make use of japa-cli
.
npm i --save-dev japa-cli
And run tests as
./node_modules/.bin/japa
Why not install @adonisjs/fold
as a dependency?
If you have ever worked with gulp, they also recommend (p:14) not to install gulp as the dependency when creating plugins.
It is required so that always the main application version of @adonisjs/fold
is installed and your provider makes use of it. Otherwise, each provider will end up shipping it’s own version of IoC container.