Sunday, 2 December 2018

Mongoose data flow

I have built a simple MERN app where users can rate phone numbers. Users just fill in the phone number, choose rating (1 - 5 star rating), their city & short text. The app has search function with filter & sorting options. It all works good enough ATM but I think it might break when multiple concurrent users use the website because I update the phone number model (mobileSchema) after a rating (messageSchema) has been submitted - using Mongoose middlewares (post hooks).

For example, I need to calculate number of ratings (messagesCount) for phone number. I use Message.countDocuments({ mobile: mobile._id }) for that. However, I also need to update other properties of phone number (mobileSchema - lastMessageDate, globalRating, averageRating) so that operation takes some time. I believe the number of ratings might not be right when 2 users submit rating at the same time - it will increment the number of ratings (messagesCount) by 1 instead of 2.

Is there a better approach? Can a post hook be fired after the previous post hook already finished?

Sample code:

const mobileSchema = new Schema({
    number: { type: String, required: true },
    plan: { type: String, required: true },
    date: { type: Date, default: Date.now, required: true, index: 1 },
    messagesCount: { type: Number, default: 0, index: 1 },
    lastMessageDate: { type: Date, index: 1 },
    // normal mean
    globalRating: { type: Number, default: 0, index: 1 },
    // weighted mean
    averageRating: { type: Number, default: 0, index: 1 }
});

const messageSchema = new Schema({
    comment: { type: String, required: true },
    city: { type: Schema.Types.ObjectId, ref: 'City', required: true, index: 1 },
    rating: { type: Number, required: true, index: 1 },
    date: { type: Date, default: Date.now, required: true, index: 1 },
    mobile: { type: Schema.Types.ObjectId, ref: 'Mobile', required: true },
    user: { type: Schema.Types.ObjectId, ref: 'User', required: true }
});

messageSchema.post('save', function (message, next) {
    const messageModel = this.constructor;
    return updateMobile(messageModel, message, next, 1);
});

const updateMobile = (messageModel, message, next, addMessage) => {
    const { _id } = message.mobile;
    const cityId = message.city._id;
    const lastMessageDate = message.date;
    let mobile;
    hooks.get(Mobile, { _id })
        .then(mobileRes => {
            mobile = mobileRes;
            return Message.countDocuments({ mobile: mobile._id })
        })
        .then(messagesCount => {
            if (messagesCount <= 0) {
                const deleteMobile = Mobile.findOneAndDelete({ _id: mobile._id })
                const deleteSeen = SeenMobile.findOneAndDelete({ mobile: mobile._id, user: message.user._id })
                const cityMobile = updateCityMobile(messageModel, mobile, cityId)
                Promise.all([deleteMobile, deleteSeen, cityMobile])
                    .then(() => {
                        return next();
                    })
                    .catch((err) => {
                        console.log(err);
                        return next();
                    })
            }
            else {
                if (addMessage === -1) lastMessageDate = mobile.lastMessageDate;
                const ratings = hooks.updateGlobalRating(mobile, messageModel)
                    .then(() => hooks.updateAverageRating(mobile, messageModel))
                    .then(() => {
                        return new Promise((resolve, reject) => {
                            mobile.set({
                                messagesCount,
                                lastMessageDate
                            });
                            mobile.save((err, mobile) => {
                                if (err) return reject(err);
                                resolve();
                            });
                        })
                    })
                const cityMobile = updateCityMobile(messageModel, mobile, cityId)
                Promise.all([ratings, cityMobile])
                    .then(([ratings, cityMobile]) => {
                        return next();
                    })
                    .catch(err => console.log(err))
            }
        })
        .catch(err => {
            console.log(err);
        })
}



from Mongoose data flow

No comments:

Post a Comment