AsyncResult和处理回滚

时间:2019-07-31 05:48:56

标签: f# expression computation

(警告:此消息也发布在https://forums.fsharp.org/t/asyncresult-and-handling-rollback/928上)

尝试在F#中实现类似于2PC的类似事务工作流(请参阅http://learnmongodbthehardway.com/article/transactions/),并遇到计算表达式(例如asyncResult)和回滚的问题。

如果您具有以下伪代码:

let rollbackWorkflow parX parY =
… here calling rollbackService1 and rollbackService2

let executeWorkflow par1 par2 par3 =
asyncResult {
let! result1 = callService1 x y z
let! result2 = callService2 x2 y2 z2
}

如果result1和/或result2为Error,如何检入executeWorkflow,然后调用rollbackWorkflow函数?我应该更改callService1和callService2以引发异常,而不是在预期的错误情况下(资金不足,超出限制)返回结果,还是应该使用类似teeError的函数?任何建议,高度赞赏!

P.S。这是我最终要实现的:

function executeTransaction(from, to, amount) {
var transactionId = ObjectId();

transactions.insert({
_id: transactionId,
source: from,
destination: to,
amount: amount,
state: “initial”
});

var result = transactions.updateOne(
{ _id: transactionId },
{ $set: { state: “pending” } }
);

if (result.modifiedCount == 0) {
cancel(transactionId);
throw Error(“Failed to move transaction " + transactionId + " to pending”);
}

// Set up pending debit
result = accounts.updateOne({
name: from,
pendingTransactions: { $ne: transactionId },
balance: { $gte: amount }
}, {
$inc: { balance: -amount },
$push: { pendingTransactions: transactionId }
});

if (result.modifiedCount == 0) {
rollback(from, to, amount, transactionId);
throw Error(“Failed to debit " + from + " account”);
}

// Setup pending credit
result = accounts.updateOne({
name: to,
pendingTransactions: { $ne: transactionId }
}, {
$inc: { balance: amount },
$push: { pendingTransactions: transactionId }
});

if (result.modifiedCount == 0) {
rollback(from, to, amount, transactionId);
throw Error(“Failed to credit " + to + " account”);
}

// Update transaction to committed
result = transactions.updateOne(
{ _id: transactionId },
{ $set: { state: “committed” } }
);

if (result.modifiedCount == 0) {
rollback(from, to, amount, transactionId);
throw Error(“Failed to move transaction " + transactionId + " to committed”);
}

// Attempt cleanup
cleanup(from, to, transactionId);
}

executeTransaction(“Joe Moneylender”, “Peter Bum”, 100);

1 个答案:

答案 0 :(得分:0)

只需在工作流之外进行一些错误处理,就像这样:

asm ("rex.W ljmp *1f\n\t"
     /* The pointer is being stored with the code.
        The jump will be to the label after the pointer itself at label '2:'. */
     "1:  .quad 2f\n\t"
     "    .word %c[sel]\n\t"
     "2:\n\t"
     :
     : [sel]"i"(__KERNEL_CS));

我用您链接的代码中的AsyncResult编写了该示例。加type TransactionError = | NoFunds | Other let rollbackWorkflow parX parY = async.Return ( printfn "here calling rollbackService1 and rollbackService2"; Ok -1 ) let callService1 parX parY = async.Return ( printfn "callService1"; if parX + parY > 0 then Ok 1 else Error NoFunds ) let callService2 parX parY = async.Return ( printfn "callService2"; if parX + parY > 0 then Ok 2 else Error Other ) let executeWorkflow par1 par2 par3 = asyncResult { let! result1 = callService1 par1 par2 let! result2 = callService2 result1 par3 return result2 } |> AsyncResult.bindError (fun x -> if x = NoFunds then rollbackWorkflow 0 1 else rollbackWorkflow 1 0) 应该是这样的:

bindError

如果考虑一下,/// Apply a monadic function to an AsyncResult error let bindError (f: 'a -> AsyncResult<'b,'c>) (xAsyncResult : AsyncResult<_, _>) :AsyncResult<_,_> = async { let! xResult = xAsyncResult match xResult with | Ok x -> return Ok x | Error err -> return! f err } 就像catch函数的纯版本,请参见this code fragment,使用另一个库。