During the MVP Summit we gathered in the hotel and had recorded one of the discussions that went there.
It was hilarious, and now it is online.
Listen to the interview war stories, and check the bit in the end.
During the MVP Summit we gathered in the hotel and had recorded one of the discussions that went there.
It was hilarious, and now it is online.
Listen to the interview war stories, and check the bit in the end.
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.
A few days ago, the BooLangStudio was announced in the Boo mailing list, bringing Boo support into Visual Studio.
Below you can see several screen shots. And you can find out more about it here.
This is a very promising move, especially since I soon have to write my tooling chapter :-)
Of course, this is still very early in the game, but it is good to see progress in this area again.
I am considering having a language that mandates tests. If you don't have a matching test for the code in question, it will refuse to run. If the tests fail, it will refuse to run. If the tests takes too long, they are considered failed and the code will refuse to run.
This certainly ensure that there would be test. It wouldn't ensure that they would be meaningful, however. That is fine by me. I am not interested in policy through enforcement, just gentle encouragement in the right direction.
The technical challenges of implementing such a system are nil. The implications on the workflow and ease of use for such a system are unknown. On the surface, checked exceptions are great. In practice, they are very cumbersome. This is why I am warning that I have only toyed with the idea, not implemented it.
Thoughts?
SciFi Inflation is the best term that I can use for this book series. It was engaging enough for me to go through all three books, but it bothered me enough to put a negative post about it.
Just about anything in those books is over-inflated. Interstellar travel times are measured in minutes, thousands of sentient races exists, sensors that can read the details of a ship from thirty light years away, an interstellar power has 300 million ships, etc.
This is like nails on board, highly disturbing for the flow of the story. And the story is good, it is just that those are beyond "wavehand physics away", I expect that. But I expect that to be done in a believable way.
Case in point, at one time the ship just blew up a few other ships, and it was hit with a bit of debris. The command that the Captain gibes? "Pilot, takes us half a light year out, I want to have a little time to respond if something like that happen again."
Does the author have any idea about how big a light year is?
Urgh!
Or, as I like to call them, yes another stupid language limitation. I did some work today on Rhino Mocks, and after being immersed for so long in DSL land, I had a rude awakening when I remembered just how much inflexible the C# language is.
Case in point, I have the following interface:
public interface IDuckPond { Duck GetDuckById(int id); Duck GetDuckByNameAndQuack(string name, Quack q); }
I want to get to a situation where the following will compile successfully:
IDuckPond pond = null;
pond.Stub( x => x.GetDuckById );
pond.Stub( x => x.GetDuckByNameAndQuack );
Any ideas?
Note that unlike my other challenges, I have no idea if this is possible. I am posting this after I got fed up with the limitations of the language.
when I am writing DSL, I keep hitting one pain point. The CLR naming conventions, which are more or less imprinted on my eyelids, are not really conductive to clear reading in a DSL.
Let us take these entities, and see what we get when we try to build a DSL from them:
The DSL is for defining business rules, and it looks like this:
when User.IsPreferred and Order.TotalCost > 1000: AddDiscountPrecentage 5 ApplyFreeShipping when not User.IsPreferred and Order.TotalCost > 1000: SuggestUpgradeToPreferred ApplyFreeShipping when User.IsNotPreferred and Order.TotalCost > 500: ApplyFreeShipping
The main problem with this style of writing is that it is visually condense. I can read it pretty much as easily as I read natural English, but anyone who is not a developer really have to make an effort, and even for me, trying to read ruby styled code is easier. Here is how this would look like when using the ruby style conventions:
when User.is_preferred and Order.total_cost > 1000:
add_discount_precentage 5
apply_free_shipping
when not User.is_preferred and Order.total_cost > 1000:
suggest_upgrade_to_preferred
apply_free_shipping
when User.is_not_preferred and Order.total_cost > 500:
apply_free_shipping
This is much easier to read, in my opinion. The problem is that I consider this extremely ugly.
Obviously a different solution is needed...
Wait a minute! Boo has an open compiler. Why not just change the way it handle references? And that is what I did:
///<summary> /// Allow to use underscore separated names, which will be translated to pascal case names. /// pascal_case -> PascalCase. /// All names that contains an underscores will go through this treatment. ///</summary> /// <example> /// You can enable this behavior using the following statement /// <code> /// compiler.Parameters.Pipeline /// .Replace(typeof (ProcessMethodBodiesWithDuckTyping), /// new ProcessMethodBodiesWithDslNamesAndDuckTyping()); /// </code> /// </example> public class ProcessMethodBodiesWithDslNamesAndDuckTyping : ProcessMethodBodiesWithDuckTyping { /// <summary> /// Called when we encounter a reference expression /// </summary> /// <param name="node">The node.</param> public override void OnReferenceExpression(ReferenceExpression node) { if(node.Name.Contains("_")) SetNodeNameToPascalCase(node); base.OnReferenceExpression(node); } /// <summary> /// Called when we encounters a member reference expression /// </summary> /// <param name="node">The node.</param> public override void OnMemberReferenceExpression(MemberReferenceExpression node) { if (node.Name.Contains("_")) SetNodeNameToPascalCase(node); base.OnMemberReferenceExpression(node); } /// <summary> /// Sets the node name to pascal case. /// </summary> /// <param name="node">The node.</param> private static void SetNodeNameToPascalCase(ReferenceExpression node) { string[] parts = node.Name.Split(new char[] { '_' },StringSplitOptions.RemoveEmptyEntries); StringBuilder name = new StringBuilder(); foreach (var part in parts) { name.Append(char.ToUpperInvariant(part[0])) .Append(part.Substring(1)); } node.Name = name.ToString(); } }
I love Boo, with cause.
Roughly speaking, a DSL is composed of the following parts:
It should come as no surprise that when we test it, we test each of those components individually. When the time comes to test a DSL, I have the following tests:
Testing the scripts
We have talked about how we can create tests for our DSL implementation, but we still haven’t talked about how we can actually test the DSL scripts themselves. Considering the typical scenarios for using a DSL (providing a policy, defining rules, making decisions, driving the application, etc), I don’t think anyone can argue against the need to have tests in place to verify that we actually do what we think we do.
In fact, because we usually use DSL as a way to define high level application behavior, there is an absolute need to be aware of what it is doing, and protect ourselves from accidental changes.
One of the more important things to remember when dealing with Boo based DSL is that the output of those DSL is just IL. This means that this output is subject to all the standard advantages and disadvantages of all other IL based languages.In this specific case, it means that we can just reference the resulting assembly and perform something write a test case directly against it.
In most cases, however, we can safely utilize the anonymous base class as a way to test the behavior of the scripts that we build. This allows us to have a nearly no-cost approach to building our tests. Let us see how we can test this piece of code:
specification @vacations:
requires @scheduling_work
requires @external_connections
specification @scheduling_work:
return # doesn't require anything
And we can test this with this code:
[Test] public void WhenUsingVacations_SchedulingWork_And_ExternalConnections_AreRequired() { QuoteGeneratorRule rule = dslFactory.Create<QuoteGeneratorRule>( @"Quotes/simple.boo", new RequirementsInformation(200, "vacations")); rule.Evaluate(); SystemModule module = rule.Modules[0]; Assert.AreEqual("vacations", module.Name); Assert.AreEqual(2, module.Requirements.Count); Assert.AreEqual("scheduling_work", module.Requirements[0]); Assert.AreEqual("external_connections", module.Requirements[1]); }
Or we can utilize a test DSL to do the same:
script "quotes/simple.boo"
with @vacations:
should_require @scheduling_work
should_require @external_connections
with @scheduling_work:
should_have_no_requirements
Note that creating a test DSL is only worth it if you expect to have a large number of DSL scripts of the tested language that you want to test.
How do I test the syntax in this DSL? HandleWith should translate to a method call with typeof(RoutingTestHandler) and a delegate.
import BDSLiB.Tests HandleWith RoutingTestHandler: lines = [] return NewOrderMessage( 15, "NewOrder", lines.ToArray(OrderLine) )
Well, I use interaction based testing, obviously. I find this test utterly fascinating, because it is fairly advance, in a roundabout sort of way, and yet it is so simple.
[Test] public void WillCallHandlesWithWithRouteTestHanlderWhenRouteCalled() { const IQuackFu msg = null; var mocks = new MockRepository(); var routing = dslFactory.Create<RoutingBase>(@"Routing\simple.boo"); var mockedRouting = (RoutingBase)mocks.PartialMock(routing.GetType()); Expect.Call(() => mockedRouting.HandleWith(null, null)) .Constraints(Is.Equal(typeof(RoutingTestHandler)), Is.Anything()); mocks.ReplayAll(); mockedRouting.Initialize(msg); mockedRouting.Route(); mocks.VerifyAll(); }
There are posts all the way to Dec 02, 2024