【转】CQRS: Sagas with Event Sourcing (Part I of II)
For starters, what is a saga? A saga is a “long-lived business transaction or process”. Okay, so what does that mean? Well, first of all, the “long-lived” part doesn’t have to mean hours, days, or even weeks—it could literally mean something as short as a few seconds. The amount of time is not the important part. It’s the fact that the “transaction”, business process, or activity spans more than one message. Udi has written several times on sagas. His articles are always worth reading. The foundational theory behind sagas is to avoid the use of blocking (and locking) transactions across lots of resources. Locks are one of the primary enemies of scalability.
Late last week I was communicating with Rinat Abdullin on the CQRS Google Group about sagas and explaining how they might be implemented using event sourcing. I have spoken of this to others in the past as well. In the aforementioned thread, I didn’t go into much detail about the “how”. Hence this post.
Perhaps we should explain why a saga is even necessary when we have aggregates and domain objects that manage state. Aggregates are great business objects that wrap a lot of business complexity. The problem with aggregates is that they only care about their little part of the universe. Sagas, on the other hand, are coordinating objects. They listen for what’s happening and they tell other objects to take appropriate action. Picture them as choreographers—let’s try and avoid the words orchestra and orchestration altogether. In essence, sagas listen for events and instruct other parts of the system to perform tasks based upon the events. This is juxtaposed to aggregates which are told to do something and then alert the world that they performed some action. This could be generalized into the following: Sagas listen to events and dispatch commands while aggregates receive commands and publish events.
Sagas manage process. They contain business behavior, but only in the form of process. This is a critical point. Sagas, in their purest form, don’t contain business logic. During Greg Young’s workshop, he hammered this one pretty hard. Most programmers think “logic” and aren’t used to thinking “process”. Let me put it this way. If you have a saga with “if else” statements, you’ve got logic. Process is best implemented using a state machine. The state machine we use to manage process within each saga is called Stateless by Nicholas Blumhardt,the creator of Autofac.
In a message-oriented world, there are two fundamental problems—ordering and duplicates. These are related to message infrastructure and its corresponding guarantees. Sagas are able to act as a kind of firewall to the outside. They encapsulate the mess and help shield the domain from much of the nasty details of reality.
With message ordering, let’s consider how a typical shipping or fulfillment department might utilize sagas to help carry out their responsibilities. Let’s imagine that fulfillment is not to ship the goods until payment has been received. As messages can arrive in any order, what would happen if “PaymentRecieved” was the first message to arrive? In that situation, fulfillment wouldn’t even have a clue what to ship because they haven’t even been made aware of a corresponding order. With sagas we can easily outline the various state transitions and then dispatch commands only when the appropriate conditions are met—such as the receipt of both the OrderReceived and PaymentReceived events, regardless of the sequence in which they arrive.
Looking at duplicates and idempotency with a state machine-based saga, it’s not hard to see that regardless of the number of times a message is received, it will only cause a state transition once. Think about it like a DVD player. You hit stop and then stop again. Not matter how many times you press stop, the state transition only occurs once. So it is with sagas. A message can be received multiple times but will only cause the desired state transition once. This makes duplicate messages naturally idempotent. Sweet.
The other really great thing about sagas is that they understand the concept of time. For example, let’s suppose we wanted to notify first-time customers of a discount if they didn’t complete checkout within 48 hours. The saga could implement a timeout by scheduling a timeout message to be sent back to the saga after 48 hours. When the timeout message was received 48 hours later, the saga would attempt to perform the state transition related to the timeout. If the customer did something in the last 48 hours, the timeout message wouldn’t cause a state transition. But if the person had not yet completed checkout, the saga could dispatch a message to the appropriate component to take corresponding action. This component would decide the “who” and the “how”. All the saga did was to tell the component: “There hasn’t been any activity, notify the customer.” The component would then decide whether or not to notify based upon business logic, e.g. is this a first-time customer, etc.
To Be Continued…
Now that we’ve explained why sagas are important and how state machines can help us implement a saga which deals with the complexity of duplicate messages, out-of-order messages, and timeout messages, in Part II of this article, we’ll look at some of the details of how to use event sourcing to create testable, robust, yet fluid sagas.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)