WF First Impressions

by mgordon 29. November 2007 05:25

I've been working with a team since March on a huge, complicated billing and customer management system for a utility company.  Part of what the application does is manage change orders of various types.  Depending on the order's type, it is routed to various locations so that actions can be performed at each of them.  When this requirement was originally discussed, it was thought that it would be sufficient to define the paths the orders would go on, statically.  That is, the order of stops would always be the same.  These paths were allowed to branch, but there was no decision logic involved in them.  I looked at WF, at that time, and decided it would be overkill.  I then went about writing an engine to manage the process of promoting the orders through the paths which was a huge, messy task.

A few months later, it as decided that decision logic was, in fact, going to be needed in the flows.  I reasoned that I had two options at this point.  I could either go back into the engine code I had written and implement some polymorphic way to call business logic (such as coding the calls against an Interface and loading up a configured class that implemented the interface at runtime)...OR I could toss the engine and look at workflow, again.  I chose to do the latter.  I hadn't yet used workflow on any production application, so I armed myself with a couple of books (this one and this one) and dug in.

Now that this portion of the application is rewritten, I wanted to post about my take on the experience. 

Less Time and Code
First of all, I was extremely impressed at how much code I DIDN'T have to write for this implementation.  In writing my engine, I had to write a ton of code to determine whether or not the order was on a path parallel to the one I was currently processing and whether I could continue or not.  Having Workflow handle all that for me was a huge bonus. This also translated into a great deal of time savings.  I implemented several complicated flows and had a working system in about two and a half weeks with the previous engine I wrote taking a couple of months.

Expressing Complicated Logic in a Visual Way
I'm not sure why, but I kept thinking I was going to encounter a set of logic I needed to implement that I could not express with the supplied workflow activities, but I did not.  I didn't need to write any custom activities (although that would have been a good exercise to have had) in order to get the job done.  I found the activities that came in the box to serve me very well.

Along with the good, though, I did encounter a few things that I wasn't crazy about.

Inheritance and the Designer
As I worked through the various flows I had to implement, I kept encountering bits of code tied to code activities that they all the flows had in common.  Naturally, my instinct told me to refactor these, put them into one place, and share them.  So, I pulled all the common bits into a base class and inserted it into the inheritance hierarchy.  I created the base class and had it inherit from SequentialWorkflowActivity (the base class, by default, of a sequential workflow) and then modified my flows to inhert from this base class.  What I expected, was for the designer to recognize the inherited methods and allow me to tie my code activities to them.  However, the designer did not display any of these methods for me to choose from, and when I manually entered the method name it both insisted upon adding the method to the workflow again AND complained that it could not do so since the class it was inheriting from already had a method by that name.  I should mention that I was using VS 2005 for my development and have not tried to do a similar thing in VS 2008.  This may have been corrected.  Also, there may very well be a way to accomplish what I needed, but the way that seemed obvious to me did not work as I expected it to.

Communication Between Workflow and Host
All communication between the workflow runtime (within which your workflow executes) and the host of the runtime is asynchronous.  To communicate into the workflow, you need to fire an event into it and the workflow needs to be in a state of actively listening for that particular event.  For the workflow to communicate with it's host, an event is fired from within the flow and the host needs to have a handler for the event.  For some of my flows, it was necessary for the flow to occasionally ask the user how to proceed with a messagebox allowing a yes or no answer.  The amount of code to implement this was quite a bit more than I expected.  I understand that the workflow engine is running on a different thread and that all this communication needs to be synchronized, but I have to think that the API providing this functionality will be improved over time to make quicker work of implementing this type of communication.

You can expect that I'll be posting more details about workflow and my experiences with it along the way.  I'm reluctant to share too much at this point since I still consider myself to be a rank amateur with the technology in regard to best practices.

Tags:

Workflow

Automatic Properties

by mgordon 14. November 2007 08:20

I've been exploring some of the new language features that will be available in the coming version of .Net.  I continue to see things that look like they have been borrowed from dynamic languages.  Another to add to the list is automatic properties.  In C#, you'll be able to define properties like this.

public string FirstName { get; set;}

This is functionally equivalent to the following:

private string _FirstName;
public string FirstName
{
    get
    {
        return _FirstName;
    }
    set
    {
        _FirstName = value;
    }
}

This will be a real timesaver for those not using a tool such as CodeRush where creating he above block of code is reduced to three keystrokes (ps<space>) and then typing in the name of the property. 

Now, look at how the equivalent is accomplished in Ruby.

attr_accessor: FirstName

Instead of specifying get; and set;, you specify attr_writer for a write-only property and attr_reader for read-only.

Tags: , , ,

General | .Net | Productivity | Ruby

Common Table Expressions and Recursion

by mgordon 12. November 2007 04:48

Some time ago, I was working on a project that needed to work with users as a hierarchy.  The hierarchy was similar to what you would see in typical organizational chart and the users were organized by who they worked for and who worked for them.  As part of one of the requirements for the system, I needed to be able to retrieve from the database any particular user and all users who were placed beneath them in the chart, from their position in it all the way to the bottom.

To store the relationship between all the users, I created a self referencing table that contained user_id and managing_user_id columns.  The idea was that the record would contain the id of a user and also the id of the user over them.  If the user was at the top of the hierarchy, the managing_user_id column would be null.

Obviously, getting the data out of the table in the way I needed it could best be done with recursion.  I needed to get the user, all records that pointed to that user as the managing user...all the records that pointed to each of those records and so on.  I was wondering how to do this without making multiple calls to the database, however, because I could see the number of calls being astronomical for users near the top of the hierarchy.

Then I ran across a technique whereby I could use a CTE in a  recursive way.  The code looked something like this.

      WITH userHierarchyTiers (user_id, managing_user_id, user_hierarchy_id) AS
      (
           SELECT uh.user_id, managing_user_id, user_hierarchy_id
           FROM user_hierarchy uh, users u
           WHERE uh.user_id = @UserId
           AND u.user_id = uh.user_id

           UNION ALL

           SELECT uh.user_id, uh.managing_user_id, uh.user_hierarchy_id
           FROM user_hierarchy uh 
           INNER JOIN userHierarchyTiers uht ON uh.managing_user_id = uht.user_id
      )
      SELECT user_id, managing_user_id from userHierarchyTiers

The key to this technique is in the fact that the second query in the CTE references the entire CTE in its FROM clause.  The net effect is that the CTE is executed and for each row in the result, the CTE is executed again and so on until there are no rows in the result set.

Tags: ,

Sql Server 2005 | Database

Extension Methods

by mgordon 9. November 2007 04:33

With the release of the next version of Visual Studio and the .Net languages, a new feature called Extension Methods will be available.  In a general sense, these allow you to add functionality to classes without having to modify their source including any class in the .Net Framework.  This is a very similar idea to what is offered in a dynamic language such as Ruby, but in the strongly typed .Net languages.

 For example, Ruby has a string type.  If, for some reason, we wanted to add a method to the string class that would return the string wrapped in html that would render the string in a particular color, we could do something like this.

class String
  def to_htmlColor (color)
    "<font color='" + color + "'>" + self + "</font>"
  end
end

puts "fido".to_htmlColor("#AAAAAA")

This would return the string <font color='#AAAAAA>fido</font>

In C#, we can accomplish a similar thing this way.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace testExtensionMethods
{

    class Program
   
{
       
static void Main(string[] args)
        {
           
Console.WriteLine("423".to_i());
           
Console.WriteLine("xxx".to_i());
        }
    }

    public static class Extensions
   
{
       
public static int to_i(this string s)
        {
           
int i;

           
if (!int.TryParse(s, out i))
            {
               
throw new InvalidCastException("String cannot be converted to an integer");
            }
           
return i;
        }
    }
}

Here, I've added a to_i() method to the .Net string class that converts the string value to an integer if it can and throws an exception if it can't.  What triggers the desired behavior is the inclusion of the this keyword in the method signature.  This syntax tells the compiler that the method is to be added to the string type since it follows the this keyword.  The new method can be used on the string class anytime my extension class is in scope.  For example, I could have added my Extensions class in a different namespace and anytime that namespace was included with a using or imports statment, my extension method is available.

Certainly, this is a more brief syntax than int.Parse("423") and feels more natural.  I can recall several situations where I've had to string together commands from various classes in a single statement.  Extension methods could help out with creating convenience methods to replace complicated syntax in these situations.

Scott Gutherie discusses how extension methods were used to add functionality to classes for use with Linq, here.  When the Linq namespaces are included, standard classes are augmented with new functionality that make them easier to use in conjunction with Linq.  In thinking about how I might use these, one idea that came to mind was in a situation where I had a data transport class.  It needs only to be able to contain the data when it's moving between layers, but when it arrives in the business layer from the data layer, it would be nice to be able to add functionality to the class.  This would keep the class nice and tight as it is passed around, but when you need to perform some action on the data contained within, extension methods could be used to add that functionality.  In a way, this technology allows you to have a class function differently based on the "mode" or context it happens to be in at any given time.  Very snazzy!

Tags: , ,

.Net | Productivity | Ruby

Video Streaming Experience

by mgordon 5. November 2007 04:20

So, The server is built and I've had some time to figure out how to get the system doing what I bought it to do.  I do development at home and needed a database server and development server for builds and a source repository.  So, the plan was to build the box out with a 64 bit OS and virtualize three machines on top of it.  This is, in fact, what I did, but soon found that I was pegging out the CPU on the Virtual server I had created for my media server.  I'm using Microsoft's virtual server and I tried tweaking the resource setting to try and get more cycles to the media machine.  Unfortuanately, the settings allowed me to specify only the percentage of a single CPU to allocate to any one virtual server.  On my particular hardware, I have 2 quad-core CPU's which means that the most of total CPU I could assign to the media machine was 12.5% of the total resource.

I had wanted to keep the host OS pristine and clean, but I decided to install TVersity on the host OS and try it from there.  To my delight, this worked perfectly.  The transcoding used between 15% and 20% of the total CPU on the machine, which was just beyond what I was able to allocate to a virtual server.  I was still experiencing some stutter while watching video on the Xbox, though, and I resoned that this was due to bandwidth on my wireless G network.  I went into the transcoder settings in TVersity and reduced the video resolution from 1024 x 768  to 640 x 480 and the stutter has gone away.  I plan to try raising this resolution a bit at a time to see where the wall is.

Tags:

General | Xbox

Installing SCSI/SAS Drivers Without a Floppy Drive

by mgordon 1. November 2007 06:01

The Dell PowerEdge I ordered finally came in.  I had ordered it without an OS installed and was eager to remedy that.  I popped the install CD in and booted the beast up.  Didn't take long for me to realize that Windows was not going to be able to see my hard drives until I loaded drivers for the controller and that I had no floppy drive through which to load them.  Without belaboring the point, would it be so difficult to have the windows installer prompt you for a cd from which to load the drivers?

After a short panic, I started looking for solutions on the web.  I read about several approaches where folks were booting from USB drives and executing a subst command to make it look like an A: drive.  Seemed too risky, to me.  Then I found a recommendation for nLiteOS which is a utility that allows you to modify the Windows install CD to include extra drivers, set up unattended installs, include hotfixes in your install and more.  The posting I read said that success could be found by including my SAS driver in the Windows installer.  Once it was added, the utility would generate an ISO image for me and I could burn a new CD from which to boot and do my install. 

I followed the tutorials on the nLiteOS web site and was pleasantly surprised to find that Windows found my drives and I was on my way.

Tags:

General

Powered by BlogEngine.NET 1.5.0.7
Theme by Extensive SEO