|
I was recently working on a project where I was building an application that was essentially taking control flow direction from multiple locations. In short, it was connected to a network server and received messages indicating certain events taking place either on the server or by other connected clients. In addition, the application has a rich WPF GUI, which means the user is pushing buttons and clicking everywhere.
After a while of building this application up so that it had some decently testable functionality, I began to notice some really big problems in design. Firstly, the application was chock full of boolean flags and enumerated flags indicating mini-states. Basically my application was full of switch statements containing really convoluted branching logic. In other words, I had logic that basically looked like this:
switch (statusFlag) { .. flag1: if (state == state1) dothis(); else if (state == state2) doThat(); break; flag2:...}
It was completely hideous. What was going on was that I was doing a poor-man's state machine. I was maintaining states and sub-states as well as the rules governing the transitions between states in integer and boolean flags, and here's the kicker: even though I wrote the application it was nearly impossible to deduce the control flow from the code. This is where Windows Workflow Foundation came in like Mighty Mouse to save the day (I really did feel like my app was tied to some railroad tracks about ready to die...)
So then this became my goal: Create an overarching state machine that governs the actions of my application, and use sub-workflows for when the application is in discrete sub-states that have their own workflow logic. The requirements for this new design were:
Basically here's how it works. The workflow in this architecture is really something like a super-controller. It is the ultimate authority on what happens in the application. The workflow tells the "outside world" (our application) about important things that need to take place and the application tells the workflow about important events that have taken place. In order to keep things clear, I typically create a single DEX class but I separate the interfaces into "In" and "Out" interfaces to really make it easy to tell what's going on. For example, if I had a DEX that was used for communicating with the network server, I might create two interfaces called INetworkServerDataExchangeIn and INetworkServerDataExchangeOut both implemented in a single class called NetworkServerDataExchangeService.
The controller in the diagram is a mini-controller responsible for small things. In the case of WPF, there will more than likely be a mini-controller for each WPF window instance running (except subservient dialogs, which are handled by the parent window/controller). What's interesting to note here is that the DEX is able to modify the model to which the view is bound. This is because, at least in my application, there is a truckload of network traffic taking place, and if I have to bother the WF runtime every time a single event takes place (in one state, I can have as many as a thousand network events every minute) then I'm wasting resources. My rule of thumb was that if the workflow doesn't actually need to react (business logic progression) to the network event, then I'm safe modifying the model right from the DEX. For example, if I have a window on screen and I get a network event indicating that, say, my ping time has gone from 120 to 119, I can set that value in the model and be done with it. If, however, the ping time goes to 0, I need to tell the workflow about it so it can decide what to do and move the state of the application accordingly.
Let's take a sample scenario and walk through it using the architecture diagram above. The application starts up, and it does nothing. It doesn't contain any code that launches additional dialogs or anything else. All it does is launch the main state machine workflow and then takes a back seat. It rigs up the DEX so that it can listen to events fired by the DEX that indicate the workflow has called external methods, but that's about it. In an application that does a network login, the sequence might go something like this:
You might be thinking at this point, "Holy crap! That's a truckload of added complexity! I could've done all of that in a single code-behind .xaml.cs file and been done three weeks ago!!!"
Well, that's correct..and incorrect. Let's analyze the complexity here:
The initial ramp-up time for coding is significantly more than just pulling the code out of your... pants. In addition, my estimation is that to get the application to a point where you can hit F5 and watch it log in takes almost four times as long as if you had written the code linearly in a 'dumb' code-behind class.
However. If you code it using the workflow, you get some huge benefits:
In case anybody is wondering, I tried and experiment and used my Mac
instead of Visio for the diagram. I used a demo of OmniGraffle to make the
illustration and I was immensely pleased with the results.
"Create an overarching state machine that governs the actions of my
application, and use sub-workflows for when the application is in discrete
sub-states that have their own workflow logic."
BPEL is more of a back-end situation. I realize that there are, in fact,
BPEL templates for Windows Workflow Foundation, but I found those to be
actually more than what I need. What I am doing here is orchestrating my
GUI, not a huge enterprise-wide business process, so I decided that I was
better served by using straight unmodified WF rather than the BPEL WF
activities that you can now download from Microsoft.
I think that by using this type of work flow. it will make things easier
for people to work!
In relation to leif's comment BPEL is more back-end. There is a BPEL for
people out there but I have never used it.