A Refactoring Story

The following is a
description of a system I have recently worked on. It describes the
thought processes we followed as we moved a large part of the code into
a declarative workflow engine. Its here because I think it shows some
of the thoughts that do into the continual design/refactoring of a
system like this. I warn you though - it is quite long :-)

The system was driven by a document workflow. The commands that a
user could perform would depend on the kind of document they were
viewing and also the state of that document.

We had recently moved to a declarative definition of workflow that
defined the Commands that could be performed on a document. When the
user viewed a document the client would call GetCcommandsForDocument
that would return a set of Command objects. Each Command returned would
generate a button on the view. When the button was clicked it would
instantiate that Command and send it back to the server via the
DoCommand method on an ICommandProcessor. On the asp client this would
delegate the call via remoting to the server.

For security reasons we separated Command and CommandProcessor, so
for example an AcknowledgeCommand would cause a CommandProcessorFactory
on the backend to reflectively lookup an AcknowledgeCommandProcessor,
inject its dependencies using pico-style DI (of
course :-) and call DoCommand on the processor. This meant that the
assembly that contained the Commands contained no domain logic code -
that was all in the CommandProcessors assembly.

Nice, but not nice enough.

Our users started asking for particular fields to be populated for
each action. For example, when the Acknowledging an invoice, the user
must correct any ambiguous dates (the invoices came from a document
recognition system and could come from many locales and languages).

At first we started writing and specialising custom Commands for
each action - previously they had been completely generic. As we went
along this path we found that we were dupicating these requirements
across Commands, since different Commands might have the same
requirement (eg. you must enter a comment).

So we abstracted a Requirement object, which encapsulated a single
such requirement. Then when the client called GetCommandsForDocument,
instead of getting a class which it would use to instantiate the
Command, it would get a set of Command instances populated with
Requirements. These were almost like Prototypes but we used them more like Flyweights. The view had a RequirementView corresponding to each Requirement, and we would use the Requirement as a model for the View.

Now when the user clicked on a command button, we would display a
new page (it would be a dialog in a gui) which would show the
RequirementViews backed by their models, Which were in turn populated
from the invoice data (which was in a DataSet. (There’s a lot of
different models here and it can get a bit confusing).

The user would enter data in the fields (RequirementViews) and click on the OK button, which would:
* Ask the RequirementViews to update their models (the Requirements in our flyweight Command)
* Ask the Command to validate itself
* If the Command is not valid, refresh the screen and indicate the fields (RequirementViews) in error
* Fire the command back to the server

* If the Command failed, refresh the screen and show the error (the
server could perform extra validation and would throw an Exception.We
could have returned an annotated Command object in the same way as the
client side validation above and had Requirement level server
validation but we never really needed it)
* go back to the previous screen and refresh it

Now we could declaratively build fine grained gui components from the back end. Our gui views were defined in an XML file that looked something like this:

<workflow type=”foo”>
<state name=”Unassigned”>
<action name=”Acknowledge” permission=”Acknowledge”
requirements=”ValidStartDate,Comment” destinationState=”InProgress”
destinationUser=”CurrentUser”/>

</state>
</workflow>

Here destinationUser is a Strategy - we had things like CurrentGroup, CurrentUser, TeamLeader, AccountsGroup etc.

We could have defined specialised Requirements like :
<validDateRequirement field=”startDate”/>
<validDateRequirement field=”endDate”/>

<commentRequirement field=”comments” required=”true”/>
but we didn’t feel the need so we used the simple attribute based approach above

Requirements became a very powerful abstraction in our system.

Having a declarative workflow also meant that we were able to
generate nice dot graphs for our BAs automatically each build, and
since we reloaded our workflow at each request (inefficient? who cares
- compared to remoting it didn’t slow the system down) we were able to
sit with the BAs, change the workflow xml file, reload the page and see
the changes immediately. It was great to see their faces light up when
two day stories became 5 minute changes to the XML
file - “Sit with me and we’ll do it now. Oh you want the fields in a
different order - hang on, how’s that? Cool? Ok I’ll commit it”.

One question this raises of course is that of how do you (or even do
you need to) test declarative systems like this. Do you need to write a
set of tests for every possible state transition? This seems a lot like
overkill - all you’re really doing is testing the contents of your XML file. But changing a part of the XML file can break the system from the users point of view. But fixing it again is really simple, so do you just give the BA’s
a tool to edit the declarative stuff and let them rework the system
themselves? If they then break the system from the users point of view
then that’s not a bug, its a misconfiguration - but the user still
can’t do their work.

Perrhaps we whould have a set of tests that act like invariants -
“In this state the date must be valid” and build them into the system?
That way the system will still function even if the details of the
Requirements are not necessarily as the user desires, or particular
actions fail due to invariant exceptions. Actually I quite like this
idea because it seems to cut across the declarative part of the system
at right angles. It defines invariants that guarantee thet the system never fails even if particular actions and commands throw errors - its also quite fail-fast.

So in the above example the system would never throw an “InvalidDateException - ‘23 Janvier S004
is not a valid date” later in the workflow when the date was used for
some piece of domain logic, rather it would fail at the Acknowledge
step with some kind of “InvariantException - The date must be valid in
the state InProgress - This is probably due to a mis-configuration. A
message has been sent to the system support staff.”.

Leave a Reply