How do you persist state of Sagas?

简介:

Just a quick question on saga persistence - how do you persist saga state and dispatch messages while avoiding transactions and 2PC? 

Long story: I'm trying to reason out the logic behind sagas, in order to understand everything better (and map concepts back to the reactive programming) 

Basically a saga is an entity, that is used to coordinate some long- running process. It can subscribe to events (UserAccountCreated), keep track of time (i.e.: user should activate his account within 24 hours) and send commands (CancelUserRegistration). 

Additionally, since saga is an entity and could be addressed in the scalable world, we can send command directly to the saga (StopRegistrationProcess). Sagas can be modeled and perceived as finite state machines. So far - so good and rather straightforward. 

However just a quick question: how do you persist saga state and send messages out of it? 

Logically, in order to avoid 2PC and transactions you would need to join state transition and publication in one atomic operation (just like with the aggregate roots and event sourcing) and reuse message dispatching mechanism that catches up with the history (append-only persistence scales much better anyway) 

This feels like more sensible and simple operation, than introducing relational DBs or any kind of transactions into the system. However, as I recall, I've never heard of using event sourcing for the saga state persistence. Is there a reason for this? How do you implement your sagas and persist their state? 

All feedback would be appreciated! 

Best regards, 
Rinat Abdullin

 

Rinat, 

There are a few options two avoid 2PC.  One of the easiest ways is to simply have the saga entity store a list of all command IDs internally.  Rarely will you have sagas that exist beyond even several dozen commands/events.  That being the case, you can effectively treat the saga as a kind of aggregate root using event sourcing. (More on this in a minute.) 

By storing the command IDs internal to the saga, you can avoid 2PC by having two completely separate transactions--an outer as well as an inner transaction.  The inner transaction is related to committing the saga "aggregate" to the event store.  The outer transaction is related to removing the message from the message queue.  If the message queue doesn't support TransactionScope, it's not a big deal--it will attempt to deliver the message at least once and you can easily detect it as a duplicate and drop it because it's already been handled. 

Let the event store do the publishing for you asynchronously. 

I've outlined a few of these concepts in some blog posts I wrote a few 
months back (one of which you commented on): 
http://jonathan-oliver.blogspot.com/2010/04/extending-nservicebus-avoiding-two.html 
http://jonathan-oliver.blogspot.com/2010/04/idempotency-patterns.html 
http://jonathan-oliver.blogspot.com/2010/04/message-idempotency-patterns-and-disk.html 

The other part of your question is how to leverage event sourcing to take care of sagas.  It's not unlike your typically aggregate root. Some kind of stimulus comes in (either a command or event), you transition the state (this being the part that's distinct from DDD aggregates), which results in a message being "raised".  Then, you commit the new state to the event store and let it perform the message dispatch asynchronously. 

Jonathan Oliver

The only thing that I would add is that sagas should be more like a state machine which is about *process*, whereas our aggregates are more about *logic* (if statements and flow control).

Jonathan Oliver 

Ah, thanks a lot guys.

So basically for the saga persistence we can have either event sourcing (command is saved along with the events in the transaction) or simple state storage (command is saved along with the latest state and possible outgoing events). Dispatcher could dispatch in async later in both cases.

Once we have command info persisted atomically with the resulting changes, we can have all the idempotence we need (still staying away from the 2PC). Consistency is 100% even if process dies between the commit and ACK.

So technically sagas are just like the aggregates (they are entities), and the primary difference is in the intent (similar to the differencebetween commands and events) and life span expectations.

This way everything that happens in saga between the handler and message dispatch is rather straightforward, reliable and simple (and
similar to the aggregate behavior).

Thanks again for helping to think though the logic of this part of CQRS!

Best regards,
Rinat

I agree. Sagas and aggregates have different intent plus resulting differences in behavior, life cycle and persistence. Ignoring this  in
the project might kick in the natural selection process for it.

However, implementation logic of command handlers outside of these "inner" specifics seem to be similar for both cases (i.e.: questions of reliability, 2PC, transactions and message dispatch). Don't you think?

Best regards,
Rinat

 

hi Rinat,

I'm using Esper for my "sagas" and currently I can rebuild it's state by replaying events at startup.

Esper allows one send timetick events to control the flow of time when replaying in isolation, and it's pretty awesome!

Pedro H S Teixeira

 

Hi,

Can anybody provide with a pseudo code for saga?

That would make things more clear.
Bhoomi Kakaiya
 
 
Bhoomi,

I've published an article that goes into some deeper on Sagas (as per discussions in this thread and outside of it).

Although there is still no source code, but it might help to understand everything.

http://abdullin.com/journal/2010/9/26/theory-of-cqrs-command-handlers-sagas-ars-and-event-subscrip.html

Just a caveat: I'm sorry for going into deep details about the partitioning logic (this was needed by the specifics). In practice implementations will probably skip this part completely in 95% of cases (and go lightly on a few other explicit constraints as well).

Best regards,
Rinat


目录
相关文章
|
存储 PyTorch 算法框架/工具
Error(s) pytorch 加载checkpoint state_dict出错:Missing key(s) && Unexpected key(s) in state_dict
Error(s) pytorch 加载checkpoint state_dict出错:Missing key(s) && Unexpected key(s) in state_dict
544 0
Error(s) pytorch 加载checkpoint state_dict出错:Missing key(s) && Unexpected key(s) in state_dict
|
11月前
加载模型出现-RuntimeError: Error(s) in loading state_dict for Net:unexpected key(s) in state_dict: XXX
加载模型出现-RuntimeError: Error(s) in loading state_dict for Net:unexpected key(s) in state_dict: XXX
358 0
|
4月前
报错modify sync object Modify sync object Failed!
报错modify sync object Modify sync object Failed!
21 1
|
8月前
|
索引
beamManagement(二)TCI-state/QCL
上一篇讲解了idle初始接入阶段,基站和UE用SSB的索引,关联PRACH的发送时刻比较内涵的指示了波束信息;在RRC建立进入connected mode后,就可以通过TCI State来指示波束信息, 为利于后续内容理解,这里先看下TCI-state及QCL的概念。
|
前端开发
使用this.setState修改state上的数据
使用this.setState修改state上的数据
使用this.setState修改state上的数据
|
存储 NoSQL Linux
redis异常 Commands that may modify the data set are disabled, because this instance is
MISCONF Redis配置为保存RDB快照,但目前无法在磁盘上持久化。可能修改数据集的命令被禁用,因为该实例被配置为在RDB快照失败时报告错误(stop-write -on-bgsave-error选项)。请检查Redis的日志RDB错误的详细信息.
redis异常 Commands that may modify the data set are disabled, because this instance is
|
存储
什么是ABAP的STATE_READ_ACCESS
订阅专栏 我们做POC的时候,也经常见到STATE_READ_ACCESS, or STATE_WRITE_ACCESS in class CL_SEND_REQUEST_BCS, CL_CRM_PML_OS_HEAD(Mail form), 以CL_CRM_PML_OS_HEAD为例,这个CLASS主要是获取MAIL FORM TEMPLATE创建时的相关信息,例如CHANGED_AT, MAIL_USAGE等等,
107 0
什么是ABAP的STATE_READ_ACCESS
|
SQL Java 数据库连接
Session的save()、update()、merge()、lock()、saveOrUpdate()和persist()方法分别是做什么的?有什么区别?
Hibernate的对象有三种状态:瞬时态(transient)、持久态(persistent)和游离态(detached),如第135题中的图所示。瞬时态的实例可以通过调用save()、persist()或者saveOrUpdate()方法变成持久态;游离态的实例可以通过调用 update()、saveOrUpdate()、lock()或者replicate()变成持久态。
1265 0
|
SQL Oracle 关系型数据库
[20171120]理解v$session的state字段
[20171120]理解v$session的state字段(11G).txt --//https://blogs.oracle.com/database4cn/vsession-%e4%bd%a0%e7%9c%8b%e5%88%b0%e7%9a%84...
1251 0