【问题标题】:Proper Error throwing and handling to abort a mongoose transaction正确的错误抛出和处理以中止猫鼬事务
【发布时间】:2021-12-09 11:54:36
【问题描述】:

我对 Node 和 mongoose 还是很陌生,我正在尝试在我的节点服务器中实现传入订单的事务以处理 4 个进程并在其中任何一个进程失败时中止,但应用程序崩溃并出现 Unhandled 'error' event错误。 操作顺序为:

  1. 更新产品销售/可用数量
  2. 获取商店的 Stripe 详细信息
  3. 获取客户 Stripe 详细信息
  4. 下订单

如果在第 3 点客户没有保存的默认付款方式,我只发送一个响应,在客户端应用程序中将提示用户注册一个,并抛出一个错误,该错误应该被捕获并中止交易恢复销售的产品/可用数量恢复为以前的值。 因此,对于事务,我是 mongoose 文档中的 try{} catch{} finally{} 范围。 问题是,当我抛出 new Error('User has not registered a default_Method'); 应用程序崩溃并出现 Unhandled 'error' event 错误时,我没有正确处理/处理错误和/或正确使用事务 API。

你能看出我做错了什么吗? 非常感谢。

exports.createOrder = async (req, res) => {

    const { productIdList, productPromotionList, customerId, shopId } = req.body;

    console.log('createOreder req.body is : ', req.body);
    var amount = 0.00;
    var stripeAccountId = '';
    var stripeCustomerId = '';
    var receiptEmail = '';
    var defaultSource = '';
    const session = await Order.startSession();
    session.startTransaction();

    // modify product records
    const ids = productIdList;
    const isPromotion = productPromotionList;
    for (var i = 0; i < ids.length; i++) {
        if (isPromotion[i] == false) {
            await Product.findByIdAndUpdate(
                ids[i],
                {
                    $inc: { soldQuantity: 1, availableQuantity: -1 },
                },
                { new: true },
                function (err, result) {
                    if (err) {
                        console.log('Mongoose Order.createOrder / Product.update error: ', err);

                        res.status(503).send({ error: "Internal error." });
                        session.abortTransaction();
                        return;
                    }

                    console.log('Mongoose Order.createOrder / Product.update Inventory updated successfully for product:', result);
                });
        } else {
            await Promotion.findByIdAndUpdate(
                ids[i],
                {
                    $inc: { soldQuantity: 1, availableQuantity: -1 },
                },
                { new: true },
                function (err, result) {
                    if (err) {
                        console.log('Mongoose Order.createOrder / Promotion.update error: ', err);
                        res.status(503).send({ error: "Internal error." });
                        session.abortTransaction();
                        return;
                    }

                    console.log('Mongoose Order.createOrder / Promotion.update Inventory updated successfully for promotion:', result);
                });
        }

    }

    // get customer stipe id
    await Shop.findById(
        shopId,
        function (err, result) {
            if (err) {
                console.log('Mongoose createOrder / Shop.findById error: ', err);
                res.status(503).send({ error: "Internal error." });
                session.abortTransaction();
                return;
            }
            if (result != null) {
                console.log("Mongoose createOrder / Shop.findById: ", result);
                if (result.stripeDetails) {
                    stripeAccountId = result.stripeDetails.id;
                } else {
                    console.log('Mongoose createOrder / Shop.findById: shop has no stripe id');
                    res.status(503).send({ error: "Internal error." });
                    session.abortTransaction();
                    return;
                }

            } else {
                console.log("Mongoose createOrder / Shop.findById: Shop not found");
                res.status(404).send({ error: "Shop not found." });
                session.abortTransaction();
                return;
            }
        }

    );

    // get customer stripe credentials 

    await User.findById(
        customerId,
        function (err, result) {
            if (err) {
                console.log('Mongoose createOrder / User.findOneById error: ', err);
                res.status(503).send({ error: "Internal error." });
                throw err;
                return;
            }
            if (result != null) {
                console.log("Mongoose createOrder / User.findOneById: ", result);

                if (result.stripeDetails) {
                    stripeCustomerId = result.stripeDetails.id;

                } else {
                    console.log('Mongoose createOrder / User.findOneById: User has no stripe id');
                    res.status(404).send({ message: "User has no stripe id" });
                    session.abortTransaction();
                    return;
                }
                // check if user has registered a pyment_method
                if (result.stripeDetails.default_source != null) {
                    defaultSource = result.stripeDetails.default_source;
                    receiptEmail = result.stripeDetails.email;

                } else {
                    console.log('Mongoose createOrder / User.findOneById: User has not registered a default_Method');
                    res.status(402).send({ message: "Stripe: Default Payment Required" });

                    /// unhandled error and app crashes
                    session.abortTransaction();
                    return;
                }

            } else {
                console.log('Mongoose createOrder / User.findOneById: User not found');
                res.status(404).send({ error: "User not found." });
                session.abortTransaction();
                return;
            }
        }

    ).catch((e) => {

        console.log('User.findById .catch() error is :', e)
    });

    /// save order + create stripe payment
    await Order.create(
        req.body,
        function (err, result) {
            if (err) {
                console.log('Mongoose createOrder error:  Order.create', error);
                res.statut(503).send({ error: "Internal error" });
                session.abortTransaction();
                return;
            }

            /// create charge/payment

            // stripe.paymentIntents.create({
            //     customer: stripeCustomerId,
            //     receipt_email: receiptEmail,
            //     account: stripeAccountId,
            //     default_source: defaultSource,
            //     confirm: true,
            //     setup_future_usage: "on_session",// makes it pay in one go
            //     amount: amount,
            //     application_fee_amount: (amount * 0.13) + 25,
            //     currency: "eur",
            //     paymentMethods: ["card"],
            //     description: `Payment for order: ${result.id}`,
            //     on_behalf_of: stripeCustomerId,

            // })
            //     .then(paymentIntent => {
            //         console.log('Mongoose createOrder stripe paymentIntent: ', paymentIntent);
            //         result.set('stripePayment', paymentIntent);
            //         result.save();
            //     })
            //     .catch(error => {
            //         console.log('Mongoose createOrder stripe paymentIntent error: ', error);
            //     });
            console.log('Mongoose createOrder: ', result);
            res.status(200).send({
                message: "Order created successfully!",
                data: result
            });

        });

    await session.commitTransaction();

    session.endSession();
}

【问题讨论】:

    标签: node.js mongoose transactions


    【解决方案1】:

    主要问题是我还没有将我的本地 MongoDB 实例转换为副本集,所以这是首先要解决的问题。 然后我实现了try{}catch{}finally{} 语句来处理错误和 await session.commitTransaction();session.endSession(); 的调用取决于会话状态,因为调用 abortTransaction() 会使会话脱离 inTransaction 状态。 我将更改业务逻辑面团并检查用户和客户付款要求,然后仅在满足产品和订单模式时开始交易,以节省服务器大量操作。

    所以这里的代码应该可以帮助其他在 mongo 事务中苦苦挣扎的人:

    exports.createOrder = async (req, res) => {
    
        const { productIdList, productPromotionList, customerId, shopId } = req.body;
    
        console.log('createOreder req.body is : ', req.body);
        var amount = 0.00;
        var stripeAccountId = '';
        var stripeCustomerId = '';
        var receiptEmail = '';
        var defaultSource = '';
        const session = await Order.startSession();
        session.startTransaction();
    
        try {
            /// test error
            // throw Error('Test error to see if calling session.endSession() in cath{} crashes the app.');
    
    
            // modify product records
            const ids = productIdList;
            const isPromotion = productPromotionList;
            for (var i = 0; i < ids.length; i++) {
                if (isPromotion[i] == false) {
                    const product = await Product.findByIdAndUpdate(
                        ids[i],
                        {
                            $inc: { soldQuantity: 1, availableQuantity: -1 },
                        },
                        { session: session },
                        function (err, result) {
                            if (err) {
                                console.log('Mongoose Order.createOrder / Product.update error: ', err);
                                res.status(503).send({ error: "Internal error." });
                                session.abortTransaction();
                                return;
                            }
    
                            console.log('Mongoose Order.createOrder / Product.update Inventory updated successfully for product:', result);
                        });
                } else {
                    const promotion = await Promotion.findByIdAndUpdate(
                        ids[i],
                        {
                            $inc: { soldQuantity: 1, availableQuantity: -1 },
                        },
                        { session: session },
                        function (err, result) {
                            if (err) {
                                console.log('Mongoose Order.createOrder / Promotion.update error: ', err);
                                res.status(503).send({ error: "Internal error." });
                                session.abortTransaction();
                                return;
                            }
    
                            console.log('Mongoose Order.createOrder / Promotion.update Inventory updated successfully for promotion:', result);
                        });
                }
    
            }
    
            ///get customer stipe id 
            ///  if passing the session to the options it will throw
            ///    Error: ClientSession cannot be serialized to BSON.
    
            const shop = await Shop.findById(
                shopId,
                // { session: session }, 
                function (err, result) {
                    if (err) {
                        console.log('Mongoose createOrder / Shop.findById error: ', err);
                        res.status(503).send({ error: "Internal error." });
                        session.abortTransaction();
                        return;
                    }
                    if (result != null) {
                        console.log("Mongoose createOrder / Shop.findById: ", result);
                        if (result.stripeDetails != null) {
                            stripeAccountId = result.stripeDetails['id'];
                        } else {
                            console.log('Mongoose createOrder / Shop.findById: shop has no stripe id');
                            res.status(503).send({ error: "Internal error." });
                            session.abortTransaction();
                            return;
                        }
    
                    } else {
                        console.log("Mongoose createOrder / Shop.findById: Shop not found");
                        res.status(404).send({ error: "Shop not found." });
                        session.abortTransaction();
                        return;
                    }
                }
    
            );
    
            // ////get customer stripe credentials 
    
            const user = await User.findById(
                customerId,
                function (err, result) {
                    if (err) {
                        console.log('Mongoose createOrder / User.findOneById error: ', err);
                        res.status(503).send({ error: "Internal error." });
                        session.abortTransaction();
                        return;
                    }
                    if (result != null) {
                        console.log("Mongoose createOrder / User.findOneById: ", result);
    
                        if (result.stripeDetails) {
                            stripeCustomerId = result.stripeDetails['id'];
    
                        } else {
                            console.log('Mongoose createOrder / User.findOneById: User has no stripe id');
                            res.status(404).send({ message: "User has no stripe id" });
                            session.abortTransaction();
                            return;
                        }
                        //check if user has registered a pyment_method
                        if (result.stripeDetails['default_source'] != null) {
                            defaultSource = result.stripeDetails['default_source'];
                            receiptEmail = result.stripeDetails['email'];
    
                        } else {
                            console.log('Mongoose createOrder / User.findOneById: User has not registered a default_Method');
                            res.status(402).send({ message: "Stripe: Default Payment Required" });
    
                            session.abortTransaction();
                            return;
                        }
    
                    } else {
                        console.log('Mongoose createOrder / User.findOneById: User not found');
                        res.status(404).send({ error: "User not found." });
                        session.abortTransaction();
                        return;
                    }
                }
    
            );
    
            //// save order + create stripe payment
            const order = await Order.create(
                req.body,
                { session: session },
                function (err, result) {
                    if (err) {
                        console.log('Mongoose createOrder error:  Order.create', error);
                        res.statut(503).send({ error: "Internal error" });
                        session.abortTransaction();
                        return;
                    }
    
                    /////// create charge/payment
    
                    // stripe.paymentIntents.create({
                    //     customer: stripeCustomerId,
                    //     receipt_email: receiptEmail,
                    //     account: stripeAccountId,
                    //     default_source: defaultSource,
                    //     confirm: true,
                    //     setup_future_usage: "on_session",// makes it pay in one go
                    //     amount: amount,
                    //     application_fee_amount: (amount * 0.13) + 25,
                    //     currency: "eur",
                    //     paymentMethods: ["card"],
                    //     description: `Payment for order: ${result.id}`,
                    //     on_behalf_of: stripeCustomerId,
    
                    // })
                    // .then(paymentIntent => {
                    //     console.log('Mongoose createOrder stripe paymentIntent: ', paymentIntent);
                    //     result.set('stripePayment', paymentIntent);
                    //     result.save();
                    // })
                    // .catch(error => {
                    //     console.log('Mongoose createOrder stripe paymentIntent error: ', error);
                    // res.statut(503).send({ error: "Payment processing error" });
                    // session.abortTransaction();
                    // });
                    console.log('Mongoose createOrder: ', result);
                    res.status(200).send({
                        message: "Order created successfully!",
                        data: result
                    });
    
                });
    
    
    
        }
        catch (error) {
            console.log('Transaction error is:', error);
            await session.abortTransaction();
            session.endSession();
            res.status(503).send({ error: "Internal error." });
            // throw error; 
        }
        finally {
    
            if (session.inTransaction == true) {
    
                console.log('committing session');
                await session.commitTransaction();
                console.log('ending session');
                // if called here the app won`t crash
                session.endSession();
            } else {
                console.log('session was aborted');
    
                /// if endSession() get called here app crashes with 
                /// error: MongoError: Attempted illegal state transition from [TRANSACTION_ABORTED] to [TRANSACTION_ABORTED]
                /// because session is not in transaction state
                // session.endSession(); 
            }
    
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-30
      • 1970-01-01
      • 2013-11-23
      • 2017-08-31
      • 2020-07-18
      • 2013-08-20
      • 1970-01-01
      相关资源
      最近更新 更多