Simple State Machine
Nathan has posted Simple State Machine to CodePlex, it is the first project that I am aware of that uses Rhino DSL and the techniques that I am talking about in the book.
What is impressive about this is the level of professionalism that is involved in the project. It is a full scale DSL, with all the supporting infrastructure. I spent half an hour or so going through the entire thing, and I am impressed.
Put simply, this is how I think state based work flows should be defined. I could easily see myself extending this a bit to add persistence support & integration with NServiceBus, and be done with it.
Like most state machines, it has the ideas of states, events that can cause the state to be changed, and legal transitions from state to state. You can define tasks which will be executed upon changing a state, or upon entering / leaving a certain state.
Enough talking, let us look at a reasonably complex work flow:
workflow "Order Lifecycle"
#Event & State Identifier Targets.
#This section controls which Types will be used
#to resolve Event or State names into strongly typed CLR objects.
#--------------------------------------------------------
state_identifier_target @OrderStatus
event_identifier_target @OrderEvents#Global Actions
#--------------------------------------------------------
on_change_state @WriteToHistory, "on_change_state"
on_workflow_start @WriteToHistory, "on_workflow_start"
on_workflow_complete @WriteToHistory, "on_workflow_complete"#Event Definitions
#--------------------------------------------------------
define_event @OrderPlaced
define_event @CreditCardApproved
define_event @CreditCardDenied
define_event @OrderCancelledByCustomer
define_event @OutOfStock
define_event @OrderStocked
define_event @OrderShipped
define_event @OrderReceived
define_event @OrderLost#State & Transition Definitions
#--------------------------------------------------------
state @AwaitingOrder:
when @OrderPlaced >> @AwaitingPaymentstate @AwaitingPayment:
when @CreditCardApproved >> @AwaitingShipment
when @CreditCardDenied >> @OrderCancelled
when @OrderCancelledByCustomer >> @OrderCancelledstate @AwaitingShipment:
when @OrderCancelledByCustomer >> @OrderCancelled
when @OutOfStock >> @OnBackorder
when @OrderShipped >> @InTransit#Individual states can define transition events as well
on_enter_state @WriteToHistory, "on_enter_state(AwaitingShipment)"state @OnBackorder:
when @OrderCancelledByCustomer >> @OrderCancelled
when @OrderStocked >> @AwaitingShipmentstate @InTransit:
when @OrderReceived >> @OrderComplete
when @OrderLost >> @AwaitingShipment#NOTE: State definitions without any transitions will cause
#the state machine to Complete when they are reached.
#------------------------------------------------------------
state @OrderComplete
state @OrderCancelled
Here is the demo application UI, for the order processing life cycle:
As I said, impressive.
Comments
This has me wondering if an internal DSL would be a useful component for an e-commerce shopping cart? Specifically use the DSL to calculate discounts between dependent products (buy one get one 50% off etc) and for promotion codes (promo code is good for product X, Y, Z only).
This would certainly be more flexible, but I wonder if it would be more difficult to maintain than an engine that is statically defined and driven by data. It seems some middle ground for this would be preferable, data driven, yet write the rules not in C#, but the DSL?
Also, how does deployment come into play? It would be preferable to support an XCopy style of deployment for the DSL script since I'm going to assume it would be constantly updated - several times a week.
Shawn,
I would tend to say that there are several fixed actions in the system, and leave the policy to the DSL.
The DSL can be just dropped into a directory, and immediately take affect.
I keep asking myself, why create a language for every little thing?
if artist is not satisfied by any one language he should make his own.
anyway, nice work. since i battle with WF (well our company's hack of WF) i can appreciate such project. All it would need now are persistence & security.
I have been thinking about persistence, but I'm not sure what there is to persist. In WF, the object graph of the whole state machine is persisted for each instance, but i'm sure that is useful in this case. The only thing I can think of to persist other than the business objects (which I presume are persisted independently) is the current state, and I figured that generally just be another property of the domain.
What do you think about this state machine implemented in ECO:
http://www.capableobjects.com/downloads/docs/vs/VS_04_-_Creating_a_state_machine.pdf
I stopped reading when I saw how many steps you need to perform there
It might not be so obvious because state machine in ECO is part of the UML model and requires some background in ECO Framework.
But in a short:
You create a model.
You define an attribute of a class that keeps current state.
You define (in UML) states, transitions, guards, effects etc.
You use state it in code. Like so:
order.Approve() - changes statues to Approved (if guards allow doing so).
The idea: all is defined on the UML diagram and uses Model Driven Architecture.
I'm using Rhino DSL in a migrations open source project that a friend and I are working on. I made a blog post about the first thing I used Rhino DSL to do: manage the configuration of different environments in a settings file.
http://nathan.whiteboard-it.com/archive/2008/06/24/settings-dsl.aspx
Nice, thanks for letting me know
Comment preview