AsyncResult and handling rollback

♀尐吖头ヾ 提交于 2021-01-29 08:49:44

问题


(warning: this was posted also on https://forums.fsharp.org/t/asyncresult-and-handling-rollback/928)

Trying to implement 2PC Transaction Like workflow in F# (see http://learnmongodbthehardway.com/article/transactions/) and hitting an issue with computation expressions (asyncResult for example) and rollback.

If you have the following pseudocode:

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
}

how can I check in executeWorkflow if result1 and/or result2 is Error and then call rollbackWorkflow function?? Should I change callService1 and callService2 to raise exceptions instead of returning results also in expected error cases (not sufficient funds, limits exceeded), or should I use some function like teeError? Any suggestion highly appreciated!

P.S. This is what I want to eventually implement:

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:


Just do some error handling outside the workflow, like this:

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)

I wrote that example with the AsyncResult from the code you linked. Plus bindError which should be something like:

/// 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
    }

If you think about it, bindError is like a pure version of the catch function, see for example this code fragment , using another library.



来源:https://stackoverflow.com/questions/57284040/asyncresult-and-handling-rollback

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!