In a previous post, I talked about modeling flowcharts with C# code. The flowcharts are designed, documented, and standardized by a non-profit organization charged with measuring the quality of patient care inside of hospitals. They do so by looking at common cases that every hospital will see, like heart failure and pneumonia patients. The logic inside each flowchart can determine if the hospital followed the “best practices” for treating each type of case. Some of the logic becomes quite elaborate, particularly when evaluating the types of antibiotics a patient received, and the antibiotic timings, relative orderings, and routes of administration.
Sitting down with 100 of pages of flowchart logic was intimidating, and realizing that new versions came along every six months was enough to induce fear. I experimented with a few visual tools and rules engines but nothing was making the job easy and producing a maintainable solution.
From the beginning I also was thinking about a domain specific language. At one point I decided to sit down with pen and paper to write down what I saw in the flow charts and what I thought a DSL might look like:
a flowchart for pneumonia case data
has a shape named “Smoking History”
with an arrow that points to “Smoking Counseling” if the patient DOES smoke
and an arrow that points to the shape “…” if patient DOES NOT smoke
has a shape named “Smoking Counseling”
with an arrow that points to …
Not great, but it wasn’t too hard to read. It also made me realize that only a few simple abstractions were needed to make an executable flowchart. Flowcharts contain shapes, and shapes contain arrows, and each arrow points to another shape and has an associated rule to evaluate and determine if the arrow should be followed. I showed the code to a couple of these classes in a previous post.
The only hard part then would be correctly assembling shape, arrow, and rule objects into the proper hierarchy for evaluation. One option was to parse instructions like the text that I had put down on paper, but I wanted to try something simpler first.
“Object graph” is a computer scientist’s term for a collection of related objects. I needed to arrange shapes, arrows, and rules into an object graph. We deal with object graphs all the time in software. ASPX files describe an object graph that is assembled at runtime to emit HTML. An even better example is XAML - the XML dialect for WPF, Silverlight, and WF. XAML is the direct representation of a hierarchical graph of CLR objects. I considered what a flowchart might look like in XML.
<Flowchart Name="Pnemonia Measure 6" CaseDataType="PnemoniaCaseData"> <Shape Name="Smoking History"> <Arrow PointsTo="Smoking Cessation" Rule="?" /> </Shape> <Shape Name="Smoking Counseling"> <!-- ... --> </Shape> <!-- ... --> </Flowchart>
The one stickler was how to represent the rule for an arrow. There are mechanisms available to represent expressions and code in XML (XAML’s x:Code directive element and serialized CodeDom objects ala WF rule sets are two examples), but code in XML is always tedious, cumbersome, and worst of all - impervious to refactoring operations.
Not to mention, I think many developers today are feeling “XML fatigue” – me included. Five years of XML hyperbole followed by 5 years of software vendors putting everything and anything between < and > will do that.
The other option was to assemble the flowchart using a fluent interface in C# – a.k.a an internal DSL. Chad Myers recently posted some excellent notes on internal DSLs, which he defines as “…bending your primary language of choice to create a special syntax that’s easier for the consumers of your API to use to accomplish some otherwise complicated task”. Chad also has notes on the method chaining pattern, which is the approach I took.
First, I needed an extension method to add a Shape to a Flowchart…
public static Flowchart<T, R> WithShape<T, R>(
this Flowchart<T, R> chart, string shapeName) { Shape<T, R> shape = new Shape<T, R> { Name = shapeName }; chart.Shapes.Add(shape); return chart; }
…and another extension method to add an Arrow to a Shape. The trick was realizing that the arrow would always apply to the last shape added to the flowchart.
public static Flowchart<T, R> WithArrowPointingTo<T, R>(
this Flowchart<T, R> chart, string pointsTo) { Arrow<T> arrow = new Arrow<T>(); arrow.PointsTo = pointsTo; chart.LastShape().Arrows.Add(arrow); return chart; }
Similarly, a Rule always goes to the last Arrow in the last Shape:
public static Flowchart<T, R> AndTheRule<T, R>(
this Flowchart<T, R> chart, Func<T, bool> rule) { chart.LastShape().LastArrow().Rule = rule; return chart; }
Using the API looks like:
new Flowchart<PnemoniaCaseData, MeasureResult>() // ... .WithShape("AdultSmokingHistory") .RequiresField(pd => pd.SmokingHistory) .WithArrowPointingTo("Rejected") .AndTheRule(pd => pd.SmokingHistory.IsMissing) .WithArrowPointingTo("Excluded") .AndTheRule(pd => pd.SmokingHistory == YesNoAnswer.No) .WithArrowPointingTo("AdultSmokingCounseling") .AndTheRule(pd => pd.SmokingHistory == YesNoAnswer.Yes) .WithShape("AdultSmokingCounseling") .RequiresField(pd => pd.SmokingCounseling) .WithArrowPointingTo("Rejected") .AndTheRule(pd => pd.SmokingCounseling.IsMissing) .WithArrowPointingTo("InDenominator") .AndTheRule(pd => pd.SmokingCounseling == YesNoAnswer.No) .WithArrowPointingTo("InNumerator") .AndTheRule(pd => pd.SmokingCounseling == YesNoAnswer.Yes) .WithShape("Rejected")
.YieldingResult(MeasureResult.Rejected) .WithShape("InDenominator")
.YieldingResult(MeasureResult.InMeasurePopulation) .WithShape("InNumerator")
.YieldingResult(MeasureResult.InNumeratorPopulation);
Overall I was happy with how quickly we were able to pound out flowcharts using the fluent API. The code is relatively easy to compare to the original flowcharts in the specifications (although with the larger flowcharts, nothing is too easy). It’s also easy to test, diff-able after check-ins, and still works after refactoring the underlying data model. The real measure of success though, will be how well the code stands up to the changes and revisions over time. The verdict is still out.
In a future post I’d like to tell you about the .RequiresField method, as it drove a number of key scenarios in the solution. First, we’ll have to talk about the magic of Expression<T>…