background

Paging is a common feature in Web development, and almost all Web frameworks have paging implemented, either built into the framework or in the form of plug-ins. Recently, I was using egg.js for back-end development. As the database was using MongoDB, the official Egg-Mongoose was naturally selected as ODM Plugin, and I happened to encounter a paging problem in the development process.

Implementation in Koa

To make it easier to understand, let’s first talk about the implementation in Koa. Using the Koa framework, a Plugin is implemented directly based on Mongoose’s own Plugin mechanism (you can also choose a tripartite implementation: The Mongoose-paginate-v2) pagination Plugin is also very convenient to use. It can be loaded when Mongoose is initialized:

const mongoose = require('mongoose') const config = require('config') const paginate = require('.. /middlewares/paginate') // Enable paging plugin mongoose.plugin(paginate) Mongoose.connect (config.get('db'), {useNewUrlParser: /middlewares/paginate') true }) mongoose.Promise = global.Promise const db = mongoose.connectionCopy the code

This Plugin can then be used in any global Model, i.e. a way to extend Paginate into a Mongoose Model, as in:

const apps = await AppModel.paginate({}, {page})
Copy the code

Problems in Egg

In Egg, since the egg-Mongoose plug-in is used, the initialization process has already been handled by the plug-in itself. Therefore, if you want the plug-in to take effect, you can directly add it to the corresponding Model:

Module.exports = app => {const mongoose = app.mongoose // enable page plugin mongoose. Plugin (paginate) const Schema = mongoose.Schema const schema = new Schema({ }) return mongoose.model('App', schema); }Copy the code

If there are multiple models, which Model is the appropriate place to load the Plugin? After all, this is a global setting. In case the Model that loaded the Plugin is abandoned, if you forget to migrate this logic, it will cause a Bug, which will be more troublesome to check after a long time.

Well, what about all models? Wouldn’t that solve the problem? In fact, this is not appropriate, too redundant, maintenance is not convenient.

So it’s important to find a global place to put it as soon as possible during App initialization, so that as long as the service is running, the plugin will work without the risk that Paginate is not a function

To solve the problem

Taking a closer look at Egg’s documentation, we realized that Egg’s support for customizing various life cycles at startup should solve our problem, so we created the app.js file in the project directory:

class AppBootHook {
    constructor(app) {
        this.app = app;
    }

    configWillLoad() {
        // Ready to call configDidLoad,
        // Config, plugin files are referred,
        // this is the last chance to modify the config.
    }

    configDidLoad() {
        // Config, plugin files have been loaded.
    }

    async didLoad() {
        // All files have loaded, start plugin here.
    }

    async willReady() {
        // All plugins have started, can do some thing before app ready
    }

    async didReady() {
        // Worker is ready, can do some things
        // don't need to block the app boot.
    }

    async serverDidReady() {
        // Server is listening.
    }

    async beforeClose() {
        // Do some thing before app close.
    }
}

module.exports = AppBootHook
Copy the code

Looking at so many life cycles, a new question arises: which life cycle should Mongoose’s Plugin be placed in? To solve this problem, you need to understand the initialization process of egg-Mongoose, so look directly at the source code: the source code is very simple, probably a quick glance to find the following line:

app.beforeStart(() => {
  loadModelToApp(app);
});
Copy the code

Well, it turns out that the models are loaded into the App in the beforeStart method. As long as the Plugin is loaded beforeStart it takes effect globally, but the above life cycle classes seem to have a beforeClose method without beforeStart.

I searched the document carefully and finally found a line at the bottom of the document:

If your Egg framework’s lifecycle functions are older, you are advised to upgrade to class method mode. See upgrade your lifecycle event function for details.

Is it the old version problem? Continue reading the document with the problem, ha, sure enough, because of the Egg upgrade:

Class AppBootHook {constructor(app) {this.app = app; } async didLoad() {// Please place the code in app.beforestart in your plug-in project here. } async willReady() {// Please place the code in app.beforeStart in your application project here. } } module.exports = AppBootHook;Copy the code

The rest of the story is simple, but in order for the paging plug-in to work before then, you need to put the corresponding logic in the pre-didLoad lifecycle, namely: configDidLoad, plus the associated code

const paginate = require('./app/middleware/paginate')
class AppBootHook {
    constructor(app) {
        this.app = app;
    }

    configWillLoad() {
        // Ready to call configDidLoad,
        // Config, plugin files are referred,
        // this is the last chance to modify the config.
    }

    configDidLoad() {
        // Config, plugin files have been loaded.
        const mongoose = this.app.mongoose
        mongoose.plugin(paginate)
    }
    ...
}

module.exports = AppBootHook
Copy the code

You’re done