<CharlieDigital/> Programming, Politics, and uhh…pineapples

30Jan/14Off

Stateless vs. Appccelerate.StateMachine

Posted by Charles Chen

I've been investigating workflow engines lately (specifically, state machine libraries) and it's come down to Stateless vs. Appcelerate.StateMachine.

First, a digression on why I'm avoiding Windows Workflow Foundation (WF).  It's true that there's tons of documentation, support from Microsoft, perhaps lots of experienced developers, but I count myself as one among many who feel burned by our experience with WF.  I've used the first release of WF on FirstPoint and mostly ended up regretting it for the poor performance and complexity (making it hard for new developers to pick it up).  I've also used the first release on various other SharePoint projects since then on top of Nintex and, again, it's been dogged by hard to trace bugs, poor performance, and stuff that's generally cumbersome (like deployment, for instance).

Microsoft seems to have addressed a lot of these issues by totally rewriting WF in .NET 4.0, but this in and of itself raises a serious question: how stable is the framework?  Sure, Microsoft provided a migration path from legacy WFs to new WFs, but it's still a headache.  Aside from that, even just looking at the APIs, it's clear that it's extremely heavy and does nothing to alleviate the poor practices which are possible with WF.

Thus enters my search for alternatives including Amazon Simple Workflow.  In the end, I've narrowed it down to Stateless and Appccelerate.StateMachine.

To test the intuitiveness, ease of use, and performance, I wrote a simple example with both of them.  I modeled the phone example from the Stateless documentation and ran it in a loop of 1,000,000.

The loop looks like so:

internal class Program
{
    private static void Main(string[] args)
    {
        var stopwatch = new Stopwatch();

        stopwatch.Start();

        for (int i = 0; i < 1000000; i++)
        {
            var phone = new Phone();

            phone.CallDialed();
            phone.CallConnected();
            phone.PlacedOnHold();
            phone.TakenOffHold();
            phone.HungUp();
        }

        stopwatch.Stop();

        Console.Out.WriteLine(stopwatch.ElapsedMilliseconds);
    }
}

The implementation of the phone in Stateless looks like so (common parts omitted for brevity):

namespace StatelessTest
{
    public class Phone
    {
        private readonly StateMachine<State, Trigger> _phone = new StateMachine<State, Trigger>(State.OffHook);

        private readonly Stopwatch _stopwatch = new Stopwatch();

        public Phone()
        {
            _phone.Configure(State.OffHook)
                  .Permit(Trigger.CallDialed, State.Ringing);

            _phone.Configure(State.Ringing)
                  .Permit(Trigger.HungUp, State.OffHook)
                  .Permit(Trigger.CallConnected, State.Connected);

            _phone.Configure(State.Connected)
                  .OnEntry(StartCallTimer)
                  .OnExit(StopCallTimer)
                  .Permit(Trigger.LeftMessage, State.OffHook)
                  .Permit(Trigger.HungUp, State.OffHook)
                  .Permit(Trigger.PlacedOnHold, State.OnHold);

            _phone.Configure(State.OnHold)
                  .SubstateOf(State.Connected)
                  .Permit(Trigger.TakenOffHold, State.Connected)
                  .Permit(Trigger.HungUp, State.OffHook)
                  .Permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);
        }

        private void StartCallTimer()
        {
            _stopwatch.Start();
        }

        private void StopCallTimer()
        {
            _stopwatch.Stop();
        }

        public void CallDialed()
        {
            _phone.Fire(Trigger.CallDialed);

            //Console.Out.WriteLine("Dialed");
        }

        public void CallConnected()
        {
            _phone.Fire(Trigger.CallConnected);

            //Console.Out.WriteLine("Connected");
        }

        public void PlacedOnHold()
        {
            _phone.Fire(Trigger.PlacedOnHold);

            //Console.Out.WriteLine("On Hold");
        }

        public void TakenOffHold()
        {
            _phone.Fire(Trigger.TakenOffHold);

            //Console.Out.WriteLine("Off Hold");
        }

        public void HungUp()
        {
            _phone.Fire(Trigger.HungUp);

            //Console.Out.WriteLine("Hung Up");
        }
    }

    // Define enums here
}

The implementation of the phone in Appccelerate.StateMachine looks like so:

namespace StateMachineTest
{
    public class Phone
    {
        private readonly PassiveStateMachine<State, Trigger> _phone = new PassiveStateMachine<State, Trigger>();

        private readonly Stopwatch _stopwatch = new Stopwatch();

        public Phone()
        {
            _phone.In(State.OffHook)
                  .On(Trigger.CallDialed).Goto(State.Ringing);

            _phone.In(State.Ringing)
                  .On(Trigger.HungUp).Goto(State.OffHook)
                  .On(Trigger.CallConnected).Goto(State.Connected);

            _phone.In(State.Connected)
                  .ExecuteOnEntry(StartCallTimer)
                  .ExecuteOnExit(StopCallTimer)
                  .On(Trigger.LeftMessage).Goto(State.OffHook)
                  .On(Trigger.HungUp).Goto(State.OffHook)
                  .On(Trigger.PlacedOnHold).Goto(State.OnHold);

            _phone.DefineHierarchyOn(State.Connected)
                  .WithHistoryType(HistoryType.None)
                  .WithInitialSubState(State.OnHold);

            _phone.In(State.OnHold)
                  .On(Trigger.TakenOffHold).Goto(State.Connected)
                  .On(Trigger.HungUp).Goto(State.OffHook)
                  .On(Trigger.PhoneHurledAgainstWall).Goto(State.PhoneDestroyed);
        }

        private void StartCallTimer()
        {
            _stopwatch.Start();
        }

        private void StopCallTimer()
        {
            _stopwatch.Stop();
        }

        public void CallDialed()
        {
            _phone.Fire(Trigger.CallDialed);

            //Console.Out.WriteLine("Dialed");
        }

        public void CallConnected()
        {
            _phone.Fire(Trigger.CallConnected);

            //Console.Out.WriteLine("Connected");
        }

        public void PlacedOnHold()
        {
            _phone.Fire(Trigger.PlacedOnHold);

            //Console.Out.WriteLine("On Hold");
        }

        public void TakenOffHold()
        {
            _phone.Fire(Trigger.TakenOffHold);

            //Console.Out.WriteLine("Off Hold");
        }

        public void HungUp()
        {
            _phone.Fire(Trigger.HungUp);

            //Console.Out.WriteLine("Hung Up");
        }
    }

    // Define enums here
}

And here are the results:

2014-01-30_095250

Here are my observations:

  1. Stateless is about 4x faster than Appccelerate.StateMachine
  2. Stateless syntax is a little bit more intuitive for creating hierarchies
  3. Appccelerate.StateMachine documentation is much more thorough and much more complete than Stateless
  4. But Stateless has more downloads (thus more users) and more presence on StackOverflow (should you need it)
  5. Appccelerate.StateMachine has some cool features around logging and extensibility, but quite honestly, I don't think they are needed given that if you use a composite pattern around your state machine, you can really manage that yourself much more concisely.
  6. The external persistence infrastructure of Stateless is simple, but not very intuitive or well documented
  7. The Appccelerate.StateMachine Nuget package did not work with the package manager console and Visual Studio 2012, requiring me to manually download and compile the source (which I don't mind, but raises some doubt)

All said and done, I think we will use Stateless as it is a very barebones and highly performant state machine, which is perfect for our needs.  Look for more Stateless posts in the future!

Attached is the sample code: StateMachineTest

Filed under: Dev, Uncategorized, WF No Comments
7Apr/08Off

On The Shortcomings of WF

Posted by Charles Chen

An article in the April issue of MSDN magazine features an interview with Bjarne Stroustrup, the man that invented C++.  There's an interesting quote that quite eloquently and succintly sums up my distaste for Windows Workflow Foundation and BizTalk (at least the versions that I've worked with).


When asked about his feelings on IDEs and how they should (or should not) support languages and the role of the IDE in software development, Stroustrup responsed:



I'm not a heavy IDE user.  I appreciate a responsive IDE editor with understanding of my language, but I also want to be able to work without an IDE.  My desire for portability of code plays a role here.  With C++, I want to be able to understand my system from just the source code in the source files.  I actively dislike IDE mechanisms that involve trasformations or generation that cannot be represented as code fit for human consumption.


While WF does certainly generate code in the background, it's not what I would consider "code fit for human consumption".  It's messy and aesthetically unpleasing (so far as code goes).  I always inevitably end up spending quite a bit of time cleaning up the mess left by the code generation engine; there's an odd disconnect between the cleanly delineated visual design of the workflow and the mess of code that gets generated just for a dependency property.

Filed under: QOTD, WF No Comments
30May/07Off

Correlation Across Workflow Instances

Posted by Charles Chen

One of the problems that I've been working on solving recently centered around correlation in workflows.  In simple terms, where a workflow may produce parallel execution paths, correlation allows the runtime to route events to the right workflow execution path.


In every example that I came across on MSDN and online, the sample cases all assumed intra-workflow correlation as opposed to inter-workflow correlation involving "parent-child" workflow instances.


I posted the initial query - and the solution I used - on this subject over at the MSDN forums:



The trick, as it turns out, is that the CorrelationToken must be initialized in the parent workflow.  To accomplish this in the sample, I made a frivolous call to an InitializeCorrelation method on my service interface using a CallExternalMethodActivity, which was marked with the CorrelationInitializer attribute.  This activity executes right before the InvokeWorkflowActivity.


[Serializable]
public class WorkflowCommunicationServiceArgs : ExternalDataEventArgs {
private string key;

public string Key {
get { return key; }
set { key = value; }
}

public WorkflowCommunicationServiceArgs(Guid instanceId, string key)
:
base(instanceId) {
this.key = key;
}
}

[ExternalDataExchange]
[CorrelationParameter("key")]
public interface IWorkflowCommunicationService {
[CorrelationAlias("key", "e.Key")]
event EventHandler<WorkflowCommunicationServiceArgs> ChildCompleted;

[CorrelationInitializer]
void InitializeCorrelation(string key);

[CorrelationInitializer]
void OnChildCompleted(string key);
}

public class WorkflowCommunicationService : IWorkflowCommunicationService {
#region IWorkflowCommunicationService Members

public event EventHandler<WorkflowCommunicationServiceArgs> ChildCompleted;

public void InitializeCorrelation(string key) {
Console.Out.WriteLine("Key -> [{0}]", key);
}

public void OnChildCompleted(string key) {
MessageBox.Show(string.Format("Completed child; Key = [{0}]", key));

RaiseChildCompletedEvent(key);
}

public void RaiseChildCompletedEvent(string key) {
if (ChildCompleted != null) {
ChildCompleted(
null
,
new WorkflowCommunicationServiceArgs(
new
Guid("5D1667BF-61F6-4bf3-81C0-E70CBE15D2EF"),
key
)
);

}
}

#endregion
}


In essence, the idea is to have two correlation initializers: one utilized by the parent/outer workflow and one utilized by the child/inner workflow when signaling back to the parent.  It seems kind of counterintuitive to require two initializations...I'm still not sure how this is working under the covers, but it works :-)


The working example can be downloaded from: http://www.charliedigital.com/junk/CorrelationTest.Working.zip

Filed under: .Net, WF No Comments
28Feb/07Off

WF Passivation Services Issues

Posted by Charles Chen

If you're expecting the WF (Windows Workflow) passivation services to work
out of the box, well, you're half right. 

From one of my workflows, I've been able to trap that an error was being
thrown at some point during execution, but it wasn't clear what the
actual error was or what was causing it.  I knew it had to do with passivation
services since everything was just fine and dandy once I removed the passivation
services from the equation.

From the runtime level, I received the following not-so-helpful error
message:

System.InvalidOperationException:
Workflow with id "[some-guid]" not found in state persistence store.

Just on a whim, I attached SQL Server Profiler to the database instance where
the persistence store is attached to track the queries and lo-and-behold, the
mystery culprit surfaced:

declare @p10 int
set
@p10=0
declare @p11 uniqueidentifier
set @p11=NULL
exec
InsertInstanceState
@uidInstanceID='2686AB8F-35A4-49C3-AED8-9F239D00D3AC',@state=0x,@status=3,@unlocked=1,@blocked=0,@info=N'Type

''Zorch.Alta.EastCastle.Workflows.DataProviders.Implementation.MockStepProvider''
in Assembly ''Zorch.Alta.EastCastle.Workflows, Version=1.0.0.0,

Culture=neutral, PublicKeyToken=null'' is not marked as serializable. Type
''Zorch.Alta.EastCastle.Workflows.DataProviders.Implementation.MockStepProvider''
in
Assembly ''Zorch.Alta.EastCastle.Workflows, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null'' is not marked as
serializable.',@ownedUntil=''9999-12-31

23:59:59:997'',@ownerID=default,@nextTimer=''9999-12-31
23:59:59:997'',@result=@p10 output,@currentOwnerID=@p11 output
select @p10,
@p11

Well, yeah, duh!  It seems so obvious after the fact.  So the moral of the
story is to make sure that all of your classes are marked with serializable :P
I'm just not sure why they decided to place the error message in the procedure
call to insert the instance state.  Why not actually raise an error to the
workflow runtime that a class was not serializable?

Filed under: .Net, WF No Comments
1Feb/07Off

My Thoughts on WF

Posted by Charles Chen

As I commented in a post on Paul Andrew's blog regarding what WF is and what it is not:

As I've been working with WF these last few weeks, I've come to form another view of WF and what it means for developers: in a way, it *forces* developers away from some bad design practices since a WF itself has no GUI and no visual user interface.
To expand on that, it forces developers who would otherwise readily hash their ASP.Net and WinForms GUI code in with the business logic to think in  a manner that separates the core business logic from the visual interface code.
The number of developers that still write monolithic projects that contain the UI and business logic is still all too high.  In studying from simplified working examples in text books, in MSDN documentation, and various articles one finds online, many developers do not gain a good understanding of how to separate the concerns of their code.
The downsides of this approach are immediately apparent after a little exposure to alternative design methods, yet for many developers, they simply don't see the light and continue to write their data access code right into the Click event of a Button.
I see WF as a way of moving developers away from this model by encouraging developers to encapsulate code in a WF program (a unit of business logic) or an activity

I would like to add to that, that in the end, WF is still "just code" (maybe my perspective is skewed after having written my own workflow engine). Don't be fooled by the drag and drop UI, the fancy terms (for example, tracking is nothing more than glorified logging that any learned developer could have easily implemented with log4net, a database appender, and a well defined logging policy), and the hype train in general.  When it comes down to it, for any significantly complex application or architecture, it still requires writing of much of the same code that you had to write before (and in some cases, you may end up writing more code to build a functionally equivalent solution) not to mention that whenever you adopt such a framework, you must learn the little nuiances and how the product architects intended for you to complete a certain task within the guidelines of the framework.

Just to share my take on it :-)

Certainly, it is no "silver bullet", as Brooks would say, but it's definitely a step in the right direction. I am quite curious as to the actual adoption rate that we will see with WF in the coming months as it offers no immediate benefit (until perhaps we see a market for custom activities much like we have today for custom controls for the UI).  To most, it simply won't offer anything compelling and to top it off, it will require additional training to implement a solution using WF.

For me, personally, it's been fun picking it apart and seeing how the minds at Microsoft implemented the same functionality I implemented in my own workflow engine that I wrote for our project at Zorch (while certainly less polished, I like to think I was heading in the same direction...and in some cases, I wish WF offered the same features as I had implemented myself (that was my ego speaking ;-))).

Filed under: .Net, Dev, WF No Comments
2Jan/07Off

To Follow Up…

Posted by Charles Chen

So it turns out that Paul Andrew, the technical product manager of WF linked to my very abstract review of Essential Windows Workflow Foundation.  For those that haven't been following, I wrote an awesome review of the book on Amazon and sent it in (or so I thought!) but I haven't seen it show up on the page yet :-S

So for the sake of others considering this book, I'll review it again.

There are two types of developers that you will come across: those that are content to make things work and solve business solutions from the top down and those that want to understand the underlying technologies to build solutions from the bottom up.  This is not so much a discussion on "architecting", mind you, but rather a discussion on how different developers approach tools and frameworks.  Not that one is better than the other, but each brings a different approach and each has different preferences with regards to technical resources.

If you fall into the former and you are mostly concerned with your immediate business solutions (learn top-down) and you learn best by doing, then this book is not for you.  The contents of this book are not so much concerned with how to solve business solutions with WF nor is it a cookbook for WF solutions.  This book doesn't have many pictures of the design surface and doesn't concern itself much with building workflows in the designer.  It is an introductory guide to the underpinnings of the WF framework.  It delves into the workings of WF and the principles behind many of the advanced concepts that may not necessarily crop up in most use cases.

If you fall into the latter category of developers (learn bottom-up) and you learn best by first understanding the tool and the design principles of the tool, then this book will be a good starting point to understanding WF.  In fact, the first chapter of the book walks through a sample implementation of a simple "workflow engine" and covers the principles that drive the implementation of the WF framework.  The chapter presents a "If I were writing a workflow engine, how would I write it?" scenario (if that makes any sense).  This outline then serves as a basis for understanding the function and design of the WF engine.

The book provides insight into advanced concepts and does a fairly good job of it (examples are simple and straightforward - oddly, not all of the code is provided online), but it seems to come up short in the last chapter, where the authors just kind of jumbled everything that they didn't cover into one chapter.  It almost seems like the authors were working on a 10 or 12 chapter book but were forced to cram the remaining topics (unfinished) into chapter 8.  In short, the book seems unfinished.

This book is not for everyone.  It does assume some familiarity with higher level .Net framework concepts that many developers from the ASP.Net world may not have experience with (specifically, threading and asynchronous method calls) so for that reason, I would recommend a companion book: Pro C# 2005 and the .NET 2.0 Platform, Third Edition.  As a general note, I've found that the "Microsoft .Net Development Series" of books from Addison Wesley typically does not cater to the first class of developer as the titles tend to be architecture and framework oriented as opposed to solution and implementation oriented.

In summary: 4 out of 5 stars; a worthy book that deserves a space on your bookshelf if you plan on doing WF.

Filed under: Book Review, Dev, WF No Comments
29Dec/06Off

Happy Holidays!

Posted by Charles Chen

Yeah, it's been a looooong time.

First of all, happy holidays to anyone reading this (hi Mom!).

Second, I know, no one likes to read excuses on blogs :P but I swear, I've been super busy and that fractured finger made extracurricular typing difficult.

One of the more exciting things that happened this week is that we switched the whole family to Sprint.  It's one of those weird things...no one I know aside from my boss has Sprint.  No one my sister knows has Sprint.  So it was kind of scary to switch; when you think about it, it's really a huge commitment!  But the allure of Sprint is in their rock bottom prices compared to the other major carriers and also the $15 for unlimited data access (I didn't believe my boss when he mentioned this, but it's true!).

Yeah, it's been a pretty freakin' long time since I've switched carriers, but for the price I was paying, Cingular just wasn't cutting it with the services provided.  For less than what we were paying for three phones (well, we did have 900 more minutes), we now have four phones with unlimited data access on two phones.

So yeah, speaking of phones, I think we may have gone overboard in that regards.  We ended up getting two Treos, one 700p and one 700wx (taking advantage of the 30 day exchange period so that we could figure out which one is better). I've read that the wx has some issues, but so far so good.  While I'm happy with the features I'm getting for my price, one thing that has me second guessing is that there seem to be pockets of my house where I'm roaming (as weird as that sounds).  Well, we'll see how it goes, but I'm loving the download speeds on the Sprint Vision network.

I've also been trying to get back to the gym...man holidays.  I always end up gaining like 6-7 pounds.  This year, it was compounded by my still healing fractured finger.  One good resource I came across is the Mayo Clinic's guide to core exercises.  I'm going to put some of these to good use.

On the professional front, I've been doing a lot of work with WF, WSS3, and Office 2007.  I wrote a great review on Amazon for Essential Windows Workflow Foundation by Dharma Shukla and Bob Schmidt, but it seems like it never made it to the product page :-(  It's a 4-star book for those that want to understand what's under the hood and the internals of WF (from a high level).  It's not a good "cook-book" type of book and it doesn't have lots of pictures, but I think it has some great info.

Well, that about wraps this up.  Happy New Year folks!

Filed under: Life, WF No Comments