Saga的设计

brighthas 14-09-12
              

下面是 saga的代码,用JS实现的,省略了多余代码。希望banq大哥和各位指点。



// 定义Saga的一个例子

var T = Saga.extend({

// 启动方法
startup: function () {
// 减少用户1 的钱
user1.减少钱() ;
//产生领域事件 “User - ”
}

// process manage 监听领域事件,根据这些事件发起新事件。
handles: {
// 用户1 减少钱事件
"User - ": function (event) {
// 用户1减少钱了,把这笔钱给用户2
user2.增加钱();
// 会产生领域事件 “User +”
},
// 监听到 用户2 增加钱事件
"User + ": function (event) {
// 这时候转账结束,要publish一个 completed事件,表示saga结束。
this.publish(
"completed");
}
}

})



saga也是一个聚合根,拥有自己的uid,在command handle里可以直接像聚合一样调用。下面是 command handle 的调用代码,省略了多余代码,看起来更清晰。


var newSaga = sagaRepository.create(...);
newSaga.startup();


saga公开的只有一个startup方法。

              

tangxuehua
2014-09-12 12:36

也可以看看我用c#实现的基于saga思想的转账的例子:
https://github.com/tangxuehua/enode/tree/master/src/Samples/BankTransferSample

brighthas
2014-09-12 13:16

谢谢,那你看看我这个思路有什么问题没有?

liangshan
2014-09-12 16:39

我想了一下转账这个问题,能不能这样考虑:
将转账这个事务看作一次运动,这是一次代表钱的数字从user1运动到user2的运动。我们可以将这次运动用如下形式标识:
public class MoneyTransfer {
public Guid Id {get; private set;}
public Guid FromAccountID {get; private set;}
public Guid ToAccountID {get; private set;}
public decimal MoneyValue {get; private set;}
}
然后这个运动的完成过程由于被我们分成了两个并行的步骤——FromAccountID的减钱和ToAccountID账户的加钱。于是MoneyTransfer中不得不加入跟踪这两个步骤完成情况的状态字段,变成下面这个样子:
public class MoneyTransferActor {
private Guid Id {get; set;}
private Guid FromAccountID {get; set;}
private Guid ToAccountID {get; set;}
private decimal MoneyValue {get; set;}

private bool[] dones{get;set;}
private IActor[] ChildActors{get;set;}
}
MoneyTransferActor中有两个ChildActor,一个负责user1的减钱一个负责user2的加钱。MoneyTransferActor负责监管整个转账事务,它的两个子actor必须都成功,如果其中一个成功了而另一个失败了则MoneyTransferActor负责通知成功的那个子actor回退。
事务完成后MoneyTransferActor的状态持久化起来即是完整的日志,完整的可以原路返回。

banq
2014-09-13 13:42

2014-09-12 16:39 "@anycmd"的内容
MoneyTransferActor负责监管整个转账事务 ...


是的,需要一个监管全局事务的管理者,但不能使用锁。Akka的Actor模型使用的是语言本身协调机制:



try {
coordinated atomic {
    account1 ! coordinated.coordinate(Deposit(75))
    account2 ! coordinated.coordinate(Withdraw(75))
}
} catch {
    case ex:Exception => println(ex.getMessage)
}