如何在 Meteor 方法调用中实现事务



假设我有 2 个集合">计划订阅"和">客户端激活"。我正在连续插入两个集合。

后面一个取决于前一个,如果任何事务失败,那么整个操作必须回滚。

如何在流星 1.4 中实现这一目标?

由于MongoDB不支持原子性,因此您必须使用方法链来管理它。

你可以写一个方法,比如说,transaction调用PlanSubscriptions.insert(data, callback)。然后在回调函数中,如果第一次插入成功,您将调用ClientActivations.insert(data, callback1),如果第二次插入成功,则callback1返回 truey,否则为 falsy。如果第一次插入返回错误,则无需执行任何操作,但如果第二次插入返回错误,则删除从第一个集合中的插入中获得的 id。

我可以建议以下结构:

'transaction'(){
PlanSubscriptions.insert(data, (error, result)=>{
if(result){
// result contains the _id
let id_plan = result;
ClientActivations.insert(data, (error, result)=>{
if(result){
// result contains the _id
return true;
}
else if(error){
PlanSubscriptions.remove(id_plan);
return false;
}
})
}
else if(error){
return false;
}
})
}

在Meteor中没有办法做到这一点,因为mongodb不是一个符合ACID的数据库。它具有单文档更新原子性,但不是多文档更新原子性,这是两个集合的情况。 来自 mongo 文档:

当单个写入操作修改多个文档时,每个文档的修改是原子的,但整个操作不是原子的,其他操作可能会交错。

有一种隔离多文档更新可见性的方法可用,但这可能不是您需要的。

使用 $isolated 运算符,影响多个文档的写入操作可以在写入操作修改第一个文档后阻止其他进程交错。这可确保在写入操作完成或出错之前,没有客户端看到更改。 独立写入操作不提供"全有或全无"原子性。也就是说,写入操作期间的错误不会回滚错误之前的所有更改

但是,有几个库试图在应用程序级别解决此问题。我建议看看小鹿

在您的情况下,您正好有两个依赖集合,可以利用两阶段提交技术。在此处阅读更多相关信息:两阶段提交

好吧,我自己想通了。

我添加了一个包babrahams:transactions

在服务器端 Meteor 方法调用中,我调用了包全局公开tx对象。整个服务器端Meteor.method({})如下所示。

import { Meteor } from 'meteor/meteor';
import {PlanSubscriptions} from '/imports/api/plansubscriptions/plansubscriptions.js';
import {ClientActivations} from '/imports/api/clientactivation/clientactivations.js';
Meteor.methods({
'createClientSubscription' (subscriptionData, clientActivationData) {
var txid;
try {
txid = tx.start("Adding Subscription to our database");
PlanSubscriptions.insert(subscriptionData, {tx: true})
ClientActivations.insert(activation, {tx: true});
tx.commit();
return true;
} catch(e){
tx.undo(txid);
}
return false;
}
});

我每添加{tx : true}一个插入,就断定它是交易的一部分。

服务器控制台输出:

I20170523-18:43:23.544(5.5)? Started "Adding Subscription to our database" with
transaction_id: vdJQvFgtyZuWcinyF
I20170523-18:43:23.547(5.5)? Pushed insert command to stack: vdJQvFgtyZuWcinyF
I20170523-18:43:23.549(5.5)? Pushed insert command to stack: vdJQvFgtyZuWcinyF
I20170523-18:43:23.551(5.5)? Beginning commit with transaction_id: vdJQvFgtyZuWcinyF
I20170523-18:43:23.655(5.5)? Executed insert
I20170523-18:43:23.666(5.5)? Executed insert
I20170523-18:43:23.698(5.5)? Commit reset transaction manager to clean state

有关更多信息,您可以转到链接:https://github.com/JackAdams/meteor-transactions

注意:我正在使用流星 1.4.4.2

只是分享这个链接给未来的读者:

https://forums.meteor.com/t/solved-transactions-with-mongodb-meteor-methods/48677

import { MongoInternals } from 'meteor/mongo';
// utility async function to wrap async raw mongo operations with a transaction
const runTransactionAsync = async asyncRawMongoOperations => {
// setup a transaction
const { client } = MongoInternals.defaultRemoteCollectionDriver().mongo;
const session = await client.startSession();
await session.startTransaction();
try {
// running the async operations
let result = await asyncRawMongoOperations(session);
await session.commitTransaction();
// transaction committed - return value to the client
return result;
} catch (err) {
await session.abortTransaction();
console.error(err.message);
// transaction aborted - report error to the client
throw new Meteor.Error('Database Transaction Failed', err.message);
} finally {
session.endSession();
}
};
import { runTransactionAsync } from '/imports/utils'; // or where you defined it
Meteor.methods({
async doSomething(arg) {
// remember to check method input first
// define the operations we want to run in transaction
const asyncRawMongoOperations = async session => {
// it's critical to receive the session parameter here
// and pass it to every raw operation as shown below
const item = await collection1.rawCollection().findOne(arg, { session: session });
const response = await collection2.rawCollection().insertOne(item, { session: session });
// if Mongo or you throw an error here runTransactionAsync(..) will catch it
// and wrap it with a Meteor.Error(..) so it will arrive to the client safely
return 'whatever you want'; // will be the result in the client
};
let result = await runTransactionAsync(asyncRawMongoOperations);
return result;
}
});

最新更新