Rhino Service BusSaga and State
In a messaging system, a saga orchestrate a set of messages. The main benefit of using a saga is that it allows us to manage the interaction in a stateful manner (easy to think and reason about) while actually working in a distributed and asynchronous environment.
In Rhino Service Bus, I built the notion of sagas from the beginning. And I initially went with the approach that mix the saga’s behavior and the saga state in the same class. That did not turn out so well. While it works for simple matters, anything of sufficient complexity started to bring issue. Mainly, it was an issue of managing dependencies and managing state. It is possible to get this worked out, but I decided to follow Udi’s footsteps and create an explicit separation between the two.
Here is how it works, we have the state class:
1: public class BaristaState2: {
3: public bool DrinkIsReady { get; set; }4:
5: public bool GotPayment { get; set; }6:
7: public string Drink { get; set; }8: }
This is just a standard class, nothing special here. But here is the actual saga class. This contains the behavior for the saga, with the state being maintained in the state class.
1: public class BaristaSaga :2: ISaga<BaristaState>,
3: InitiatedBy<PrepareDrink>,
4: Orchestrates<PaymentComplete>
5: {
6: private readonly IServiceBus bus;7:
8: public BaristaState State { get; set; }9:
10: public Guid Id { get; set; }11:
12: public bool IsCompleted { get; set; }13:
14: public BaristaSaga(IServiceBus bus)15: {
16: this.bus = bus;17: State = new BaristaState();18: }
19:
20: public void Consume(PrepareDrink message)21: {
22: State.Drink = message.DrinkName;
23:
24: for (int i = 0; i < 10; i++)25: {
26: Console.WriteLine("Barista: preparing drink: " + drink);27: Thread.Sleep(500);
28: }
29: State.DrinkIsReady = true;30: SubmitOrderIfDone();
31: }
32:
33: public void Consume(PaymentComplete message)34: {
35: Console.WriteLine("Barista: got payment notification");36: State.GotPayment = true;37: SubmitOrderIfDone();
38: }
39:
40: private void SubmitOrderIfDone()41: {
42: if (State.GotPayment && State.DrinkIsReady)43: {
44: Console.WriteLine("Barista: drink is ready");45: bus.Publish(new DrinkReady46: {
47: CorrelationId = Id,
48: Drink = State.Drink
49: });
50: IsCompleted = true;51: }
52: }
53: }
There are a few things to notice in here. The saga class is a standard component, we use DI to inject dependencies to the class so it can perform whatever it is that it wants. The state property is used to maintain the state of the saga between message invocations.
This results in a simpler design for some parts of the code, and I think that overall it is a very simple model to talk and reason about.
More posts in "Rhino Service Bus" series:
- (08 Aug 2009) DHT Saga Sate Persisters Options
- (21 Jan 2009) Concurrency Violations are Business Logic
- (19 Jan 2009) Concurrency in a distributed world
- (16 Jan 2009) Saga and State
- (15 Jan 2009) Field Level Security
- (14 Jan 2009) Understanding a Distributed System
- (14 Jan 2009) The Starbucks example
- (14 Jan 2009) Locality and Independence
- (14 Jan 2009) Managing Timeouts
Comments
What you describe is nice except it isn't a Saga it is more of a workflow. The notion of Saga which is originated from databases relates to the overall coordination of state between the different services - or the context for the whole business process.
In the coffee shop example you use that would be the whole "transaction" from the point the customer orders her coffee until she either gets it or the transaction is canceled (e.g. it took too long and the customer leaves or the coffee shop is out of milk etc.)
Unlike database (or distributed) transaction when/if a saga is aborted the different component of the system might not return to their previous state e.g. if the customer complains that the coffee is not good and gets her money back. the milk is not separated back from the coffee beans and returned to the bottle - rather the coffee cup goes to the trash.
Workflow is one strategy a service can take to handle the long running interaction within a saga. In your case the BristaSaga class (which I think should be BristaWF) orchestrate the internal state transitions depending on the different messages that arrive within the saga. In your case you have a hardcoded workflow - but it is also possible to use a workflow engine for the job.
By the way, in the above example you could also use a statemachine instead of a WF to manage the process
You can read more on Sagas here www.rgoarchitects.com/Files/SOAPatterns/Saga.pdf
Arnon,
You are correct, but WF is a very loaded term.
See this post for more details:
www.udidahan.com/.../no-more-workflow-for-nserv...
Arnon: I'm not 100% sure of how you distinguish a Saga from a Workflow, could you elaborate some more on this?
A Saga involves a number of underlying workflows?
A Saga might as well contain a number of underlying Sagas?
Isn't it just a question of at what level it is initiated?
If a Saga should represent the whole transaction / business process, then who should handle it? Couldn't it be implemented as a Saga, exactly as Ayende describes it, by the initiating service (in this case the ordering)?, which then also is given the responsibility to handle restoring the total state etc of underlying/involved services if the transaction is aborted? The possibility to restore state does of course depend on what the specific Saga is handling, some processes might not be able to "rollback" completely, it's rather a question of rolling back all involved parties to a known/acceptable state.
We have implemented the same kind of functionality in what we call processes. Each service can have multiple running processes of the same kind, a new process instance is initiated by a triggering message, whereby it is registered with a process manager within the service. The process manager handles the routing of incoming messages to the correct process. An ongoing process involves communication with other services, exactly as described in
"5.4.2 The Solution" in the Saga paper you refer to. When a process instance is completed it is unregistered from the process manager.
The concept of processes and process manager in our system comes from http://www.eaipatterns.com/ProcessManager.html
Why are you using a GUID as an identifier instead of a long etc.? Isn't there a chance that a duplicate could be generated? I know it's about a trillion to one but that's still not good enough in my opinion.
James,
There is no chance for generating duplicate guids. Check the spec for that.
Guids also allow you to generate them without having a single source that delegate identifiers.
Not having know what a saga was before this, I came to the conclusion that it was an orchestator of state changes of a service/object? on a bus with multiple interactions. I'm behind the curve in this area.
I'm thinking I should subscribe to Udi's blog, but I cant get to his website right now. Says 'failed to connect'.
@Kristofer
Saga is similar to a transaction in the sense that it provides a shared context for an attempt to get a distributed consensus
Unlike a transaction which insures ACID properties. Sagas are not.
The concept of dissipating that shared context, having each party (service) affect whether the saga should be aborted or successful etc. is what I call a saga.
When a saga is aborted the only thing the coordinator can do is pass the status to the participants. Each of the services is responsible to do its best effort to handle the abort (either by rolling back, compensation or whatever)
Workflow is another thing altogether. which keeps a context between calls and means externalizing the decisions on the logic flow from the business logic (usually with a workflow engine). You can use workflows within a service (a pattern I call workflodize) or you can use them externally (a pattern I call orchestrated choreography e.g. BPM)
You can use either form of workflow to support the implementation of a saga but you can also implement sagas without workflows.
In our system we use an "event broker" (see www.rgoarchitects.com/.../EventingInWCF.aspx) the event broker infrastructure dissipates the saga context when you raise a saga event. A service that initialized a saga (by sending the first event) can choose to close the saga (commit) or abort it. etc. We don't currently have any workflow driven services (but some of them use a state machine as an alternative)
I think the term Saga does not describe Ayende's class since the "barista" is just on of the participants in the saga there are other participants.
Arnon
Ayende,
Are BaristaSaga objects instantiated per message? If so, can two different instances be consuming different messages concurrently?
The reason I ask is because it looks like handling the PrepareDrink message could take some time. Is it possible that a PaymentComplete message could come in before the PrepareDrink message is finished being handled?
If the two instances of BaristaSaga have their own instance of BaristaState, I can see the GotPayment value set by handling the PaymentComplete message getting lost.
If the two instances of BaristaSaga share the same instance of BaristaState, do I now have to worry about synchronizing changes to the state across all of the sagas? Also, wouldn't this prevent having multiple barista "servers" handling messages since they wouldn't be able to share instances across processes/machines.
Thanks.
Jason,
A single saga may run concurrently. Ideally, it is possible to run the saga in parallel, usually because this is an idempotent messages.
Or, the saga persister takes care of locking.
Ayende,
I found a test in Rhino.ServiceBus called Can_send_several_messaged_to_same_instance_of_saga_entity so I assume you mean that a single instance can handle multiple messages concurrently and not separate instances.
That takes care of the problems I saw with two saga instances using different instances of the state object.
Thanks.
Jason,
Several instances vs. single instance.
Saga instance is the same thing as the saga state. It does manifest itself as several instances, mind you.
But conceptually it is the same thing, with the same state.
My understanding of sagas has always been from the perspective of long-lived transactions (LLT) and typically those running / orchestrated across multiple enterprises within some supply / value chain. That the [various] transactions and compensating transactions within each enterprise involved in an LLT are 'bundled' into a saga which is bound to an LLT by some mechanism.
The idea essentially being that each saga is responsible for maintaining the consistency of their [component] state thereby contributing to the consistency of the parent LLT state. But after all this discussion I can now see this saga pattern / notion exhibits somewhat of a 'fractal' characteristic relative to its application at various scales both within and across enterprises.
Healy,
That is how I think about this as well
Aye, the problem with names.
Technically, we're talking about ACTA and not SAGA. Sagas require that every action has a compensating action. Actas don't.
Next, long-lived transactions have a DB up perspective on the world. When writing messaging logic, we have a communications down perspective.
Also, in a DB up perspective, if you assume a single DB (like many do), you run into problems mapping "sagas" onto autonomous business services (SOA).
In short, I needed a name to distinguish a set of patterns from other such sets, communicating core differentiating factors. Since sagas lead so nicely from state management to messaging, I chose them, even though acta would have been more correct, for the "intention-revealing name".
The problem with names...
What is ACTA?
Comment preview