dynamic and generic workflows in · dynamic and generic workflows in .net bart de smet promoters:...

131
Universiteit Gent Faculteit Ingenieurswetenschappen Vakgroep Informatietechnologie Voorzitter: Prof. Dr. Ir. P. LAGASSE Dynamic and generic workflows in .NET door Bart DE SMET Promotors: Prof. Dr. Ir. B. DHOEDT, Prof. Dr. Ir. F. DE TURCK Scriptiebegeleider: Lic. K. STEURBAUT Scriptie ingediend tot het behalen van de academische graad van burgerlijk ingenieur in de computerwetenschappen Academiejaar 2006-2007

Upload: others

Post on 22-Mar-2020

30 views

Category:

Documents


0 download

TRANSCRIPT

Universiteit Gent

Faculteit Ingenieurswetenschappen

Vakgroep Informatietechnologie

Voorzitter: Prof. Dr. Ir. P. LAGASSE

Dynamic and generic workflows in .NET

door

Bart DE SMET

Promotors: Prof. Dr. Ir. B. DHOEDT, Prof. Dr. Ir. F. DE TURCK

Scriptiebegeleider: Lic. K. STEURBAUT

Scriptie ingediend tot het behalen van de academische graad van burgerlijk ingenieur in de

computerwetenschappen

Academiejaar 2006-2007

Ghent University

Faculty of Engineering

Department of Information Technology

Head: Prof. Dr. Ir. P. LAGASSE

Dynamic and generic workflows in .NET

by

Bart DE SMET

Promoters: Prof. Dr. Ir. B. DHOEDT, Prof. Dr. Ir. F. DE TURCK

Assistance by Lic. K. STEURBAUT

Master Thesis written to obtain the academic degree of Master of Computer Science Engineering

Academic year 2006-2007

iv

Preface

Two exciting years have flown by... I still remember the sunny summer day in 2005 when I decided to

continue my studies at Ghent University with a special study “bridge program” to obtain a Master of

Computer Science Engineering – Software Engineering degree in two years. Thanks to Prof. Dr. Ir. K.

De Bosschere and Prof. Dr. Ir. L. Eeckhout for their support in composing a special personal study

program. I’d like to thank my parents too for giving me this opportunity; housing and supporting a

student for two additional years is a real challenge as well.

Without doubt, it’s been a challenging two years to combine courses from the Bachelor and Master

curricula, sometimes having to attend three lessons at the same time, fighting conflicting project

deadlines while reserving time for extra-curricular activities. Luckily, this final year’s Master Thesis

allows for a personal touch to put the crown on the work.

The subject of this Master Thesis is Windows Workflow Foundation, a core pillar of Microsoft’s latest

release of the .NET Framework. When choosing a topic for the thesis, back in the spring of 2006, the

technology was still in beta, imposing quite some other challenges. Lots of betas, breaking changes

and sometimes a bit of frustration later, the technology has reached its final version, and to be

honest I’ve been positively surprised with the outcome and certain aspects of the technology.

Although my interest in .NET goes back to the early 2000s, workflow didn’t cross my path until one

year ago, wetting my appetite for this topic even more.

I’d like to thank Prof. Dr. Ir. B. Dhoedt and Prof. Dr. Ir. F. De Turck for their support to research this

cutting edge technology and to support this thesis. Furthermore, I can’t thank enough Kristof

Steurbaut for his everlasting interest in the topic and for providing his insights in practical use cases

for the technology at INTEC.

Researching the topic of workflow also opened up for another opportunity during this academic year,

writing a scientific paper entitled “Dynamic workflow instrumentation for Windows Workflow

Foundation” for the ICSEA’07 conference. Without the support from Bart, Filip, Kristof and Sofie this

wouldn’t have been possible. Their incredible eye for detail was invaluable to deliver a high quality

work and made it a unique and wonderful experience.

This year hasn’t only been a massive year at university; it’s been a busy year outside as well. In

November 2006, I went to Barcelona to attend the Microsoft TechEd 2006 conference where I was

responsible for some Ask-the-Experts booths, attended numerous sessions on various technologies

including Workflow Foundation and where I participated in Speaker Idol and won. Special thanks to

Microsoft Belux to support this trip and to provide lots of other opportunities to speak at various

conferences.

Looking at the future, I’m happy to face another big oversea challenge. In February 2007 I visited the

US headquarters of Microsoft Corporation in Redmond, WA. After two stressful days with flight

delays, tough interview questions and meeting a bunch of great people, I returned home on my

birthday with what’s going to be my most wonderful birthday gift so far: a full time job offer as

Software Design Engineer at Microsoft Corporation. I’m proud to say I’ll take on this opportunity

starting from October this year.

v

The cutting edge nature of the technology discussed in this thesis, contacts with Microsoft and my

future plans to work at Microsoft Corporation have driven the decision to write this work in English,

supported by Prof. Dr. Ir. B. Dhoedt. Special thanks to Prof. Dr. Ir. Taerwe and Prof. Dr. Ir. De

Bosschere to grant permission for this.

Finally, I’d also like to thank my colleague students in the Master of Computer Science Engineering

“bridge program” for their endless support on a day-to-day basis. Arne and Jan, you’ve been a great

support the last two years and I hope to stay in touch.

Bart De Smet, May 2007

Toelating tot bruikleen

“De auteur geeft de toelating deze scriptie voor consultatie beschikbaar te stellen en delen van deze

scriptie te kopiëren voor persoonlijk gebruik. Elk ander gebruik valt onder de beperkingen van het

auteursrecht, in het bijzonder met betrekking tot de verplichting de bron uitdrukkelijk te vermelden bij

het aanhalen van resultaten uit deze scriptie.”

Rules for use

“The author grants the permission to make this thesis available for consultation and to copy parts of

this thesis for personal use. Every other use is restricted by the limitations imposed by copyrights,

especially concerning the requirement to mention the source explicitly when referring to results from

this thesis.”

vi

Dynamic and generic workflows in .NET

door

Bart DE SMET

Scriptie ingediend tot het behalen van de academische graad van burgerlijk ingenieur in de

computerwetenschappen

Academiejaar 2006-2007

Promotors: Prof. Dr. Ir. B. DHOEDT, Prof. Dr. Ir. F. DE TURCK

Scriptiebegeleider: Lic. K. STEURBAUT

Faculteit Ingenieurswetenschappen

Universiteit Gent

Vakgroep Informatietechnologie

Voorzitter: Prof. Dr. Ir. P. LAGASSE

Samenvatting

In november 2006 bracht Microsoft de Windows Workflow Foundation (WF) uit als deel van de .NET

Framework 3.0 release. Workflow stelt ontwikkelaars in staat om businessprocessen samen te stellen

op een grafische manier via een designer. In dit werk evalueren we de geschiktheid van workflow-

gebaseerde ontwikkeling in de praktijk, toegepast op medische ‘agents’ zoals deze in gebruik zijn op

de dienst Intensieve Zorgen (IZ) van het Universitaire Ziekenhuis Gent (UZ Gent). Meer specifiek

onderzoek we hoe workflows dynamisch aangepast kunnen worden om tegemoet de komen aan

dringende structurele wijzigingen of om aspecten zoals loggen en authorizatie in een workflow te

injecteren. Dit deel van het onderzoek resulteerde in het bouwen van een zogenaamd

instrumentatieraamwerk. Verder werd ook onderzoek verricht naar het bouwen van een generieke

set bouwblokken die kunnen helpen bij het samenstellen van data-gedreven workflows zoals dit

typisch gebeurt bij het bouwen van medische ‘agents’. Ontwerpbeslissingen worden in detail

besproken en prestatieanalyses worden uitgevoerd om de toepasbaarheid van workflow, via de in dit

werk gebouwde technieken, te toetsen.

Trefwoorden: .NET, workflow, dynamic adaptation, instrumentation, generic frameworks

vii

Dynamic and generic workflows in .NET

by

Bart DE SMET

Master Thesis written to obtain the academic degree of Master of Computer Science Engineering

Academic year 2006-2007

Promoters: Prof. Dr. Ir. B. DHOEDT, Prof. Dr. Ir. F. DE TURCK

Assistance by K. STEURBAUT

Faculty of Engineering

Ghent University

Department of Information Technology

Head: Prof. Dr. Ir. P. LAGASSE

Summary

Recently, Microsoft released Windows Workflow Foundation (WF) as part of its .NET Framework 3.0

release. Using workflow, business processes can be developed much like user interfaces, in a

graphical fashion. In this work, we evaluate the feasibility of workflow-driven development in

practice, applied on medical agents from the department of Intensive Care (IZ) of Ghent University

Hospital (UZ Gent). More specifically, we investigate how workflows can be modified dynamically to

respond to urgent changes or to crosscut aspects in an existing workflow definition. This resulted in

the creation of an instrumentation framework. Furthermore, a generic framework is created to assist

in the development of data-driven workflows by means of composition of generic building blocks.

Design decisions are outlined and performance analysis is conducted to evaluate the applicability of

workflow in this domain using the techniques created and described in this work.

Keywords: .NET, workflow, dynamic adaptation, instrumentation, generic framework

viii

Dynamic and generic workflows in .NET

Bart De Smet

Promoters: Bart Dhoedt, Filip De Turck

Abstract – Workflow-based programming is a recent

evolution in software development. Using this technology,

complex long-running business processes can be mapped

to software realizations in a graphical fashion, resulting in

many benefits. However, due to their long-running

nature, the need for dynamic adaptation of workflows

grows. In this research, we investigate mechanisms that

allow adapting business processes at execution time. Also,

in order to make composition of business processes easier

to do – ultimately by the end-users themselves – a set of

domain-specific building blocks is much desirable. The

creation of such a set of generic and flexible blocks was

subject of our research as well.

Keywords – .NET, workflow

I. INTRODUCTION

Recently, workflow-based development has become

a mainstream programming technique. With workflow,

software processes can be represented graphically as a

composition of building blocks that encapsulate various

kinds of logic, much like flowchart diagrams. Not only

does this close the gap between software engineers and

business people, it has several technical advantages too.

This research focuses on Microsoft Windows

Workflow Foundation (WF) [1].

One typical type of application that greatly benefits

from a workflow-based approach is the category of

long-running business processes. For example, in order

processing systems an order typically goes through a

series of human approval steps and complex service

interactions for payment completion and order delivery.

Workflow helps to build such processes thanks to the

presence of runtime services that keep long-running

state information, track the stage a workflow is in, etc.

However, the use of long-running processing results

in new challenges that have to be tackled, one of which

is dynamic adaptation of workflows. Imagine the case

in which company policies change during the lifetime

of an order process workflow, e.g. concerning payment

validation checks. In order to reflect such structural

changes we need a mechanism to modify running

workflow instances in a reliable manner.

Another research topic embodies the creation of a set

of domain-specific building blocks that allow for easy

workflow composition, ultimately putting business

people in control of their software processes. The

results of this research were put in practice based on

practical eHealth applications from the UZ Gent where

patient info is processed in an automated manner.

II. DYNAMIC ADAPTATION

WF comes with a feature that allows a workflow

instance to be adapted at runtime. Based on this

dynamic update feature we‟ve built a more complex

tool to assist in dynamic workflow adaptation and

instrumentation.

Dynamic workflow adaptation can be summarized as

the set of actions that have to be taken in order to

change a workflow instance at runtime. For example,

the processing of an order or a set of orders might

require additional steps based on business decisions.

Using our tool, such an adaptation can be applied in a

safe manner, without causing any downtime of the

order processing application and with guarantees

concerning the workflow instance‟s correctness.

Instrumentation on the other hand has a more

technically-driven background. It‟s very common for

software to contain lots of boilerplate code in order to

authorize users or to log diagnostic information at

execution time. In workflow-based environments we

don‟t want to spoil the visual representation of a

workflow with such aspects. Instrumentation helps to

inject these aspects dynamically (see Figure 1). At the

left-hand side of the figure the original workflow

definition is shown. This workflow has been

instrumented with timers, an authorization mechanism

and a logging activity at runtime, the result of which

can be seen on the right-hand side of the figure.

Figure 1 - Example of workflow instrumentation

ix

As part of our research we investigated the impact of

dynamic adaptation and instrumentation on the

application‟s overall performance. It was found that

various shapes of dynamic adaptation have non-trivial

costs associated with them, especially in case ad-hoc

updates are applied to a running workflow.

Instrumentation has a significant cost too but its

advantages of increased flexibility and the preservation

of a workflow‟s pure and smooth graphical nature

outweigh the costs.

This part of the research was the subject of a paper

entitled “Dynamic workflow instrumentation for

Windows Workflow Foundation” that was submitted to

and accepted for the ICSEA‟07 conference [2].

III. GENERIC COMPOSITION

Another important aspect when creating workflows is

the use of specialized building blocks that reflect the

business the workflow is operating in. In our research

we created a set of generic building blocks to retrieve

and manipulate data from databases as part of a

medical agent used in the Intensive Care (IZ)

department of Ghent University Hospital (UZ Gent).

The sample “AB Switch” agent performs processing of

medical patient data on a daily basis to propose a

switch of antibiotics for patients that match certain

criteria [3].

Using a few well-chosen building blocks we were

able to express the AB Switch agent in workflow using

graphical composition. A part of the agent‟s workflow-

based implementation is shown in Figure 2. The

yellow, green and red blocks in the displayed workflow

fragment are highly specialized components written

during the research. For example, the yellow data

gathering block can execute a query against a database

in a generic manner, boosting its flexibility and

usability in various kinds of workflows, hence the label

„generic‟.

Figure 2 - A part of the AB Switch agent workflow

Of course one should take various quality attributes

into account when replacing code-based applications by

a workflow-based variant. Various design decisions

have been outlined in our research, including the

applicability in service-based architectures [4].

Another important consideration is performance. It

was shown that workflow can boost the application‟s

performance when exploiting the intrinsic parallelism

of workflow instances. For example, when processing

patient data it‟s beneficial to represent each patient as

an individual workflow instance, allowing the decision

logic for multiple patients to be executed in parallel.

Since developers don‟t have to bother much about the

complexities of multi-threaded programming when

working with WF, this should be considered a big plus.

The performance results for parallel data processing

using workflow compared to a procedural alternative

are shown in Figure 3.

Figure 3 - Parallel workflow execution vs. procedural code

IV. CONCLUSION

Workflow seems to be a valuable candidate for the

implementation of various types of applications. Using

dynamic adaptation and instrumentation workflows can

be made highly dynamic at runtime. Generic building

blocks allow for easy composition of pretty complex

(data-driven) workflows, while having the potential to

raise the performance bar.

ACKNOWLEDGEMENTS

The author wants to thank promoters Bart Dhoedt

and Filip De Turck for the opportunity to conduct this

research and to create a paper on dynamic

instrumentation for the ICSEA‟07 conference. The

realization of this work wouldn‟t have been possible

without the incredible support by Kristof Steurbaut

throughout the research.

REFERENCES

[1] D. Shukla and B. Schmidt, Essential Windows Workflow

Foundation, Addison-Wesley Pearson Education, 2007.

[2] B. De Smet, K. Steurbaut, S. Van Hoecke, F. De Turck and B. Dhoedt, Dynamic workflow instrumentation for Windows

Workflow Foundation, ICSEA‟07.

[3] K. Steurbaut, Intelligent software agents for healthcare decision support – Case 1: Antibiotics switch agent (switch IV-

PO), UGent-INTEC, 2006.

[4] F. De Turck, et al, Design of a flexible platform for execution of medical decision support agents in the Intensive Care Unit,

Comput Biol Med. 37, 2007.

0

5

10

15

20

25

1 2 3 4 5 6 7 8 9 10Ex

ecu

tio

n t

ime

(s)

Number of workflow instances

ProceduralWorkflow

x

Table of contents

Chapter 1 - Introduction ........................................................................................ 1

1 What’s workflow? ............................................................................................................................ 1

2 Why workflow? ................................................................................................................................ 2

3 Windows Workflow Foundation ...................................................................................................... 2

4 Problem statement .......................................................................................................................... 4

Chapter 2 - Basics of WF ......................................................................................... 5 1 Architectural overview ..................................................................................................................... 5

2 Workflows and activities .................................................................................................................. 6

2.1 Types of workflows .................................................................................................................. 6

2.2 Definition of workflows............................................................................................................ 7

2.2.1 Code-only ......................................................................................................................... 7

2.2.2 Markup-based definition with XOML ............................................................................... 8

2.2.3 Conditions and rules ........................................................................................................ 9

2.3 Compilation .............................................................................................................................. 9

2.4 Activities ................................................................................................................................. 10

3 Hosting the workflow engine ......................................................................................................... 11

3.1 Initializing the workflow runtime and creating workflow instances ..................................... 11

3.2 Runtime services .................................................................................................................... 12

4 Dynamic updates ........................................................................................................................... 12

Chapter 3 - Dynamic updates ............................................................................ 13 1 Introduction ................................................................................................................................... 13

2 The basics ....................................................................................................................................... 13

2.1 Internal modification ............................................................................................................. 13

2.2 External modification ............................................................................................................. 14

3 Changing the transient workflow................................................................................................... 14

3.1 Inserting activities .................................................................................................................. 14

3.2 More flexible adaptations ...................................................................................................... 15

3.3 Philosophical intermezzo – where encapsulation vanishes… ................................................ 16

xi

3.4 Establishing data bindings ...................................................................................................... 16

4 Changing sets of workflow instances ............................................................................................. 19

5 An instrumentation framework for workflow ............................................................................... 19

5.1 A simple logging service ......................................................................................................... 20

5.2 Instrumentation for dynamic updates ................................................................................... 21

5.2.1 Instrumentation with suspension points ....................................................................... 21

5.2.2 Responding to suspensions ............................................................................................ 23

5.3 Advanced instrumentation logic ............................................................................................ 26

5.4 Conclusion .............................................................................................................................. 27

6 A few practical uses of instrumentation ........................................................................................ 27

6.1 Logging ................................................................................................................................... 28

6.2 Time measurement ................................................................................................................ 30

6.3 Authorization ......................................................................................................................... 32

6.4 Production debugging and workflow inspection ................................................................... 35

7 Tracking in a world of dynamism – the WorkflowMonitor revisited ............................................. 39

8 Performance analysis ..................................................................................................................... 41

8.1 Research goal ......................................................................................................................... 41

8.2 Test environment ................................................................................................................... 41

8.3 Test methodology .................................................................................................................. 41

8.4 Internal workflow modification ............................................................................................. 42

8.4.1 Impact of workload on adaptation time ........................................................................ 43

8.4.2 Impact of dynamic update batch size on adaptation time ............................................ 44

8.5 External workflow modification ............................................................................................. 45

8.6 Instrumentation and modifications – a few caveats ............................................................. 47

9 Conclusion ...................................................................................................................................... 48

Chapter 4 - Generic workflows ......................................................................... 50 1 Introduction ................................................................................................................................... 50

2 A few design decisions ................................................................................................................... 52

2.1 Granularity of the agent workflow ........................................................................................ 52

2.2 Encapsulation as web services ............................................................................................... 53

2.3 Database logic ........................................................................................................................ 54

3 From flowchart to workflow: an easy step? .................................................................................. 56

3.1 Flow control ........................................................................................................................... 56

xii

3.2 Boolean decision logic ............................................................................................................ 56

3.3 Serial or parallel? ................................................................................................................... 57

3.4 Error handling ........................................................................................................................ 58

4 Approaches for data gathering ...................................................................................................... 60

4.1 Design requirements and decisions ....................................................................................... 60

4.2 Using Local Communication Services ..................................................................................... 60

4.3 The data gathering custom activity ....................................................................................... 62

4.4 Implementing a query manager ............................................................................................ 63

4.4.1 An XML schema for query representation ..................................................................... 63

4.4.2 Core query manager implementation ........................................................................... 65

4.4.3 The query object ............................................................................................................ 66

4.5 Hooking up the query manager ............................................................................................. 68

4.6 Chatty or chunky? .................................................................................................................. 68

4.6.1 Chatty ............................................................................................................................. 68

4.6.2 Chunky ........................................................................................................................... 68

4.6.3 Finding the right balance ............................................................................................... 69

5 Other useful activities for generic workflow composition ............................................................ 69

5.1 ForeachActivity ...................................................................................................................... 69

5.2 YesActivity and NoActivity ..................................................................................................... 71

5.3 FilterActivity ........................................................................................................................... 74

5.4 PrintXmlActivity ..................................................................................................................... 75

5.5 An e-mail activity ................................................................................................................... 77

6 Other ideas ..................................................................................................................................... 77

6.1 Calculation blocks .................................................................................................................. 77

6.2 On to workflow-based data processing? ............................................................................... 80

6.3 Building the bridge to LINQ .................................................................................................... 80

6.4 Asynchronous data retrieval .................................................................................................. 81

6.5 Web services .......................................................................................................................... 82

6.6 Queue-based communication ................................................................................................ 82

7 Putting the pieces together: AB Switch depicted .......................................................................... 83

8 Designer re-hosting: the end-user in control ................................................................................. 85

9 Performance analysis ..................................................................................................................... 88

9.1 Research goal ......................................................................................................................... 88

9.2 Test environment ................................................................................................................... 88

xiii

9.3 A raw performance comparison using CodeActivity.............................................................. 88

9.4 Calculation workflows with inputs and outputs .................................................................... 91

9.5 Iterative workflows ................................................................................................................ 91

9.6 Data processing ...................................................................................................................... 94

9.6.1 Iterative data processing ................................................................................................ 94

9.6.2 Nested data gatherings .................................................................................................. 96

9.6.3 Intra-workflow parallelism ............................................................................................. 99

9.6.4 Inter-workflow parallelism ............................................................................................. 99

9.6.5 Simulating processing overhead .................................................................................. 101

10 Conclusion ................................................................................................................................ 103

Chapter 5 – Conclusion ..................................................................................... 106

Appendix A – ICSEA’07 paper .......................................................................... 108

Bibliography .......................................................................................................... 116

Chapter 1 – Introduction | 1

Chapter 1 – Introduction

1 What’s workflow? The concept of workflow exists for ages. On daily basis humans execute workflows to get their jobs

done. Examples include shopping, decision making process during meetings, etc. All of these have

one thing in common: the execution of a flow of individual steps that lead to some desired result. In

case of the shopping example, one crosses a market place with a set of products in mind to find the

best buy available, performing decision making based on price, quality and marketing influences.

In the computing space, programmers have been dealing with workflow for ages as well. Application

development often originates from a flowchart diagram being translated into code. However, that’s

where it often ends these days. The explicitness of a visual representation of a workflow is turned

into some dark piece of code, which makes it less approachable by management people, not to speak

about the problem of code maintenance especially when code is shared amongst developers. Today’s

workflow concept is all about keeping the graphical representation of some kind of business process

that can be turned to execution by a set of runtime services.

Workflow is based on four tenets. Although not so well-known (yet) as the web service SOA tenets or

the database ACID properties, these four tenets are a good starting point for further discussion:

Workflows coordinate work performed by people and software

This first tenet categorizes workflows in two major groups: human workflows and automated

system processes. The former one involves direct interaction with humans, such as approvals

of invoices as part of a larger workflow, while the latter one is situated in the business

processing space and involves communication between services and machines.

Workflows are long-running and stateful

Using workflow, business processes are turned to execution. Since human interaction or

reliance on external parties is often part of such a business process, workflows tend to be

long-running and keep state. It’s not atypical to have a workflow instance running for many

hours, days or even months. Runtime services are required to support this.

Workflows are based on extensible models

To deal with ever changing business processes and changes of business rules, workflows

need a big deal of flexibility that allows for rapid modification without recompilation and a

deep knowledge of the software internals of the business application. In the end, this should

allow managers to adapt the business process themselves without developer assistance.

Workflows are transparent and dynamic through their lifecycle

The long-running characteristic of workflows should not turn them into a black box. There’s a

big need for transparency that allows analysts to see what’s going on inside the workflow in

a near real-time fashion. Also, workflows should allow for dynamic changes at runtime to

deal with changing requirements. When providing services to 3rd parties, it’s important to

meet Service Level Agreements (SLA) which further strengthens the need for transparency.

It’s also important to remark that the second tenet on the long-running and stateful character of

workflows is in strong contrast to the stateless character of web services. The combination of both

Chapter 1 – Introduction | 2

principles can unlock a lot of potential however, for instance by exposing a workflow through a web

service to allow cross-organization business processing (e.g. a supply chain).

2 Why workflow? In order to be successful, workflow needs a set of compelling reasons to use it. In the previous

paragraph a few advantages were already pointed out. One good reason to use workflows is the

visual representation of workflows that makes them easier to understand and to maintain. This

graphical aid provided by tools makes workflows approachable to a much broader set of people,

including company management.

Furthermore, the need for long-running workflows implies the availability of a set of runtime services

to allow hydration (i.e. the persistence of a running workflow when it becomes idle) and dehydration

(i.e. the process of loading a workflow in memory when it becomes active again) of a workflow. In a

similar way, the need for transparency leads to the requirement of having runtime services for

tracking and runtime inspection. Considering these runtime services (amongst others like scheduling

and transactions), workflow usage becomes even more attractive. Having to code these runtime

services yourself would be a very time-consuming activity and lead to a productivity decrease.

In the end, workflow is much more than some graphical toy and has a broad set of applications:

Business Process Management (BPM) – Workflows allow for rapid modification in response

to changing business requirements. This makes software a real tool to model business

processes and to use software for what it should be intended for: supporting the business.

Document lifecycle management – Versioning, online document management systems and

interactions between people have become a must for companies to be productive when

dealing with information. Approval of changes is just one example workflow can be used for.

Page or dialog flow – A typical session when working with an application consists of a flow

between input and output dialogs or pages. Using workflow, this flow can be modeled and

changed dynamically based on the user’s input and validation of business rules.

Cross-organization integration – Combining workflow with the power of web services, one

can establish a more dynamic way to integrate businesses over the internet in a “Business-

to-Business” (B2B) fashion, e.g. in order-supply chain processing.

Internal application workflow – The use of workflow inside an application can allow of

extension and modification by end-users. Pieces of the application that rely on business rules

can be customized more easily and with out-of-the-box tool support.

3 Windows Workflow Foundation The last couple of years, workflow has evolved from a niche to an applicable paradigm in software

development. Products like Microsoft BizTalk Server have been successful and introduced workflow

to enterprises as an approach to deal with inter-organization process integration (“biz talk”). In

BizTalk we often talk about orchestration rather than workflow. Orchestration helps developers to

automate business processes that involve multiple computer systems, for example in a B2B scenario.

Workflow can be seen as a way to compose such an orchestration in an easier way.

Chapter 1 – Introduction | 3

With Windows Workflow Foundation (WF), a technology introduced in the .NET Framework 3.0,

workflow is brought to the masses and becomes a first class citizen of the .NET developer’s toolbox.

The .NET Framework 3.0, formerly known as WinFX, is a set of managed code libraries that’s created

in the Windows Vista timeframe and ships with Windows Vista out-of-the-box but is also ported back

to run on Windows XP and Windows Server 2003. Other pillars of .NET Framework 3.0 include (see

Figure 1):

Windows Presentation Foundation (WPF) code-named “Avalon”, a graphical foundation that

unifies the worlds of GDI, Windows Forms, DirectX, media and documents based on a new

graphical composition engine. WPF can be programmed using XAML (eXtensible Application

Markup Language) which we’ll use in the WF space too. A related technology is XPS or XML

Paper Specification, used for document printing.

Windows Communication Foundation (WCF) code-named “Indigo”, a unified approach to

service-oriented programming based on the principles of SOA (service-oriented architecture),

bringing together the worlds of DCOM, .NET Remoting, MSMQ, Web Services and WSE. It’s

built around key concepts of service and data contracts and has a high amount of code-less

XML-based configuration support.

Windows CardSpace (WCS) code-named “InfoCards”, a technology to deal with digital

identities and to establish an Identity Metasystem, based on a set of WS-* standards. WCS

can be seen as a new approach to federated identity which was formerly considered in the

.NET PassPort initiative that lacked openness and wide customer adoption due to trust

issues. The use of open standards should help digital identity management to become a

more widely accepted paradigm.

Figure 1 - .NET Framework 3.0

Just like we’ve seen the availability of the DBMS extend to the desktop with products like SQL Server

2005 Express and more recently SQL Server Everywhere Edition, WF brings the concept of workflow

Chapter 1 – Introduction | 4

processing to the desktop. Essentially WF is an in-process workflow processing engine that can be

hosted in any .NET application, ranging from console applications over Windows Forms-based GUI

applications to Windows Services and web services.

Compared to BizTalk Server, WF is a pluggable lightweight component that can be used virtually

anywhere but lacks out-of-the-box support for complex business integration (e.g. using data

transformations), business activity monitoring (BAM), adapters to bridge with external systems (like

MQ Series, SAP, Siebel and PeopleSoft) and reliability and scalability features. Although there is a

blurry zone between both technologies, it’s safe to say BizTalk is rather to be used in complex cross-

organization business integration scenarios while WF benefits from its more developer-oriented

fashion and is to be used more often inside an application. For the record, Microsoft has already

announced to replace the orchestration portion of BizTalk by WF in a future release of the BizTalk

product, leading to convergence of both technologies.

That Microsoft puts a bet on workflow-based technologies should be apparent from the adoption of

the WF technology in the next version of the Microsoft Office System, i.e. Office System 2007

(formerly known as Office “12”) and the Windows SharePoint Services 3.0 technology for document

management scenarios. Other domains where WF will be implemented are ASP.NET to create a

foundation for page flow, future releases of BizTalk as mentioned previously and Visual Studio Team

System for work item processing.

More information on Windows Workflow Foundation can be found on the official technology website

http://wf.netfx3.com.

4 Problem statement This first part of this work focuses on dynamic adaptation of workflows at runtime. Without doubt,

scenarios exist where it’s desirable to modify a workflow instance that’s in flight. A possible scenario

consists of various business reasons that mandate a dynamic change (e.g. introducing an additional

human approval step after visual inspection of the workflow instance’s state). However, also in other

situations dynamic adaptation can be beneficial, for example to weave aspects in a workflow

definition without making the core workflow heavier or clumsier.

In the second part, focus is moved towards the creation of generic workflow building blocks that

makes composition of data-driven workflows easier. The results of this research and analysis are

applied on workflow-based agent systems that are used by the department of Intensive Care (IZ) of

Ghent University Hospital (UZ Gent).

For both parts, performance tests are conducted to evaluate the feasibility of the discussed

techniques and to get a better image of possible performance bottlenecks.

Chapter 2 – Basics of WF | 5

Chapter 2 – Basics of WF

1 Architectural overview On a macroscopic level, the WF Runtime Engine gets hosted inside some host process such as a

console application, a Windows Forms application, a Windows Service, a web application or a web

service. The tasks of the runtime engine are to instantiate workflows and to manage their lifecycle.

This includes performing the necessary scheduling and threading.

The concept workflow is used to refer to the definition of a workflow, which is defined as a class, as

discussed further on. Each workflow is composed from a series of activities which are the smallest

units of execution in the workflow era. One can reuse existing activities that ship with WF but the

creation of custom activities either from scratch or by composition of existing activities is supported

too. We’ll discuss this concept further on.

A single workflow can have multiple workflow instances, just like classes are instantiated. The big

difference compared to “simple objects” is the runtime support that workflow instances receive, for

example to dehydrate a running workflow instance when it is suspended. The services in charge of

these things are called the Runtime Services.

Figure 2 outlines the basic architecture of WF.

Figure 2 - General architecture [1]

Next to the runtime, there is tools support as well. In the future these tools will become part of

Visual Studio “Orcas” but for now these get embedded in Visual Studio 2005 upon installation of the

Windows SDK. An interesting feature of the graphical workflow designer is that it allows for re-

Chapter 2 – Basics of WF | 6

hosting in other applications, effectively allowing developers to expose a designer to the end-users

(e.g. managers) to modify workflows using graphical support.

2 Workflows and activities

2.1 Types of workflows WF distinguishes between two types of workflows. The first type are the sequential workflows that

have a starting point and an ending point with activities in between that are executed sequentially.

An example of such a workflow is depicted in Figure 3.

Figure 3 - A sequential workflow

Chapter 2 – Basics of WF | 7

The second type of workflow supported by WF is the state machine workflow. This type of workflow

is in fact a state machine that has a starting state and ending state. Transitions between states are

triggered by events. An example of a state machine workflow is shown in Figure 4.

Figure 4 - A state machine workflow

2.2 Definition of workflows As mentioned in the previous paragraph, workflows are defined as classes, essentially by composing

existing activities. There are different ways to define a workflow.

2.2.1 Code-only

When choosing the code-only approach, the Integrated Development Environment (IDE) creates at

least two files. One contains the “designer generated code” (Code 2), the other contains additional

code added by the developer, for example the code executed by a CodeActivity activity (Code 1).

public sealed partial class SimpleSequential: SequentialWorkflowActivity

{

public SimpleSequential()

{

InitializeComponent();

}

private void helloWorld_ExecuteCode(object sender, EventArgs e)

{

Console.WriteLine("Hello World");

}

}

Code 1 - Developer code for a sequential workflow

Chapter 2 – Basics of WF | 8

partial class SimpleSequential

{

#region Designer generated code

/// <summary>

/// Required method for Designer support - do not modify

/// the contents of this method with the code editor.

/// </summary>

[System.Diagnostics.DebuggerNonUserCode]

private void InitializeComponent()

{

this.CanModifyActivities = true;

this.helloWorld = new System.Workflow.Activities.CodeActivity();

//

// helloWorld

//

this.helloWorld.Name = "helloWorld";

this.helloWorld.ExecuteCode +=

new System.EventHandler(this.helloWorld_ExecuteCode);

//

// SimpleSequential

//

this.Activities.Add(this.helloWorld);

this.Name = "SimpleSequential";

this.CanModifyActivities = false;

}

#endregion

private CodeActivity helloWorld;

}

Code 2 - Designer generated code of a sequential workflow

2.2.2 Markup-based definition with XOML

Automatically generated code like the one in Code 2 consists of the same elements all the time:

classes (the activity object types) are instantiated, properties are set, event handlers are registered

and parent-child hierarchies are established (e.g. this.Activities.Add). This structure is an ideal

candidate for specification using XML. This is what XOML, the eXtensible Object Markup Language,

does. The equivalent of Code 2 in XOML is displayed in Code 3.

<SequentialWorkflowActivity

x:Class="WorkflowConsoleApplication1.SimpleSequentialMarkup"

x:Name="SimpleSequentialMarkup"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">

<CodeActivity x:Name="helloWorld"

ExecuteCode="helloWorld_ExecuteCode" />

</SequentialWorkflowActivity>

Code 3 - XOML representation of a sequential workflow

XOML gets translated into the equivalent code and is compiled into an assembly that’s equivalent to

a code-based definition. It’s a common misunderstanding that XOML is only used by WPF (where it is

called XAML) to create GUIs; XOML can be used for virtually any object definition that’s based on

composition, including user interfaces and workflows.

Chapter 2 – Basics of WF | 9

2.2.3 Conditions and rules

Beside of the workflow definition itself, a workflow often relies on conditional logic. Such conditions

can be defined in code or declaratively using XML. The latter option allows for dynamic changes of

rules at runtime without the need for recompilation, which would cause service interruption. The

creation of such a declarative rule condition is illustrated in Figure 5.

Figure 5 - Definition of a declarative rule

2.3 Compilation Workflow compilation is taken care of by the Visual Studio 2005 IDE using the MSBuild build system.

Under the hood, the Workflow Compiler wfc.exe is invoked to build a workflow definition. This

workflow-specific compiler validates a workflow definition and generates an assembly out of it. The

command-line output of the compiler is shown in Listing 1.

C:\Users\Bart\Documents\WF>wfc sample.xoml sample.cs

Microsoft (R) Windows Workflow Compiler version 3.0.0.0

Copyright (C) Microsoft Corporation 2005. All rights reserved.

Compilation finished with 0 warning(s), 0 error(s).

Listing 1 - Using the Windows Workflow Compiler

Additionally, WF supports dynamic compilation of XOML files using the WorkflowCompiler class, as

shown in Code 4. This allows a new workflow definition to be compiled and executed dynamically. An

applicable scenario is when the workflow designer is hosted in an application and an end-user

defines a workflow using that tool.

WorkflowCompiler compiler = new WorkflowCompiler();

WorkflowCompilerParameters param = new WorkflowCompilerParameters();

compiler.Compile(param, new string[] { "Sample.xoml" });

Code 4 - Invoking the workflow compiler at runtime

Chapter 2 – Basics of WF | 10

2.4 Activities The creation of workflows is based on the principle of composition. A set of activities is combined

into a workflow definition in either a sequential or event-driven state manner, in a similar way as GUI

applications are composed out of controls.

WF ships with a series of built-in activities that are visualized in the Visual Studio 2005 Toolbox while

working in a workflow-enabled project. An extensive discussion of those activities would lead us too

far, so we’ll just illustrate this toolbox in Figure 6. When required through the course of this work,

additional explanation of individual activities will be given in a suitable place.

Figure 6 - Built-in activities

Remark that a large portion of these activities has an equivalent in classic procedural programming,

such as if-else branches, while-loops, throwing exceptions and raising events. Others enable more

complex scenarios such as replication of activities, parallel execution and parallel event listening.

Chapter 2 – Basics of WF | 11

A powerful feature of WF and the corresponding tools is the ability to combine multiple activities

into another activity. Using this feature, one is able to create domain-specific activities that allow for

reuse within the same project or cross-project by creating a library of custom activities. This also

creates space for 3rd party independent software vendors (ISVs) to create a business out of

specialized workflow activity creation.

3 Hosting the workflow engine As mentioned a couple of times already, WF consists of a workflow engine that has to be hosted in

some kind of managed code process. Visual Studio 2005 provides support to create console

applications as well as other types of application projects with workflow support. A simple example

of a workflow host is displayed in Code 5 .

3.1 Initializing the workflow runtime and creating workflow instances The most important classes in this code fragment are WorkflowRuntime and WorkflowInstance. The

former one effectively loads the workflow runtime engine in the process; the latter one instantiates

an instance of the workflow in question. Generally spoken, there will be just one WorkflowRuntime

object per application domain, while there will be one workflow instance per invocation of the

workflow. For example, when using a web service host, the invocation of a certain web method could

trigger the creation of a workflow instance that then starts its own life. This effectively allows to

expose long-running stateful workflows through web services.

class Program

{

static void Main(string[] args)

{

using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())

{

AutoResetEvent waitHandle = new AutoResetEvent(false);

workflowRuntime.WorkflowCompleted +=

delegate(object sender, WorkflowCompletedEventArgs e)

{

waitHandle.Set();

};

workflowRuntime.WorkflowTerminated +=

delegate(object sender, WorkflowTerminatedEventArgs e)

{

Console.WriteLine(e.Exception.Message);

waitHandle.Set();

};

WorkflowInstance instance = workflowRuntime.CreateWorkflow

(typeof(WorkflowConsoleApplication1.Workflow1));

instance.Start();

waitHandle.WaitOne();

}

}

}

Code 5 - A console application workflow host

We won’t cover all possible hosts in here, as the principle is always the same. Nevertheless it’s worth

to note that the web services hosting model relies on the WebServiceInput, WebServiceOutput and

Chapter 2 – Basics of WF | 12

WebServiceFault activities to take data in and send data or faults out. Another interesting thing to

know is the lack of direct WCF support in WF, something that can only be established by manual

coding. According to Microsoft this is due to timing issues since WF was introduced relatively late in

the .NET Framework 3.0 development cycle. This shortcoming will be fixed in a future release.

3.2 Runtime services We already discussed the need for a series of runtime services in order to enable a set of workflow-

specific scenarios such as dehydration, which is the act of persisting runtime data when a workflow

goes idle. A brief overview of runtime services and their role is given below:

Scheduling Services are used to manage the execution of workflow instances by the

workflow engine. The DefaultWorkflowSchedulerService is used in non-ASP.NET applications

and relies on the .NET thread pool. On the other side, the ManualWorkflowSchedulerService

is used by ASP.NET hosts and interacts with the ASP.NET host process (e.g. the worker pool).

CommitWorkBatch Services enable custom code to be invoked when committing a work

batch. This kind of service is typically used in combination with transactions and is used to

ensure the reliability of the workflow-based application by implementing additional error-

handling code.

Persistence Services [2] play a central role in workflow instance hydration and dehydration.

Putting a workflow instance’s runtime data on disk is a requirement to be able to deal with

long-running workflows and to ensure scalability. By default SQL Server is used for

persistence.

Tracking Services [3] allow inspection of a workflow in flight by relying on events that are

raised by workflow instances. Based on a Tracking Profile, only the desired data is tracked by

the tracking service. WF ships with a SQL Server database tracking service out of the box.

Local Communication Services enable communication of data to and from a workflow

instance. For example, external events can be sent to a workflow instance and data can be

sent from a workflow instance to the outside world, based on an interface type. The type of

service is often referred to as “data exchange”.

Services can be configured through the XML-based application configuration file or through code. All

of these services are implementations of a documented interface in the Windows SDK which allows

for custom implementation, e.g. to persist state to a different type of database.

4 Dynamic updates Changing a workflow instance that’s in progress is one very desirable feature. In order to respond to

rapid changing business requirements, workflow changes at runtime are a common requirement.

With WF, it’s possible to modify a workflow instance both from the inside (i.e. the workflow’s code)

and the outside (i.e. the host application). This is accomplished by means of the WorkflowChanges

class which we’ll deal with quite a lot in this work.

Chapter 3 – Dynamic updates | 13

Chapter 3 – Dynamic updates

1 Introduction Due to the typical long-running character of workflows, it’s often desirable to be able to change a

workflow while it’s in flight. Common scenarios include the change of business requirements and

adaptation of company policies that need to be reflected in running workflow instances. Windows

Workflow Foundation recognizes this need and has support for dynamic updates built in. This way a

business process can be adapted dynamically, reducing downtime that would occur in typical

procedural code due to recompilations. Furthermore, workflow adaptation occurs on the level of

workflow instances, which reaches far beyond the flexibility one would have with procedural coding.

2 The basics In order to make changes to a workflow instance, one has to create an instance of the type

WorkflowChanges. This class takes the so-called transient workflow, which is the activity tree of the

running workflow, and allows application of changes to it. Once changes have been proposed, these

have to be applied to the running instance. At this point in time, activities in the tree can vote

whether or not they allow the change to occur. If the voting result is positive, changes are applied

and the workflow instance has been modified successfully. This basic process is reflected in code

fragment Code 6.

WorkflowChanges changes = new WorkflowChanges(this);

//Change the transient workflow by adding/removing/... activities

changes.TransientWorkflow.Activities.Add(...);

foreach (ValidationError error in changes.Validate())

{

if (!error.IsWarning)

{

string txt = error.ErrorText;

//Do some reporting and/or fixing

}

}

this.ApplyWorkflowChanges(changes);

Code 6 - Basic use of WorkflowChanges

2.1 Internal modification Code fragment Code 6 employs so-called internal modification of a workflow. Basically, this means

that the workflow change is applied from inside the workflow itself. So, the piece of code could be

living inside a CodeActivity’s ExecuteCode event handler for instance. Notice the constructor call to

WorkflowChanges uses ‘this’ to change the current workflow instance from the inside.

This type of modification can be useful in circumstances where the change can be anticipated

upfront. As an example, the workflow could add a custom activity somewhere in the activity tree

when certain conditions are fulfilled. Needless to say, a similar effect could be achieved by means of

an IfElseBranchActivity. However, one could combine this with reflection and instantiate the desired

Chapter 3 – Dynamic updates | 14

activity by loading it from an assembly. For instance, some custom order delivery processing activity

could be inserted in the workflow definition. If another shipping company requires a different flow of

activities, this could be loaded dynamically. Instead of bloating the workflow definition with all

possible side paths, dynamic adaptation could take care of special branches in a running workflow

tree. Furthermore, in combination with “Local Communication Services” the workflow instance could

retrieve information from the host that’s used in the decision process for dynamic adaptation.

2.2 External modification The opposite of internal modification is external modification, where a workflow instance is adapted

by the host application. Although this limits the flexibility for investigation of the internal workflow

instance state, one has full access to the context in which the workflow is operating. External stimuli

could be at the basis of the change, either automatically or manually (by business people). It’s safe to

state that external modification is the better candidate of the two to apply unanticipated changes.

External modification is applied using the same WorkflowChanges class that exposes the transient

workflow. In order to apply changes, the workflow instance has to be in a suitable state that allows

for dynamic adaptation. Typically, this is the suspended state, as illustrated in code fragment Code 7.

WorkflowRuntime workflowRuntime = new WorkflowRuntime();

workflowRuntime.WorkflowSuspended +=

delegate (object sender, WorkflowSuspendedEventArgs e) {

WorkflowChanges changes = new WorkflowChanges(e.WorkflowInstance);

//Change the transient workflow

changes.TransientWorkflow.Activities.Add(...);

foreach (ValidationError error in changes.Validate())

{

if (!error.IsWarning)

{

string txt = error.ErrorText;

//Do some reporting and/or fixing

}

}

e.WorkflowInstance.ApplyWorkflowChanges(changes);

};

Code 7 - External modification during workflow suspension

3 Changing the transient workflow As mentioned before, changes are applied on a transient workflow that represents a workflow

instance that is in flight. One obtains access to this transient workflow by using the WorkflowChanges

class. In this paragraph, we take a closer look at dynamic adaptation using this transient workflow.

3.1 Inserting activities A typical situation that might arise is the need for additional logic in a workflow’s definition. For

example, in an order processing scenario it might be desired to change running workflow instances

to incorporate a new step in the (typically long-running) order processing workflow.

Chapter 3 – Dynamic updates | 15

Adding a new activity to a transient workflow is accomplished by the Activities collection’s Add and

Insert methods. The former one adds an activity to the end of the activities collection which might

not be the best choice since a change typically employs locality. Using the Insert method, one has

more control over the position where the activity will be inserted.

However, this is often not enough to accomplish the required level of flexibility since the positions of

activities in the tree are often not known upfront. If changes are applied only once, position indices

could be easily inferred from the compiled workflow definition by human inspection. Things get

worse when the original definition changes and when dynamic updates are made accumulative. In

order to break the barrier imposed by this limitation, the GetActivityByName method was

introduced. For example, one could insert an additional step in a workflow right after the submission

of an order, as illustrated in code fragment Code 8.

WorkflowChanges changes = new WorkflowChanges(instance);

//Find the location to insert

Activity a = changes.TransientWorkflow.GetActivityByName("SubmitOrder");

int i = changes.TransientWorkflow.Activities.IndexOf(a) + 1;

SendMailActivity mailer = new SendMailActivity();

changes.TransientWorkflow.Activities.Insert(mailer, i);

instance.ApplyWorkflowChanges(changes);

Code 8 - Applying positional workflow changes

Composite activities, like a WhileActivity or an IfElseActivity, are a bit more difficult to change since

one needs to touch the “body” of these activities. Basically, the same principles apply, albeit a bit

lower in the tree hierarchy, typically using tree traversal code.

3.2 More flexible adaptations More powerful mechanisms can be employed to find the place where a change needs to be applied.

Because the Activities collection derives from the collection base classes in the .NET Framework, all

the power of System.Collections.Generic comes with it for free in the context of workflow. As an

example, one could use the FindAll method to locate activies that match a given predicate:

WorkflowChanges changes = new WorkflowChanges(instance);

//Find activities to adapt

List<Activity> lst = changes.TransientWorkflow.Activities.FindAll(

delegate(Activity a) {

InterestCalculatorActivity ica = a as InterestCalculatorActivity;

return (a != null && ica.Intrest > 0.05);

});

foreach (InterestCalculatorActivity ica in lst)

{

//Change parameterization, delete activites, add activities, etc.

}

Code 9 - Advanced activity selection

Of course it’s also possible to delete activities by means of the Remove method of the Activities

collection.

Chapter 3 – Dynamic updates | 16

3.3 Philosophical intermezzo – where encapsulation vanishes… All of this dynamic update magic comes at a cost. Encapsulation of activities and their composition in

a workflow is left behind once dynamic updates from the outside (“external modification”) are

considered. Therefore, one has to consider carefully whether or not dynamic updates from the

outside are really required. At the stage of applying external modification, the black box of the

workflow definition has to be opened in order to apply changes.

Whenever the original workflow type definition changes, workflow adaptation logic might break. It’s

important to realize that there is no strong-typed binding between the transient workflow and the

underlying workflow definition by default. In the code fragment Code 8, we’ve illustrated the use of

finding activities by name, which clearly is very dependent on internal details of the workflow

definition.

This being said, adaptation logic is often short-lived and created to serve just one specific type of

adaptation associated with a given version of a workflow definition (e.g. because the underlying

business process has changed – a change that could be incorporated statically in the next version of

the workflow definition). Furthermore, when creating adaptation logic on the fly – with the aid of a

graphical workflow designer – the verification of adaptation logic against the underlying workflow

definition becomes easier because of designer support, e.g. to find the names of activities in the

workflow.

3.4 Establishing data bindings Adding an activity somewhere in a workflow instance dynamically is one thing; connecting it to other

colleague activities is another thing. Essentially, we want to be able to bind input properties on the

newly inserted activity to some kind of data sources in the workflow definition; the other way

around, we want to be able to feed the output properties of our inserted activity back to the

surrounding workflow class for further consumption down the line.

To support data bindings by means of properties, WF introduces the concept of dependency

properties which act as a centralized store for workflow state. The code to create a sample activity

with dependency properties that allow for data binding is shown in code fragment Code 10.

public class DemoActivity : Activity

{

private double factor;

public DemoActivity() {}

public DemoActivity(double factor) { this.factor = factor; }

public static DependencyProperty InputProperty =

DependencyProperty.Register(

"Input",

typeof(double),

typeof(DemoActivity)

);

public double Input

{

get { return (double)base.GetValue(InputProperty); }

set { base.SetValue(InputProperty, value); }

}

Chapter 3 – Dynamic updates | 17

public static DependencyProperty OutputProperty =

DependencyProperty.Register(

"Output",

typeof(double),

typeof(DemoActivity)

);

public double Output

{

get { return (double)base.GetValue(OutputProperty); }

set { base.SetValue(OutputProperty, value); }

}

protected override ActivityExecutionStatus Execute(

ActivityExecutionContext executionContext)

{

double input = (double)GetValue(InputProperty);

double res = input * factor;

SetValue(OutputProperty, res);

return ActivityExecutionStatus.Closed;

}

}

Code 10 - Using dependency properties for data binding flexibility

This sample activity opens the doors to data binding. The use of the custom activity in the WF

designer in Visual Studio 2005 is shown in Figure 7.

Figure 7 - Design-time support for bindable properties

Binding support at design-time is visualized by the presence of a small blue-white information icon in

the right margin of the property names column. Clicking it allows to bind the property of the activity

to a property of the enclosing workflow definition, as shown in Figure 8.

However, when applying dynamic updates we want to be able to establish such a binding at runtime.

This is made possible through the use of the ActivityBind class in WF. An example of this class’s

usage is illustrated in code fragment Code 11.

Chapter 3 – Dynamic updates | 18

Figure 8 - Binding a property at design-time

For simplicity’s sake, this sample inserts a DemoActivity activity at the end of a sequential workflow that would otherwise return the same value as the input value. By feeding the workflow’s input to the dynamically added DemoActivity and the DemoActivity’s output in the reverse direction, we can adapt the original input value dynamically. This mechanism could be used to apply a correction factor to numerical data that flows though a workflow, e.g. to account for increased shipping costs. private void UpdateWorkflow(WorkflowInstance instance)

{

WorkflowChanges changes =

new WorkflowChanges(instance.GetWorkflowDefinition());

DemoActivity da = new DemoActivity(2.0);

ActivityBind bindInput = new ActivityBind("Workflow1", "Input");

da.SetBinding(

MultiDynamicChange.DemoActivity.InputProperty, bindInput);

ActivityBind bindOutput = new ActivityBind("Workflow1", "Output");

da.SetBinding(

MultiDynamicChange.DemoActivity.OutputProperty, bindOutput);

changes.TransientWorkflow.Activities.Add(da);

instance.ApplyWorkflowChanges(changes);

}

Code 11 - Establishing dynamic data binding

Chapter 3 – Dynamic updates | 19

4 Changing sets of workflow instances Rarely one needs to change just one instance of a workflow. If this is the case, additional logic can be

added to code fragment Code 7 in order to select the desired workflow instance, e.g. by means of its

unique workflow identifier (using the WorkflowInstance’s InstanceId property).

In the majority of scenarios however, changes have to be applied to all workflow instances in

progress or a certain subset of workflow instances based on some filter criterion. Furthermore, it’s

often desirable to be able to apply changes out of band, meaning that we don’t want to wait for a

suspension to happen due to some workflow instance state transition, for example because of

workflow instance state persistence. Based on human inspection of one workflow instance, business

people might decide to change a whole set of workflow instances and they want the change to

become effective right away.

To support this out-of-band multi-instance adaptation, the host application can query the runtime

for all running workflow instances, as illustrated in code fragment Code 12.

foreach (WorkflowInstance instance in wr.GetLoadedWorkflows())

UpdateWorkflow(instance);

Code 12 - Querying loaded workflows

Each WorkflowInstance can be queried for the underlying workflow definition using a method called

GetWorkflowDefinition that obtains a reference to the root activity. Based on the workflow instance

information, additional filtering logic can be applied to select a subset of workflow instances and/or

workflow instances of a given type. Once such a set of workflow instances is retrieved, changes can

be applied using WorkflowChanges. The WF runtime takes care of suspending the workflow prior to

making the dynamic update and resuming it right after the change was applied. Since changes are

applied on an instance-per-instance basis, exceptions thrown upon failure to change an instance only

affect one particular workflow adaption at a time, so rich failure reporting and retry logic can be

added if desired.

5 An instrumentation framework for workflow Now that we’re capable of applying changes to a workflow instance, we can take it to another level.

In this paragraph, we’ll discuss the possibility to create an instrumentation framework that allows for

dynamic aspect insertion into a workflow instance at runtime. A few usage scenarios include the

instrumentation of workflows for debugging purposes, adding logging logic, insertion of timing

constructs to measure average delays and waiting times for customers, dynamic addition of security

checks to protect certain workflow branches from being executed by non-authorized users, etc.

When considering instrumentation, we’re talking about a dynamic change to a fixed definition at

runtime. This makes WF’s dynamic update feature the ideal candidate to build an instrumentation

framework on. Essentially, we’ll instrument a workflow instance from the outside prior to starting it.

The instrumentation process can be driven by a wide variety of decision logic (e.g. based on input

values), additional data (such as threshold values), dynamic assembly loading using reflection (e.g. to

add more flexibility to the instrumentor itself), or ultimately another workflow itself.

Chapter 3 – Dynamic updates | 20

5.1 A simple logging service An example of a simple workflow instrumentor that tracks progress via a logging service is illustrated

in code fragments Code 13 and Code 14.

The former one shows the interface – and a simple implementation – for a logging service that’s used

in the logging activity itself to submit logging information to. Notice that this way of tracking is

slightly different from the built-in tracking service in WF. Tracking is either enabled or disabled, while

instrumentation can be done selectively, meaning that a specific subset of the created workflow

instances could be instrumented based on decision logic. Also, the “logging points” in the workflow

can be limited to a few interesting places in order to reduce overall overhead.

Code fragment Code 14 illustrates a logging activity that pushes data to the registered logger service

through its interface contract. We limit ourselves to simple (positional) tracking messages.

[ExternalDataExchange]

interface ILogger

{

void LogMessage(string message);

}

class Logger : ILogger

{

private TextWriter tw;

public Logger(TextWriter tw) { this.tw = tw; }

public void LogMessage(string message)

{

tw.WriteLine(message);

}

}

Code 13 - Local Communication Service definition and sample implementation for logging

class LogActivity : Activity

{

private string message;

public LogActivity() { }

public LogActivity(string message) { this.message = message; }

public string Message

{

get { return message; }

set { message = value; }

}

protected override ActivityExecutionStatus Execute

(ActivityExecutionContext executionContext)

{

executionContext.GetService<ILogger>().LogMessage(message);

return ActivityExecutionStatus.Closed;

}

}

Code 14 - A static message logging activity

Chapter 3 – Dynamic updates | 21

Logging becomes more interesting when we’re able to capture and format interesting data that’s

fueling the workflow instance, i.e. some kind of white box inspection on demand. Our generic

approach to data processing workflows in Chapter 4 makes such inspection even more manageable.

5.2 Instrumentation for dynamic updates Although instrumentation itself is driven by dynamic updates, it can be used as a starting point for

dynamic updates on its turn. So far, the discussed methodologies of adapting a workflow in progress

are fairly risky in a sense that there’s no guarantee that the service is in a state where a dynamic

update is possible. Also, it’s possible that an update is applied at an unreachable point in a workflow

instance, for example to an execution path already belonging the past.

Ideally, we’d like to have control over dynamic updates much like we had in the internal modification

scenario but without sacrificing the cleanliness of the underlying workflow definition. To state it

another way, we don’t want to poison the workflow definition with lots of “possibly we might need

to adapt at this point sometimes” constructs. After all, if we were building this kind of logic into the

workflow definition itself, we really do need to ask ourselves whether dynamic updates are required

in the first place.

Dynamic update instrumentation combines the best of both worlds. First of all, we’ll have exact

control over when an update takes place, by so-called suspension points. Secondly, we preserve the

flexibility of external modification to consume data and context available in the workflow hosting

application layer. Finally, it’s possible to have tight control over the internal workflow instance state,

much like we have in pure internal modification.

Let’s start with an outline of the steps taken to establish dynamic update instrumentation:

1. When a workflow instance is created, suspension points are added to the workflow

definition based on configuration. All these points are uniquely identified.

2. The workflow host application responds to workflow instance suspension events and – based

on configuration – decides whether or not action has to be taken at that point in time.

a. If an update is demanded, different options are available:

i. Slight changes that don’t require access to internal state can rely on external

modification.

ii. More advanced changes that require internal state can be realized by

injecting a custom activity that carries update logic into the workflow

instance from the outside.

b. Regardless of the application of an update, the workflow instance is resumed.

5.2.1 Instrumentation with suspension points

Step one involves the instrumentation itself. During this step, SuspendActivity activities are added to

the workflow at configurable places. This realizes our requirement to have control over the timing of

workflow modifications. For example, we could inject a suspension point right before an order is

submitted to a courier service to account for possible last-minute market changes or courier service

contract changes. Essentially, we’re creating a callback mechanism at a defined point in time, without

having to change the original workflow definition and allowing for flexible configuration.

Code fragment Code 15 shows simple instrumentation logic.

Chapter 3 – Dynamic updates | 22

WorkflowInstance instance =

workflowRuntime.CreateWorkflow(typeof(SampleWorkflow));

WorkflowChanges c = new WorkflowChanges(instance.GetWorkflowDefinition());

SuspendActivity s = new SuspendActivity("suspend1");

s.Error = "between_1_and_2";

int pos = c.TransientWorkflow.Activities.IndexOf(

c.TransientWorkflow.GetActivityByName("demoCode1"));

c.TransientWorkflow.Activities.Insert(pos + 1, s);

instance.ApplyWorkflowChanges(c);

instance.Start();

Code 15 - Injecting a suspension point

Needless to say, this code can be extended to inject multiple suspension points in a configuration-

driven manner. Notice that every suspension point needs to have a unique name (e.g. “suspend1”)

which can be generated at random. Next, an Error property value has to be set. This will be used in

the decision logic of step two (see next section) to identify the suspension location. Error really is a

bit of a misnomer, since workflow suspension doesn’t necessarily imply that an error has occurred. In

our situation it really means that we want to give the host application a chance to take control at

that point in time. Finally, the suspension point has to be inserted at the right place in the workflow,

e.g. before or after an existing activity in the workflow. In our sample, the suspension point is

injected after an activity called “demoCode1”. The original sample workflow and the corresponding

instrumented one are depicted in Figure 9.

Figure 9 - Dynamic workflow instrumentation before (left) and after (right)

When making this more flexible, we end up with a configurable table of suspension point injections

that consists of tuples {Before/After, ActivityName}. Based on this, instrumentation can be applied in

a rather straightforward iterative fashion. Changing this table, for instance stored in a database, at

runtime will automatically cause subsequent workflow instances to be instrumented accordingly.

Chapter 3 – Dynamic updates | 23

If desired, existing workflows could be (re)instrumented too at database modification time, keeping

in mind that some suspension points won’t be hit on some workflow instances because of their

temporal nature. This effect could be undesired since intermediate states could be introduced, e.g.

when implicit assumptions are made on different suspension points and actions. An example of this

pathological situation is instrumentation and adaptation for performance measurement. Consider

the situation in which we want to inject a “start counter” activity on position A and a “stop counter”

activity on position B. If position A already belongs to the past in some workflow instance, an injected

“stop counter” activity will produce invalid results (or worse, it might crash the workflow instance)

since the corresponding “start counter” activity won’t ever be hit.

5.2.2 Responding to suspensions

In the previous step, we took the steps required to set up “rendez-vous points” where the workflow

instance will be suspended, allowing the surrounding host application to take control. This is the

place where further actions can be taken, such as modifying the workflow. A business-oriented

sample usage is the injection of additional order processing validation steps due to company policy

changes before the workflow continues to submit the order.

Code fragment Code 16 outlines the steps taken in the workflow host to respond to the suspension

point introduced earlier by Code 15. Observe the consumption of the e.Error property in the decision

making process to trace back the suspension point itself.

workflowRuntime.WorkflowSuspended +=

delegate(object sender, WorkflowSuspendedEventArgs e)

{

switch (e.Error)

{

case "between_1_and_2":

//adaptation logic goes here

Console.WriteLine("Instrumentation in action!");

e.WorkflowInstance.Resume();

break;

}

};

Code 16 - Adaptation in response to a suspension point

Again, additional flexibility will be desirable since we can’t recompile the host application to embed

this switching logic. Different approaches exist ranging from simple adaptations to far more complex

ones. An overview:

The tuple {Before/After, ActivityName} could be linked to a set of tuples containing actions to

be taken when the suspension point is hit, optionally with additional conditions. Such an

“action tuple” could look like ,Before/After, ActivityName, ActivityType- meaning that an

activity of type ActivityType has to be inserted before or after ActivityName in the workflow.

Evaluation of conditions could be expressed using the rule engine in WF and by serializing the

rules in an XML file.

A more generic approach would use reflection and application domains to load the switching

logic dynamically. For each suspension point, the “response logic type” is kept as well (see

tuple representation in Code 17). At runtime, these response logic types (which implement

the interface of Code 18) – are loaded dynamically (Code 19). When the WorkflowSuspended

Chapter 3 – Dynamic updates | 24

event is triggered, the “Error” event argument is mapped to the corresponding “response

logic type” that gets a chance to execute. This is illustrated in Code 20.

enum InstrumentationType { Before, After }

class InstrumentationPoint {

private Guid id;

private InstrumentationType t;

private string p;

private string a;

public Guid Id { get {return id;} set {id = value;} }

public InstrumentationType Type { get {return t;} set {t = value;} }

public string Point { get {return p;} set {p = value;} }

public string ActionType { get {return a;} set {a = value;} }

}

Code 17 - Type definition for instrumentation tuples

interface IAdaptationAction : IDisposable {

void Initialize(WorkflowInstance instance);

void Execute();

}

Code 18 - A generic adaption action interface

private Dictionary<string, IAdaptationAction> actions =

new Dictionary<string, IAdaptationAction>();

...

WorkflowInstance instance =

workflowRuntime.CreateWorkflow(typeof(SampleWorkflow));

WorkflowChanges c = new WorkflowChanges(instance.GetWorkflowDefinition());

int i = 0;

foreach (InstrumentationPoint ip in GetInstrumentationPoints())

{

SuspendActivity s = new SuspendActivity("suspend" + ++i);

s.Error = ip.Id.ToString();

int pos = c.TransientWorkflow.Activities.IndexOf(

c.TransientWorkflow.GetActivityByName(ip.Point));

c.TransientWorkflow.Activities.Insert(

ip.Type == InstrumentationType.After ? pos + 1 : pos, s);

actions.Add(ip.Id.ToString(),

(IAdaptationAction) Assembly.Load(ip.Assembly).GetType(ip.Type));

}

instance.ApplyWorkflowChanges(c);

instance.Start();

Code 19 - Fully dynamic instrumentation

Notice that the code fragments above need robust error handling to keep the workflow runtime from

crashing unexpectedly when creating workflow instances. The dynamic nature of the code makes

compile-time validation impossible, so runtime exceptions will be thrown when invalid parameters

are passed to the instrumentor. For example, activities with a given name could be non-existent or

the instrumentation action type could be unloadable. One might consider running a different

WorkflowRuntime instance to check the validity of instrumentation logic using dummy workflow

Chapter 3 – Dynamic updates | 25

instances prior to pushing it to the production runtime environment. Furthermore, caching and

invalidation logic for the configuration will improve performance significantly since reflection is

rather slow. A final note embodies the use of dynamic assembly loading in a rather naïve way. In the

current version of the CLR, loading an assembly in an application domain is irreversible [4]. This could

lead to huge resource consumption by short-lived “adaption actions”. A better approach would be to

load the assemblies in separate application domains (for example on a per-batch basis for workflow

adaptations) which can be unloaded as elementary units of isolation in the CLR. However, to cross

the boundaries of application domains, techniques like .NET Remoting will be required. Because of

this increased complexity we won’t elaborate on this, but one should be aware of the risks imposed

by naïve assembly loading.

workflowRuntime.WorkflowSuspended +=

delegate(object sender, WorkflowSuspendedEventArgs e)

{

using (IAdaptationAction action = actions[e.Error])

{

action.Initialize(e.WorkflowInstance);

action.Execute();

e.WorkflowInstance.Resume();

}

};

Code 20 - Dynamic adaptation action invocation at suspension points

As an example, we’ll build a simple adaptation action (Code 21) that corresponds to the one shown in

Code 11, including the data binding functionality. However, encapsulation in an IAdaptationAction

type allows for more flexible injection when used together with the instrumentation paradigm.

public class TimesTwoAdaptor : IAdaptationAction

{

private WorkflowInstance instance;

public void Initialize(WorkflowInstance instance)

{ this.instance = instance; }

public void Execute()

{

WorkflowChanges changes =

new WorkflowChanges(instance.GetWorkflowDefinition());

DemoActivity da = new DemoActivity(2.0);

ActivityBind bindInput = new ActivityBind("Workflow1", "Input");

da.SetBinding(DemoActivity.InputProperty, bindInput);

ActivityBind bindOutput = new ActivityBind("Workflow1", "Output");

da.SetBinding(DemoActivity.OutputProperty, bindOutput);

changes.TransientWorkflow.Activities.Add(da);

instance.ApplyWorkflowChanges(changes);

}

public void Dispose() {}

}

Code 21 - Encapsulation of a workflow adaptation

Chapter 3 – Dynamic updates | 26

5.3 Advanced instrumentation logic In code fragment Code 19 we’ve shown a simple way to instrument a workflow instance based on

positional logic (i.e. “after” or “before” a given activity). However, this logic won’t work with more

complex workflow definitions which do not have a linear structure. For example, when a workflow

contains an IfElseActivity, the workflow definition’s Activities collection won’t provide access to the

activities nested in the branches of the IfElseActivity. In order to instrument the workflow at these

places too, one will need to extend the code to perform a recursive lookup, or alternatively the

activity name reference could take an XPath-alike form to traverse the tree up to the point where

instrumentation is desired.

An example of a recursive instrumentation implementation with user interaction is shown in code

fragment Code 22.

delegate void Instrumentor(Activity activity,

WorkflowChanges changes,

bool before, bool after);

public void Instrument(Activity activity,

WorkflowChanges changes,

Instrumentor DoInstrument) {

char r;

do {

Console.Write(

"Instrument {0}? (B)efore, (A)fter, (F)ull, (N)one ",

activity.Name);

r = Console.ReadLine().ToLower()[0];

} while (r != 'b' && r != 'a' && r != 'f' && r != 'n');

if (r != 'n')

DoInstrument(activity, changes,

r == 'b' || r == 'f', r == 'a' || r == 'f');

if (activity is CompositeActivity)

foreach (Activity a in ((CompositeActivity)activity).Activities)

Instrument(a, changes, DoInstrument);

}

Code 22 - Recursive workflow definition traversal for instrumentation

Using the Instrumentator delegate, it becomes possible to adapt the instrumentation logic at will.

This further increases the overall flexibility of the instrumentation framework. This way, one could

instrument with suspension points but also with, for instance, logging activities. Furthermore, one

could get rid of the Console-based interaction by querying some data source through a querying

interface to get to know whether or not an activity has to be instrumented.

This form of recursion-based instrumentation is most useful when lots of instrumentations will be

required and if the underlying querying interface doesn’t cause a bottleneck. On the other side,

when only a few of sporadic instrumentations are likely to be requested and when workflow

definitions are quite huge, it might be a better idea to use an approach based on instrumentation

points without having to traverse the whole workflow definition tree.

Last but not least, notice that instrumentations can be performed on already-instrumented workflow

instances too, allowing for accumulative instrumentations.

Chapter 3 – Dynamic updates | 27

5.4 Conclusion As we’ve shown in this paragraph, dynamic instrumentation of workflows through the use of

suspension points is a very attractive way to boost the flexibility of WF. This technique combines the

goodness of external modifications on the hosting layer – having access to contextual information –

with the horsepower of internal modifications that have a positional characteristic in a workflow

definition allowing for “just-in-time” adaption. To realize the flexibility of this mechanism, one should

think of the result obtained by encapsulating the instrumentation logic itself in an “adaptation

action” (the snake swallowing its own tail).

During the previous discussion, one might have observed a correspondence to the typical approaches

employed in the aspect-oriented development, such as crosscutting. Indeed, as we’ll show further in

this work, combining dynamic adaptation with generic components for “aspects” like logging,

authorization, runtime analysis, etc. will open up the door for highly-dynamic systems that allow for

runtime modification and production debugging to a certain extent.

The primary drawback to this methodology is the invasive nature of dynamic activity injections that

can touch the inside of the workflow instance, effectively breaking encapsulation. Notice that the WF

architecture using dependency properties still hides the real private members of workflow types, so

unless you’re reflecting against the original workflow type you won’t be able to get access to private

members from an object-oriented (OO) perspective.

However, philosophically one could argue that the level of abstraction that WF enables is one floor

higher than the abstraction and encapsulation level in the world of OO. Based on this argument,

injection of activities in a workflow instance really is a breakage of encapsulation goals. Nevertheless,

when used with care and with rigorous testing in place it shouldn’t hurt. As a good practice,

developers should adopt strict rules when writing injections since there’s not much the runtime can

do to protect them against malicious actions. Flexibility at the cost of increased risk…

6 A few practical uses of instrumentation In this paragraph, we present a few practical usage scenarios for workflow instrumentation. First,

let’s define instrumentation more rigorously:

Workflow instrumentation is the action of adding activities dynamically on pre-defined places in the

activity tree of a newly created workflow instance before it’s started.

With this definition, we can suggest a few uses for instrumentation in practice:

Adding logging to a workflow. This can be used to gather diagnostic information, for example

to perform production debugging.

Measurement of service times can be accomplished by adding a “measurement scope” to

the workflow instance, i.e. surrounding the region that needs to be times with a “start to

measure” activity and a “stop to measure” activity.

Protecting portions of a workflow from unauthorized access. To do this, the workflow host

application layer could add some kind of access denied activities in places that are disallowed

for the user that launches the workflow instance. This decouples the authorization aspect

from the internals of a workflow definition.

Chapter 3 – Dynamic updates | 28

6.1 Logging As a first example, we’ll create a logger that can be added dynamically by means of instrumentation.

It allows data to be captured from the workflow instance in which the logger is injected by means of

dependency properties. To illustrate this, consider a workflow defined as in Code 23.

public sealed partial class AgeChecker : SequentialWorkflowActivity

{

public AgeChecker() { InitializeComponent(); }

static DependencyProperty FirstNameProperty =

DependencyProperty.Register("FirstName", typeof(string),

typeof(AgeChecker));

static DependencyProperty AgeProperty =

DependencyProperty.Register("Age", typeof(int),

typeof(AgeChecker));

public string FirstName {

get { return (string)this.GetValue(FirstNameProperty); }

set { this.SetValue(FirstNameProperty, value); }

}

public int Age {

get { return (int)this.GetValue(AgeProperty); }

set { this.SetValue(AgeProperty, value); }

}

private void sayHello_ExecuteCode(object sender, EventArgs e) {

Console.ForegroundColor = ConsoleColor.Green;

Console.WriteLine("Welcome {0}!", FirstName);

Console.ResetColor();

}

}

Code 23 - A simple workflow definition using dependency properties

This workflow definition consists of a single “sayHello” CodeActivity that prints a message to the

screen. Assume we want to check that the Age property has been set correctly; in order to do so,

we’d like to inject a logging activity into the workflow instance in order to inspect the internal values

at runtime. We’ll accomplish this by means of instrumentation. In code fragment Code 24 you can

see the definition of such a simple logging activity which has a few restrictions we’ll talk about in a

minute.

public class LoggingActivity: Activity

{

private string message;

private string[] args;

public LoggingActivity() { }

public LoggingActivity(string message, params string[] args)

{

this.message = message;

this.args = args;

}

protected override ActivityExecutionStatus Execute

(ActivityExecutionContext executionContext)

{

Chapter 3 – Dynamic updates | 29

ArrayList lst = new ArrayList();

foreach (string prop in args)

lst.Add(Parent.GetValue(

DependencyProperty.FromName(prop, Parent.GetType())));

executionContext.GetService<ILogger>().LogMessage(

message, lst.ToArray());

return ActivityExecutionStatus.Closed;

}

}

Code 24 - A simple LoggingActivity

This activity takes two constructor parameters: a message to be logged (in the shape of a “formatting message” as used in .NET) and a parameter list with the names of the properties that need to be fed into the formatting string. Inside the Execute method, the activity retrieves the values of the specified properties using the dependency properties of the parent. Notice that this forms a first limitation, since nesting of composite activities will make the search for the right dependency properties to get a value from more difficult. This can be solved using a recursive algorithm that walks up the activity tree till the Parent property is null. Nevertheless, for the sake of the demo this definition is sufficient. Next, the message together with the retrieved parameters is sent to an ILogger (see Code 25) service using Local Communication Services. [ExternalDataExchange]

interface ILogger

{

void LogMessage(string format, params object[] args);

}

Code 25 - The ILogger interface for LoggingActivity output

Now, to do the instrumentation it’s just a matter of injecting well-parameterized LoggingActivity instances wherever required and writing an ILogger implementation, as shown in Code 26. Needless to say, the ILogger implementation can be implemented in any fashion the user sees fit, e.g. to write the logging messages to a database or to the Windows Event Log. Notice that the host for our workflow runtime needs to be aware of the eventuality that logging might be required somewhere in the future. The registration of the logger implementation and the instrumentation logic are shown in Code 27. class Logger : ILogger

{

private TextWriter tw;

public Logger(TextWriter tw) { this.tw = tw; }

public void LogMessage(string format, params object[] args)

{

tw.WriteLine(format, args);

}

}

Code 26 - Implementation of a logger that uses a TextWriter

using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())

{

ExternalDataExchangeService eds = new ExternalDataExchangeService();

workflowRuntime.AddService(eds);

eds.AddService(new Logger(Console.Out));

Chapter 3 – Dynamic updates | 30

Dictionary<string, object> args = new Dictionary<string, object>();

args.Add("FirstName", "Bart");

args.Add("Age", 24);

WorkflowInstance instance = workflowRuntime.CreateWorkflow(

typeof(WFInstrumentation.AgeChecker), args);

WorkflowChanges c =

new WorkflowChanges(instance.GetWorkflowDefinition());

c.TransientWorkflow.Activities.Insert(0,

new LoggingActivity(

"Hello {0}, you are now {1} years old.", "FirstName", "Age"));

instance.ApplyWorkflowChanges(c);

instance.Start();

...

Code 27 - Instrumentation of a workflow instance with the logger

In here, we added the logger to the top of the workflow instance’s activity tree. On top of the code fragment you can see the registration of the logger through Local Communication Services. For sake of the demo, we’ll just print the logging messages to the console.

6.2 Time measurement Another example of dynamic workflow instrumentation is the injection of a set of timing blocks,

acting as a stopwatch. In this case, we want to inject two activities around a region we want to

measure in time. The first activity will start the timer; the second one will stop the timer and report

the result.

Again, the structure of the instrumentation is the same. First, we’ll write the StopwatchActivity as

shown in Code 28. Next, an interface will be provided to allow the activity to communicate with the

host environment using the Local Communication Services (Code 29).

public class StopwatchActivity : Activity

{

private StopwatchAction action;

private string watcher;

private static Dictionary<string, Stopwatch> watches

= new Dictionary<string, Stopwatch>();

public StopwatchActivity() { }

public StopwatchActivity(string watcher, StopwatchAction action)

{

this.watcher = watcher;

this.action = action;

if (!watches.ContainsKey(watcher))

watches.Add(watcher, new Stopwatch());

}

protected override ActivityExecutionStatus

Execute(ActivityExecutionContext executionContext)

{

if (action == StopwatchAction.Start)

watches[watcher].Start();

Chapter 3 – Dynamic updates | 31

else

{

watches[watcher].Stop();

TimeSpan ts = watches[watcher].Elapsed;

watches.Remove(watcher);

executionContext.GetService<IStopwatchReporter>()

.Report(WorkflowInstanceId, watcher, ts);

}

return ActivityExecutionStatus.Closed;

}

}

public enum StopwatchAction { Start, Stop }

Code 28 - Time measurement using a StopwatchActivity

[ExternalDataExchange]

public interface IStopwatchReporter

{

void Report(Guid workflowInstanceId,

string watcher, TimeSpan elapsed);

}

Code 29 - Reporting measurement results through an interface for Local Communication Services

Finally, the instrumentation can be done by implementing the IStopwatchReporter interface (Code

30) and injecting the measurement points in the workflow instance (Code 31).

class StopwatchReporter : IStopwatchReporter

{

public void Report(Guid workflowInstanceId,

string watcher, TimeSpan elapsed) {

Console.WriteLine("{0}: {1} - {2}", workflowInstanceId,

watcher, elapsed.TotalMilliseconds);

}

}

Code 30 - Implementation of a stopwatch timing reporter

using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())

{

ExternalDataExchangeService eds = new ExternalDataExchangeService();

workflowRuntime.AddService(eds);

eds.AddService(new StopwatchReporter());

WorkflowInstance instance = workflowRuntime.CreateWorkflow(

typeof(WFInstrumentation.Workflow1));

WorkflowChanges c =

new WorkflowChanges(instance.GetWorkflowDefinition());

c.TransientWorkflow.Activities.Insert(1,

new StopwatchActivity("measureDelay", StopwatchAction.Stop));

c.TransientWorkflow.Activities.Insert(0,

new StopwatchActivity("measureDelay", StopwatchAction.Start));

instance.ApplyWorkflowChanges(c);

instance.Start();

...

Code 31 - Instrumentation of a workflow with stopwatch timers

Chapter 3 – Dynamic updates | 32

As an example we apply the instrumentation to a simple workflow that contains a single delay

activity set to a few seconds, as shown in Figure 10.

Figure 10 - Sample workflow for stopwatch testing

This sample could be extended easily to allow for more flexibility. For instance, a third stopwatch

action could be introduced to report the result. This would allow for cumulative timings where the

timer is started and stopped on various places, while the result is only reported once when the

activity is told to do so by means of the action parameter. We could also allow for reuse of the same

Stopwatch instance by providing a reset action that calls the Stopwatch instance’s Reset method.

6.3 Authorization The next sample shows how instrumentation can be applied to workflow instances to protect certain

paths from unauthorized access. In some cases, authorization might be built-in to the workflow

definition itself, for example using Authorization Manager [5], but in other cases it might be more

desirable to inject intra-workflow authorization checks at a later stage in the game.

In order to reject access to a certain portion of a workflow we’ll use a ThrowActivity that throws an

AccessDeniedException as defined in Code 32.

[Serializable]

public class AccessDeniedException : Exception

{

public AccessDeniedException() { }

public AccessDeniedException(string message)

: base(message) { }

public AccessDeniedException(string message, Exception inner)

: base(message, inner) { }

protected AccessDeniedException(

System.Runtime.Serialization.SerializationInfo info,

System.Runtime.Serialization.StreamingContext context)

: base(info, context) { }

}

Code 32 - AccessDeniedException definition for authorization barriers

Now consider a workflow definition like the one shown in Figure 11. However, in a non-instrumented

workflow we don’t want to have the accessDenied activity in place yet. As a matter of fact, a

Chapter 3 – Dynamic updates | 33

definition like the one shown below won’t have any value since it’s impossible to place an expensive

order at all. Instead, we want the “authorization barrier” to be inserted on the instance level.

Figure 11 - An instrumented workflow with an authorization barrier

Our goal is to add this one dynamically through an external modification driven by instrumentation at

the host level. The code shown in Code 33 illustrates how to accomplish this goal. Notice that this

sample also illustrates how to instrument a composite activity, in this case the left branch of the

IfElseActivity.

Dictionary<string, object> args = new Dictionary<string, object>();

args.Add("OrderValue", 15000);

WorkflowInstance instance = workflowRuntime.CreateWorkflow(

typeof(WFInstrumentation.Workflow3), args);

WorkflowChanges c = new WorkflowChanges(instance.GetWorkflowDefinition());

ThrowActivity t = new ThrowActivity();

t.FaultType = typeof(AccessDeniedException);

((CompositeActivity)c.TransientWorkflow.GetActivityByName("expensive"))

.Activities.Insert(0, t);

instance.ApplyWorkflowChanges(c);

instance.Start();

Code 33 - Instrumentation of a workflow instance with an access authorization guard

Chapter 3 – Dynamic updates | 34

The instrumentation of a workflow instance with the code above results in an exception being

thrown when the left branch is hit, for example when the order value exceeds 10000. Notice that this

exception can be caught by adding fault handlers to the workflow definition, as shown in Figure 12.

Figure 12 - Handling AccessDeniedException faults in the workflow

However, the presence of such handlers assumes prior knowledge of the possibility for the workflow

to be instrumented with an authorization barrier, which might be an unlikely assumption to make.

Nevertheless, situations can exist where it’s desirable to have the entire authorization mechanism

being built around dynamic instrumentations. In such a scenario, adding fault handlers in the

workflow definition perfectly makes sense.

An alternative approach to authorization would be to add static barriers at development time, which

rely on Local Communication Services to query the host whether or not access is allowed at a certain

point in the workflow tree, based on several indicators like the user identity and various internal

values. This approach could be extended to the dynamic case by providing a generic “workflow

inspector” activity that can be injected through instrumentation. Such an activity would be

parameterized with a list of names for the to-be-retrieved properties and would take an approach

like the one used in the logging sample (Code 24) to collect the values. Using Local Communication

Services, these values can be sent to the host where additional logic calculates whether or not access

is allowed. If not, a dynamic update is applied in order to put an authorization barrier in place.

Chapter 3 – Dynamic updates | 35

6.4 Production debugging and workflow inspection The proposal of a generic “workflow inspector”, as in the previous paragraph, would provide

additional benefits, for example while debugging a workflow type in the production stage when

issues are observed. In this final sample, we’ll illustrate how one can build a generic workflow

inspector utility that can be added dynamically through instrumentation, for instance to assist in

production debugging scenarios. This tool could be used as a replacement for the logging activity we

saw earlier, by putting the text formatting logging logic in the host application and consuming the

data provided by the inspector.

Let’s start by defining what an inspection looks like. As we want to keep things as generic as possible,

we’ll make it possible to inspect “classic” properties, dependency properties as well as fields. Next, a

format is needed to point to the inspection target, for example an activity that can be nested deeply

in the activity tree. To serve this goal, we’ll introduce a forward slash separated path notation that

starts from the parent of the inspector activity itself. For example, if we inject an inspector in the

expensive branch from Figure 11, the root path (“”) will be the expensive branch itself. By using the

path “..” we’ll point at the isExpensive activity. To point at the cheap activity, we’ll need to use

“../cheap” as a path. Finally, an inspection will also hold the name of the inspection target, which can

be a (dependency) property or a field. The result is shown below in code fragment Code 34.

[Serializable]

public class Inspection

{

private InspectionTarget target;

private string path;

private string name;

public Inspection(InspectionTarget target, string path, string name)

{

this.target = target;

this.path = path;

this.name = name;

}

public InspectionTarget Target

{

get { return target; } set { target = value; }

}

public string Path

{

get { return path; } set { path = value; }

}

public string Name

{

get { return name; } set { name = value; }

}

public override string ToString()

{

return String.Format("({1}) {0}:{2}", path,

Enum.GetName(typeof(InspectionTarget), target)[0], name);

}

}

Chapter 3 – Dynamic updates | 36

public enum InspectionTarget

{

Field,

Property,

DependencyProperty

}

Code 34 - Defining type for a single workflow inspection

Notice the type needs to be marked as Serializable in order to be used correctly by Local

Communication Services when reporting the result. The reporting interface is shown in code

fragment Code 35 and is pretty straightforward. Basically, we’ll map an Inspection on the results of

that inspection. This way, the user of the inspector can trace back the retrieved values to the

inspection target.

[ExternalDataExchange]

interface IInspectorReporter

{

void Report(Guid workflowInstanceId, string inspection,

Dictionary<Inspection, object> results);

}

Code 35 - Inspector reporting interface

This interface reports the results with the corresponding inspection name (set by the inspector

activity’s constructor, see further) and the workflow instance ID.

Finally, let’s take a look at the inspector activity itself. The code is fairly easy to understand and

performs some activity tree traversal (see method FindActivity) and a bit of reflection stuff in order

to get the values from fields and properties. Getting the value from a dependency property was

illustrated earlier when we talked about a logging activity.

public class InspectorActivity : Activity

{

private string inspection;

private Inspection[] tasks;

public InspectorActivity() { }

public InspectorActivity(string inspection, Inspection[] tasks)

{

this.inspection = inspection;

this.tasks = tasks;

}

protected override ActivityExecutionStatus Execute

(ActivityExecutionContext executionContext)

{

Dictionary<Inspection, object> lst =

new Dictionary<Inspection, object>();

foreach (Inspection i in tasks)

{

Activity target = FindActivity(i.Path);

switch (i.Target)

{

case InspectionTarget.Field:

Chapter 3 – Dynamic updates | 37

lst.Add(i, target.GetType().GetField(i.Name)

.GetValue(target));

break;

case InspectionTarget.Property:

lst.Add(i, target.GetType().GetProperty(i.Name)

.GetValue(target, null));

break;

case InspectionTarget.DependencyProperty:

lst.Add(i, target.GetValue(DependencyProperty

.FromName(i.Name, target.GetType())));

break;

}

}

executionContext.GetService<IInspectorReporter>()

.Report(WorkflowInstanceId, inspection, lst);

return ActivityExecutionStatus.Closed;

}

private Activity FindActivity(string path)

{

Activity current = this.Parent;

foreach (string p in path.Split('/'))

{

if (p == null || p.Length == 0)

break;

else if (p == "..")

current = current.Parent;

else

current = ((CompositeActivity)current).Activities[p];

}

return current;

}

}

Code 36 - Activity for dynamic workflow inspection

As an example, we’ll replace the logging instrumentation as shown in Code 27 by an equivalent

inspection instrumentation. First, we’ll need to implement the IInspectorReporter interface, which

we do in Code 37.

class InspectorReporter : IInspectorReporter

{

public void Report(Guid workflowInstanceId, string inspection,

Dictionary<Inspection, object> results)

{

Console.WriteLine("Report of inspection {0}", inspection);

foreach (Inspection i in results.Keys)

Console.WriteLine("{0} - {1}", i, results[i]);

}

}

Code 37 - Implementing the inspector reporting interface

Next, we should not forget to hook up this inspector service at the workflow runtime level. This is

done in an equivalent way as shown previously in Code 27. Finally, the instrumentation itself has to

be performed, which is outlined in code fragment Code 38.

Chapter 3 – Dynamic updates | 38

WorkflowChanges c = new WorkflowChanges(instance.GetWorkflowDefinition());

Inspection[] tasks = new Inspection[2];

tasks[0] = new Inspection(InspectionTarget.DependencyProperty, "",

"Age");

tasks[1] = new Inspection(InspectionTarget.DependencyProperty, "",

"FirstName");

InspectorActivity inspect =

new InspectorActivity("FirstName_Age", tasks);

c.TransientWorkflow.Activities.Insert(0, inspect);

instance.ApplyWorkflowChanges(c);

Code 38 - Instrumentation of a workflow with the inspector

The result of this inspection when applied on the workflow defined in Code 23 is shown in Figure 13.

Figure 13 - The workflow inspector at work

Of course we don’t want to shut down the workflow runtime in order to attach a workflow inspector

to an instance. Therefore, the hosting application needs to be prepared to accept debugging jobs

when demanded. A first scenario consists of attaching a “debugger” to an existent workflow instance

that is specified by its instance ID. Then the host application can accept calls to debug a workflow

instance, obtain the instrumentation logic (for example by dynamically loading the instrumentation

job that injects the “workflow inspector” or by applying an auto-generated inspection activity that

reflects against all the available properties of the workflow instance in order to get access to it).

Another scenario is to instrument workflow instances at creation time to capture information about

newly created instances and what’s going on inside these instances.

From the elaboration above, one might see a pretty big correspondence to the definition of tracking

services. It’s the case indeed that there is quite some overlap with the goals of tracking in WF since

serialized workflow information can be used to inspect workflow instance internals. However, using

dynamic instrumentation, one can get a more fine-grained control over the inspections itself and the

places where these inspections happen. One could take all of this a step further by using the

inspections as a data mining tool to gather information about certain internal values that occur

during processing, which might otherwise remain hidden. Last but not least, where tracking services

impose a fixed overhead till the workflow runtime is reconfigured to get rid of tracking, dynamic

instrumentation does not and, hence, is more applicable when sporadic inspections are required.

Chapter 3 – Dynamic updates | 39

7 Tracking in a world of dynamism – the WorkflowMonitor revisited In our introduction to WF, we briefly mentioned the existence of so-called tracking services:

Tracking Services allow inspection of a workflow in flight by relying on events that are raised by

workflow instances. Based on a Tracking Profile, only the desired data is tracked by the tracking

service. WF ships with a SQL Server database tracking service out of the box.

Being able to capture the progress of a workflow instance is one thing, consuming the tracking data is

even more desirable. To illustrate how to visualize tracking data, the Windows SDK ships with a WF

sample called the WorkflowMonitor. The tool allows inspecting workflows that are still running or

have terminated their lifecycle.

As a matter of fact, it serves as a sample to two core WF features: tracking and workflow designer re-

hosting. The latter one empowers developers to embed the workflow designer, which is also used by

the WF Extensions for Visual Studio 2005, in their own applications. For the WorkflowMonitor

sample, the designer serves as a convenient way to visualize a workflow instance in a read-only

manner. Figure 14 shows the WorkflowMonitor in action.

Figure 14 - The WorkflowMonitor in action

In our world of dynamic workflow updates, the WorkflowMonitor as it ships with the SDK falls short

since it’s not able to visualize dynamic changes in real-time when a workflow instance is running. In

order to overcome this limitation, we made a simple change to the WorkflowMonitor’s code [6] in

order to update the designer view on a periodic basis, allowing for dynamic updates to become

visible.

Chapter 3 – Dynamic updates | 40

Figure 15 - Instrumentation visualization

The result of a dynamic instrumentation (as explained above when talking about the instrumentation

principle) applied to the workflow of Figure 14 is shown in Figure 15. In this illustration one can

clearly observe the three suspension points that were added dynamically as well as a dynamic

update (dynDelay) applied to the workflow through one of these suspension points.

Monitoring workflow instances in progress could be the starting point for dynamic adaptations as

covered earlier:

For non-instrumented workflow instances, it’s possible to perform an update right away

using the external modification methodologies described earlier in this chapter.

Chapter 3 – Dynamic updates | 41

When a workflow instance has been instrumented, applying updates at suspension points

becomes dependent on the way the IAdaptionAction has been implemented. For example,

the action could query a database that contains adaptation hints on a per-workflow instance

basis. When such a hint is present for the suspended workflow instance and for the current

suspension point, an update is applied, possibly loading an activity dynamically from disk in

order to be inserted in the workflow instance.

8 Performance analysis

8.1 Research goal In this paragraph we take a closer look at the results of a performance study conducted to measure

the impact of dynamic updates on the overall system throughput. As we’ve seen previously,

workflow instances are hosted by a workflow runtime that’s responsible for the scheduling, the

communication with services and the instance lifecycles. Depending on the application type, the

workflow runtime will have a workload varying from a few concurrent instances to a whole bunch of

instances that need to be served in parallel, a task performed by WF’s scheduling service. There are a

few metrics that can be used to get an idea of the system’s workload:

Number of concurrent workflow instances that are running.

Total number of workflow instances hosted by the runtime, some of which might be idle.

Average time for a workflow instance to reach the termination state.

Other influences are caused by the services hooked up to the runtime. For instance, when tracking

has been enabled, this will have a big impact on the overall performance. In a similar fashion,

persistence services will cause significant additional load on the machine as well as network traffic to

talk to the database. It’s clear that the network layout and other infrastructure decision will influence

WF’s performance. An interesting whitepaper on performance characteristics is available online [7].

8.2 Test environment All tests performed were conducted on a Dell Inspiron 9400 machine, with dual core Intel Centrino

Duo processor running at 2.16 GHz with an L1 cache of 32 KB and an L2 cache of 2 MB. The machine

has 2 GB of RAM and is running Windows Vista Ultimate Edition (32 bit). Tests were conducted with

persistence services disabled, with the workflow runtime hosted by a simple console application,

compiled in Release mode with optimizations turned on and started from the command-line without

a debugger attached. In a production environment, the workflow runtime would likely be hosted

inside a Windows Service or using IIS on a Windows Server 2003 configuration and with other

services turned on. Therefore, our results should be seen as a lower bound, ruling out influences

from network traffic, database loads and other stress factors.

8.3 Test methodology Each test was conducted in a pure form, meaning that it’s the only scenario that’s running at the

same time. In hosting scenarios where multiple workflow types are running in parallel, figures will

vary. However, on dedicated systems it’s likely the case that only a few workflow types and

adaptation or instrumentation mechanisms are running on the same box.

Chapter 3 – Dynamic updates | 42

For the workflow host, we’ve chosen to use a straightforward console-based application that prints

test results to the console output in a comma-separated format ready for processing in Excel. This

way, we eliminate possible overhead that could result from writing the test results to Excel or some

other data store directly by means of a data provider.

In order to make accurate timing possible, the Stopwatch class from the System.Diagnostics

namespace was used. This class encapsulates a high-resolution timer based on the Win32 API

QueryPerformanceCounter and QueryPerformanceFrequency functions [8]. An example of the use of

this class is shown in code fragment Code 39.

Stopwatch sw = new Stopwatch();

sw.Start();

//

// Perform work here.

//

sw.Stop();

TimeSpan ts = sw.Elapsed;

sw.Reset();

Code 39 - Performance measurement using System.Diagnostics.Stopwatch

We’ll systematically measure the time it takes to adapt a workflow dynamically, i.e. the cost of the

ApplyWorkflowChanges method call. When working with suspension points, other information will

be gathered too, such as the time it takes to suspend a workflow and to resume it immediately,

which is a common scenario when instrumented workflows ignore suspension points. Suspending a

workflow instance will give other instances a chance to execute before the former instance is

resumed again, which can lead to performance losses when the runtime is under a high load.

8.4 Internal workflow modification A first set of experiments we conducted analyze the performance cost associated with internal

workflow modifications. As we’ll see, this measurement can be used as a lower bound to other

metrics, because the modification doesn’t require any suspension of the workflow instance. Since the

adaptation code runs inside the workflow itself (typically wrapped in a CodeActivity), it is a safe point

to make a change since the workflow instance is waiting for the CodeActivity to complete, ruling out

any chance to get pre-empted by the workflow scheduler.

Please refer to Table 1 for a legend of the symbols used throughout the subsequent study.

Table 1 - Legend for performance graphs

Symbol Meaning

n Number of sequential modifications

N Number of concurrent users in the system

Avg Average service time to apply the change

Worst Worst service time to apply the change

Best Best service time to apply the change

Chapter 3 – Dynamic updates | 43

8.4.1 Impact of workload on adaptation time

This first experiment shows the impact of workload on adaptation time. We considered a workload

of respectively 10, 100 and 1000 workflow instances in progress and measured how long it takes to

apply a dynamic update. This update consists of the addition of one or more activities to each

workflow instance in the same “update batch”. The results of adding one, two and three activities

are displayed in Graph 1, Graph 2 and Graph 3 respectively.

From these graphs we can draw a few conclusions. First of all, when the number of concurrent

instances increases exponentially, there’s little influence on the best and average adaptation

duration scores. However, when considering the worst possible duration it take to apply the dynamic

update, we clearly see a much worse result, due to possible scheduling issues that cause another

instance to get priority.

Graph 1 - Impact of workload on internal modification duration (n = 1)

Graph 2 - Impact of workload on internal modification duration (n = 2)

0

10

20

30

40

50

60

70

80

10 100 1000

ms

N

Best

Avg

Worst

0

20

40

60

80

100

120

140

160

10 100 1000

ms

N

Best

Avg

Worst

Chapter 3 – Dynamic updates | 44

Graph 3 – Impact of workload on internal modification duration (n = 3)

8.4.2 Impact of dynamic update batch size on adaptation time

Another conclusion we can make based on these results is the impact of applying multiple

adaptations in the same dynamic update batch (a.k.a. WorkflowChanges). Graph 4 illustrates the

impact of applying multiple adaptations at the same time for a given workload. We can conclude that

there’s a linear growth in dynamic update application time, in terms of the number of adaptations in

the dynamic update batch.

Graph 4 - Impact of update batch size on internal modification duration (N = 1000)

Since internal modifications have a precise timing – meaning that there’s no risk of missing an update

because it is applied in-line with the rest of the workflow instance execution – we can conclude that

updating a workflow instance, even under high load conditions, only introduces a minor delay on the

average. Therefore, we do not expect much delay on the overall execution time of an individual

workflow instance, a few exceptions set apart (characterized by the worst execution times shown

0

50

100

150

200

250

300

10 100 1000

ms

N

Best

Avg

Worst

1 2 3

Avg 8,981 14,570 21,871

Worst 72,600 156,500 258,000

Best 6,900 9,000 11,000

0

50

100

150

200

250

300

ms

n

Avg

Worst

Best

Chapter 3 – Dynamic updates | 45

above). Although internal modifications can be applied pretty quickly, this should be seen in contrast

with the lower flexibility of internal modification, as discussed before.

A simple but effective performance optimization when applying internal modifications exists when

adjacent activities have to be added in the activity tree. In such a case, it’s desirable to wrap those

individual activities in one single SequenceActivity, so that only one single insertion is required to get

the desired effect.

8.5 External workflow modification Next, we took a closer look at the performance implications imposed by external workflow

modification. Again, we considered different workloads and multiple update batch sizes.

Graph 5 - Impact of workload on external modification duration (n = 1)

Graph 6 - Impact of workload on external modification duration (n = 2)

0

1.000

2.000

3.000

4.000

5.000

6.000

7.000

10 100 1000

ms

N

Best

Avg

Worst

0

1.000

2.000

3.000

4.000

5.000

6.000

7.000

8.000

9.000

10 100 1000

ms

N

Best

Avg

Worst

Chapter 3 – Dynamic updates | 46

Graph 7 - Impact of workload on external modification duration (n = 3)

A first fact that draws our attention is the different order of magnitude (seconds) of the adaptation

durations compared to the equivalent internal adaptations, certainly for higher workloads. As a

matter of fact, the average execution time for external modification grows faster than the equivalent

average execution time in the internal modification case.

This difference can be explained by the fact that external workflow modifications have a far bigger

impact on the scheduling of the workflow runtime since workflow instances need to be suspended

prior to performing a dynamic update. This trend is illustrated in Graph 8, where we measured the

time elapsed between suspending a workflow instance and resuming it directly again, based on the

WorkflowSuspended event provided by the workflow runtime.

Graph 8 - Suspend-resume time

0

1.000

2.000

3.000

4.000

5.000

6.000

7.000

8.000

9.000

10.000

10 100 1000

ms

N

Best

Avg

Worst

0

20

40

60

80

100

120

140

160

180

200

10 100 1000

ms

N

Best

Avg

Worst

Chapter 3 – Dynamic updates | 47

Notice that in our tests, we didn’t use persistence services, so there’s yet no impact from database

communication that occurs upon persistence that might be triggered by suspension. So, in reality

once can expect even higher wait times.

In Graph 9 we take a closer look at the impact of multi-adaptation dynamic update batches.

Graph 9 - Impact of update batch size on external modification duration (N = 1000)

Again, we observe a linear relationship between the number of adaptations and the execution time

required to apply the dynamic update. This can be reduced by merging neighboring activities into a

SequenceActivity when applicable, in order to reduce the batch size of the dynamic update.

Comparing internal modifications with external modifications, the former ones certainly outperform

the latter ones. However, external modifications offer a more flexible way of adaptation in terms of

the ability to apply unanticipated updates from inside the workflow runtime hosting layer context.

Furthermore, external modifications have access to contextual information about the surrounding

system in which the workflow instances participate.

As a conclusion, one should adopt the best practice to evaluate carefully which internal modifications

might be desirable (i.e. update scenarios that can be anticipated upfront) and what information

“gates” (e.g. by means of Local Communication Services, providing access to the hosting layer

context) should be present for internal modifications to have enough information available.

8.6 Instrumentation and modifications – a few caveats Instrumentation can be used to serve various goals, one of which could be applying a drastic change

to the workflow’s structure by dropping activities from the workflow graph while injecting others.

However, this kind of workflow modification implies quite some risks concerning positional

dependencies. Data produced by one part of the workflow could be consumed everywhere else

down the drain. Breaking such dependencies is much easier than fixing these again.

In WF, the dataflow of a workflow is implicit in contrast to the control flow (workflow so to speak).

Although issues such as missing property assignments or type mismatches of activity properties can

be detected by the workflow compiler and hence by the dynamic update feature of WF, the

semantics of the flow can’t be validated.

1 2 3

Avg 3.450,007 4128,337 4.872,183

Worst 6.792,600 8121,500 9.441,300

Best 160,500 277,100 312,500

01.0002.0003.0004.0005.0006.0007.0008.0009.000

10.000

ms

n

Avg

Worst

Best

Chapter 3 – Dynamic updates | 48

If such a situation arises, one should double-check whether or not the workflow definition does

reflect the typical flow required by the application. One could compare – at least conceptually – the

use of dynamic adaptation with exceptions in object oriented languages: one should keep exceptions

for – what’s in a name? – “exceptional” atypical situations. If one doesn’t obey to this rule, the

application might suffer from serious performance issues, not to speak about the impact on the

overall application’s structure.

However, if a change of the dataflow for individual workflow instances still makes sense after

thorough inspection, one should be extremely cautious to wire up all activities correctly. Custom

activities therefore should have proper validation rules in place that check for the presence of all

required properties. Also, obeying to strict strong typing rules for custom activity properties will help

to avoid improper property binding.

Although state tracking could help to recover data from messed up workflow instances, it’s better to

avoid mistakes altogether. Considering the long-running nature of workflows, making mistakes isn’t a

valuable option. Therefore, putting in place a preproduction or staging environment to check the

effects of a dynamic modification with respect to the correctness of workflow instances would be a

good idea for sure.

In case an urgent modification is required and time is ticking before a workflow instance will

“transition” to the next phase, one could inject a suspension point to keep the workflow from

moving on while (manual) validation of the dynamic adaptation can be carried out on dummy

workflow instances in preproduction. Once validation has been done, one can signal the suspended

instances to grab the modification, apply it and continue their journey. Signaling can be done using

various mechanisms in WF, including external event activities.

9 Conclusion The long-running nature of workflows is typically in contrast with their static definitions. For this very

reason, it makes sense to look for dynamic adaptation mechanisms that allow one or more instances

of a workflow to be changed at runtime. In this chapter we’ve outlined the different approaches

available in WF to do this.

Even if workflows are not used in a long-running fashion, dynamic adaptation proves useful to

abstract away aspects like logging that shouldn’t be introduced statically. If such aspects would be

included in the workflow’s definition right away, one of the core goals of workflow – graphical

business process visualization – would be defeated.

Performance-wise, one can draw the distinction between external and internal modification. While

the former one allows more flexibility with respect to the direct availability of state from the

environment surrounding the workflow instances, it has a much higher cost than internal

modification, due to the need to suspend a workflow instance which might also cause persistence to

take place. Furthermore, to allow precise modifications from the outside suspension points could be

used, but these incur a non-trivial cost too.

Chapter 3 – Dynamic updates | 49

Finally, we’ve discussed instrumentation too, which can be seen as external modification that takes

place when a new workflow instance is spawned. The flexibility of this approach outweighs the slight

performance costs: no suspension is needed since the workflow instance hasn’t been started yet.

In conclusion, dynamic adaptation of workflows should be used with care and one should balance

between static, dynamic and instrumented workflows. Also, starting with one approach doesn’t rule

out another: a workflow instance’s dynamic adaptations could be fed back to the workflow definition

if the dynamic adaptation happens to become applicable to all workflow instances. This kind of

feedback loop could be automated as well.

The work of this chapter, especially the creation of an instrumentation tool, was subject of a paper

entitled “Dynamic workflow instrumentation for Windows Workflow Foundation” that was submitted

to ICSEA’07. A copy of this paper is included in Appendix A.

Chapter 4 – Generic workflows | 50

Chapter 4 – Generic workflows

1 Introduction

In the previous chapter we have been looking at the possibility to adapt workflows dynamically at

runtime, as a tool to abstract aspects such a logging, authentication, etc. but also to change the

behavior of (long-running) workflow instances. In this chapter we will move our focus towards the

composition of workflows in a generic manner, based on a pre-defined set of workflow activities that

encapsulate various kinds of operations such as data retrieval and calculation. The term composition

can be explained by analogy with puzzling. Starting from individual pieces (a set of dedicated domain-

specific activities), a workflow definition is created or “composed”. Most of these individual pieces

are created in such a way that a maximum level of flexibility is gained (e.g. a query block that hasn’t

any strong bindings to a specific database schema), hence the qualification as “generic”.

The discussion in this chapter is applied to a practical use case in the field of eHealth from the

department of Intensive Care (IZ) of Ghent University (UZ Gent) called “AB Switch” (antibiotics

switch). The AB Switch agent performs processing of medical patient data on a daily basis to propose

a switch of antibiotics from intravenous to per os for patients that match certain criteria. Currently,

the agent has been implemented as a “normal” .NET-based application that is triggered on a daily

basis. The overall architecture of the AB Switch agent is shown in Figure 16. Patient data is retrieved

from the Intensive Care Information System (ICIS) database and analyzed by the agent based on

criteria proposed by medical experts, yielding a set of candidate patients. From this information, an

electronic report is sent to the doctors by e-mail.

Figure 16 - AB Switch agent architecture [9]

Chapter 4 – Generic workflows | 51

As part of this work, the current implementation of the AB Switch agent was investigated in detail.

From this investigation we can conclude that the typical flow of operations performed by the agent

consists of many repetitive tasks, each slightly different from one another, for example to query the

database. It is clear that the real flow of operations is buried under masses of procedural coding,

making the original flow as depicted in Figure 17 invisible to end-users and even to developers.

Figure 17 - Flowchart for the AB Switch agent [9]

Therefore, workflow seems an attractive solution to replace the classic procedural coding of the

algorithm that is full of data retrieving activities, calculation activities, decision logic, formatting and

sending of mails, etc. This methodology would not only allow visual inspection of the agent’s inner

workings by various stakeholders, but it also allows hospital doctors to investigate the decision logic

being taken on a patient-per-patient basis, i.e. which criteria led to a positive or negative switch

advise. In other words, workflow wouldn’t only provide a more visual alternative to the original

procedural coding, but the presence of various runtime services such as tracking would open up for

possibilities far beyond what is possible to implement in procedural coding in a timely fashion.

Chapter 4 – Generic workflows | 52

Our approach consists of the creation of a set of easy-to-use building blocks that allows composition

of a workflow without having to worry about database connections for data gathering, mail creation

and delivery, etc.

2 A few design decisions Before moving on to the implementation of the generic building blocks, a few design decisions have

to be made. We’ll cover the most important ones in this paragraph.

2.1 Granularity of the agent workflow One important question we should ask ourselves is what we’ll consider to be the atomic unit of work

in our workflow-driven agent. Two extremes exist:

The agent is represented as one big workflow that is triggered once a day and processes all

patients one-by-one as part of the workflow execution.

Decision making for an individual patient is treated as the workflow, while the hosting layer

is responsible to create workflow instances for each patient.

Both approaches have their pros and cons. Having just one workflow that is responsible for the

processing of all patients makes the agent host very simple. It’s just a matter of pulling the trigger

once a day to start the processing. However, for inspection of individual patient results based on

tracking (see Tracking Services in Chapter 1 paragraph 7 on page 39), this approach doesn’t work out

that well since one can’t gain direct access to an individual patient’s tracking information. Also, if one

would like to trigger processing for an individual patient on another (timely) basis or in an ad-hoc

fashion, the former approach won’t help out.

From the performance point of view, having one workflow instance per patient would allow the

workflow runtime’s scheduling service to run tasks in parallel without any special effort from the

developer. On the dark side of the latter approach, the hosting layer will have more work to do since

it needs to query the database as well to retrieve a set of patients that have to be processed.

Furthermore, aggregating the information that’s calculated for the patients (e.g. for reporting

purposes or for subsequent data processing by other agents or workflows) will become a task of the

host, which might be more difficult due to the possibility for out of order completion of individual

workflow instances. This out of order completion stems from the scheduling mechanism employed in

WF. There’s no guarantee that workflow instances complete in the same order as they were started.

After all, in the light of enabling long-running processing in workflow, some instances might take

longer to execute than others. But even in the case of short-running workflows, the runtime

preserves the right to “swap out” a workflow instance in favor of another one. This behavior

becomes more visible on machines hosting multiple workflow definitions that can be instanced at

any point in time. Therefore one shouldn’t rely on any ordering assumptions whatsoever.

We’ll try to find the balance between both approaches by encapsulating the database logic in such a

way that it can be used by both the host and the workflow itself, in order to reduce the plumbing on

the host required to talk with a database.

As a final note, we should point out that composition of individual workflow definitions is possible in

WF by various means. One possibility is to encapsulate the decision making workflow for a patient in

Chapter 4 – Generic workflows | 53

a custom activity itself. However, this won’t improve the granularity at which tracking happens since

tracking is performed on the workflow instance level. Because we’d still have just one workflow

instance for all patients, we’ll only gain some level of encapsulation of the inner calculation logic

(comparable to the refactoring of a foreach loop inner code block). Another possibility is to use the

workflow invocation activity of WF. Using this activity, the treatment of an individual patient can be

spawned as another workflow instance (of another type), overcoming the problems with tracking.

However, the asynchronous invocation nature of the workflow invocation activity makes collection of

results for aggregation in a mail message more difficult again.

2.2 Encapsulation as web services Current prototypes of the decision support applications for the Intensive Care Unit (ICU) department

of UZ Gent, written by INTEC, are driven by a set of interconnected web services reflecting the

Intensive Care Agent Platform (ICAP) [10] methodology. From that perspective, it’s much desirable to

enable the “publication” of the AB Switch agent workflow through a web services façade.

Taking this one step further, it might be desirable to publish portions of the workflow as well, down

to the level of individual building blocks (i.e. the domain-specific custom activities such as a generic

query block). This could be done by wrapping the portion that’s to be exposed as a web service in an

individual workflow definition. Alternatively, when publishing just one specific building block, the

custom activity could be backed by an interface that acts as the web service’s operational contract,

ready for direct publication without going through WF at all. It’s clear that this approach will only

work when the execution logic of the custom activity is short-running, allowing it to be called via a

(duplex) web method. If it’s long-running however, it’s almost inevitable to run it in WF to guarantee

correct execution and survival from service outages. Nevertheless, such a long-running operation can

still be published via a “one way” web method.

Web services encapsulation can easily be done using various technologies, including classic web

services in ASP.NET (aka .asmx) or through WCF (Windows Communication Foundation). It should be

noted that the former technology is natively supported by WF today, by means of various activities in

the WF toolbox that help to accept web service calls, take the input and send back output or faults.

Nevertheless, we won’t use this approach since it kind of “poisons” the workflow definition with a

static coupling to a web services contract, which doesn’t make much sense if the workflow is to be

called locally (possibly in-process) on the agent host. Also, by keeping the workflow definitions

independent from communication mechanisms, a great deal of flexibility can be realized to allow a

workflow’s publication through one communication mechanism or another. Notice that WF doesn’t

support distribution of portions of a workflow across multiple hosts out of the box. Connecting

different workflows that run on separate machines using web services is possible though. WF does

support long-running workflows to be migrated from one host to another as a result of a suspend-

resume cycle of operations. That is, a workflow instance doesn’t have any binding to the host it was

created on, hence enabling another host to resume its processing.

Our approach therefore consists of defining a regular communication-poor workflow, wrapped in an

interface that’s exposed as a WCF operational contract. The agent host can call the workflow

implementation directly, while the same component can be encapsulated in a WCF service host right

away to allow for flexible communication, e.g. over web services. In this context, we remind the

reader of the generic characteristic of WCF, allowing services to be bound to various communication

Chapter 4 – Generic workflows | 54

protocols without touching the underlying implementation at all. These protocols include – amongst

others – TCP, .NET Remoting, MSMQ, web services, named pipes and support a whole set of WS-*

standards.

2.3 Database logic The AB Switch agent is highly data-driven, consuming multiple query result sets required to figure out

whether or not a patient should be considered for an antibiotics switch. From this observation, quite

some questions arise, most of which are related to the overall agent’s architecture. The most

important of these issues are elaborated on in this paragraph.

It goes without saying that retrieving lots of data from the database over the network is a costly

operation, certainly if only a slight portion of that data is required in the output of the agent, i.e. the

mail with advice for the medical personnel. Because of this, one could argue that a large portion of

the agent’s functionality could be implemented on the database layer itself, in the shape of stored

procedures. Activities such as the calculation of the recorded medication dose (e.g. the levophed

dose) for a patient are certainly well-suited for this kind of approach. Today’s database systems

support quite some built-in mathematical functions that allow for server-side calculations. In

combination with “ON UPDATE” triggers or features such as “computed columns”, one could

calculate various patient metrics on the fly when data is altered in the database, offloading this

responsibility from agents. Furthermore, implementing this logic in the database directly allows for

reuse of the same logic across multiple front-end agents that all rely on the same results.

Ultimately, one could implement the agent’s filtering logic entirely on the database layer using stored

procedures, triggers for online automatic calculation of doses when patient data is changing, etc. This

would reduce the agent to a scheduler, some database calling logic and a mailer. Although this

approach might work out pretty well, depending on various Database Management System (DBMS)-

related quality attributes and database load conditions, we’re trapped again by some kind of

procedural coding – this time in the database tier – that hides the original logic. Without workflow

support inside the database directly – which might be subject of Microsoft’s next-generation SQL

Server product – we won’t gain much from this approach.

The current AB Switch agent relies on quite some ad-hoc queries, as the ones displayed in Code 40.

During our investigation of the current implementation, a few possible enhancements were

proposed to make these queries more efficient in the context of the AB Switch agent. Making such

changes shouldn’t impose a problem since all queries are ad-hoc ones that are kept on the client and

are only used by the agent itself. Nevertheless, moving some queries to the database layer as stored

procedures would be a good thing, allowing for tighter access control at the DBMS level and possibly

boosting performance.

A few other remarks were made concerning the current implementation, such as:

The (theoretical) risk for SQL injection attacks because of string concatenation-based query

composition in code;

Quite a bit of ugly coding to handle unexpected database (connection) failures, partially

caused by the shaky Sybase database provider in .NET;

A few performance optimizations and .NET and/or C# patterns that could prove useful for

the agent’s implementation.

Chapter 4 – Generic workflows | 55

SELECT DISTINCT PatientID

FROM Patients

WHERE EnterTime BETWEEN @from AND @to

AND PharmaID in (" + IVs + ")

SELECT DISTINCT s.PharmaID, s.PharmaName

FROM Patients AS p, Pharma AS s

WHERE p.PatientID=@patient

AND p.EnterTime BETWEEN @from AND @to

AND p.PharmaID = s.PharmaID

AND p.PharmaID in (" + IVs + ")

ORDER BY s.PharmaName

SELECT EnterTime, PharmaID

FROM Patients

WHERE PatientID = @patient

AND EnterTime BETWEEN @from AND @to

AND PharmaID IN (1000592,1000942,1000941)

Code 40 - A few AB Switch queries

In our translation from a procedural to a workflow-based agent implementation, we’ll provide a

query “data gathering” block that encapsulates all of the database-related logic and enables a

generic approach to retrieve data based on a set of pre-defined and configurable queries.

Because of the possible reliability issues with Sybase’s .NET data provider and the lack of good

information and support for this data provider, we’ll use ODBC to connect to the database, calling

into the Sybase ODBC data provider instead. Using the ODBC Data Source Administrator of Windows

(Figure 18), a data source can be created that links to the Sybase database, assuming the Adaptive

Server Enterprise (ASE) [11] OLEDB and ODBC supporting files have been installed and registered on

the system.

Figure 18 - ODBC Data Source Administrator

Chapter 4 – Generic workflows | 56

3 From flowchart to workflow: an easy step? At the core of our workflow implementation effort for the AB Switch agent is the translation of a

flowchart (Figure 17) into a WF sequential workflow. Although it might look a pretty simple and

straightforward step, there are quite some caveats.

3.1 Flow control One commonly used structure in flowcharts is the direct connection of multiple flow paths to one

flow block, as shown in Figure 19 at the left-hand side. Such a structure is somewhat equivalent to a

procedure call in a classic programming language, or to a goto statement in older languages. WF

doesn’t support a direct mapping of this structure because of its tree-based workflow nature.

However, by wrapping the common block in a separate custom activity, one can avoid redundant

duplication of those blocks in order to improve maintainability and to keep a good deal of readability.

The result of such an approach is shown in Figure 19 at the right-hand side.

Figure 19 - Shortcuts in flow diagrams and the WF equivalent

3.2 Boolean decision logic Other flowchart structures that don’t have a straightforward mapping to a WF-based workflow are

Boolean operators. The AB Switch agent is essentially a piece of filtering logic that retrieves a set of

condition operands that are joined together using AND operators, as illustrated in Figure 20.

Such a series of AND operators can be translated into a tree of nested IfElseActivity activities, which

comes very close to the actual current implementation in code. However, this kind of nesting makes

the workflow definition rather complex and the decision logic is a bit more hidden since one has to

know which branch of an IfElseActivity represents the positive case and which one represents the

negative case. Also, the else-branches typically will be empty in case of filtering structures like the

ones in AB Switch.

A colored approach to indicate success or failure, as used in a flowchart like the one in Figure 20 is

much more intuitive. Therefore, we’ll mimic this approach by creating custom activities that signal

success or failure and act as a bit of eye candy to improve the visuals of a workflow definition.

Chapter 4 – Generic workflows | 57

Figure 20 - Boolean operators in a flowchart

The result of using true/false indicators in workflows is shown in Figure 21.

Figure 21 - Colored status indicators

3.3 Serial or parallel? Furthermore, the approach of nested IfElseActivity blocks implies serialization of the decision logic

which might or might not be a suitable thing to do, depending on the probability for conditions to be

evaluated positively or negatively.

By putting conditions with a high probability for a negative result on top of the nested IfElseActivity

tree, database traffic can be reduced significantly. However, when such a probabilistic ordering isn’t

possible, parallel execution of data gathering and evaluation using success/failure indicators might be

an attractive alternative, allowing for more parallelism. This isn’t straightforward to do in procedural

Chapter 4 – Generic workflows | 58

coding but is much easier in workflow composition using the ParallelActivity. In such a case, when

most cases evaluate positively, the overhead caused by retrieving data that’s not needed further on

will be largely overshadowed by the additional parallelism. An example of parallel data retrieval is

shown in Figure 22.

Figure 22 - Parallel data gathering

Notice that there are various kinds of parallelism in workflow, both intra-workflow and on the

runtime level. By representing the evaluation process for a single patient as a single workflow

instance, we’ll get parallel processing of multiple patients at a time. By putting ParallelActivity blocks

in the workflow definition, we do get parallelism inside workflow instances as well.

It might be tempting to wrap Boolean operators in some kind of parallel activity too. In such a

structure, all branches of the parallel activity would represent an operand of the enclosing operator.

Although such an approach has great visual advantages, it’s far from straightforward to implement

and to use during composition. Remember that WF is all about control flows and there is no intrinsic

concept of data exchange between activities in a contract-driven manner. In other words, it’s not

possible to apply a (to WF meaningful) interface (such as “returns a Boolean value”) to an activity

that’s used by an enclosing activity (e.g. the one that collects all Boolean results and takes the

conjunction or disjunction). Instead, WF is built around the concept of (dependency) properties for

cross-activity data exchange, which introduces the need for quite a bit of wiring during composition,

e.g. to connect child activities (the operands) to their parent (the operator). In addition, quite some

validation logic would be required to ensure the correctness of a “Boolean composition”.

3.4 Error handling Another important aspect occurring on a regular basis in flowchart diagrams is some kind of error

handling. In Figure 23 a dotted line is used to indicate the path taken in case of an error. Such a

construct doesn’t map nicely to code when using exception handling mechanisms. As a matter of

fact, it has a closer correspondence to an “On Error Goto …” kind of construct in BASIC languages.

Since WF is based on object-oriented design under the covers, concepts like exception handling shine

through to the world of WF. By making custom activities throw exceptions, one can take advantage

of WF Fault Handlers when composing a workflow, as shown in Figure 24.

Chapter 4 – Generic workflows | 59

Figure 23 - Error paths in flowcharts

In case data gathering results have to be validated to match certain criteria, e.g. a non-null check,

additional custom activities can be created that perform such logic and make the handling of such

corner cases visible in the workflow definition. In case of a criteria matching failure, a special custom

exception can be thrown, somewhat the equivalent of putting an implicit “throws” clause in the

custom activity’s contract. Notice however that .NET doesn’t use checked exceptions [12]. Therefore

users of such a custom activity should be made aware of the possibility for the activity to throw an

exception, e.g. by providing accurate documentation.

Figure 24 - Fault Handlers in WF

Chapter 4 – Generic workflows | 60

4 Approaches for data gathering As outlined throughout the previous sections of this chapter, the AB Switch agent is highly data-

driven, so building a data gathering mechanism is a central asset of a workflow-based approach.

Various approaches should be taken under consideration.

4.1 Design requirements and decisions In order to make the data gathering mechanism flexible a few design decisions have to be made:

Queries shouldn’t be hardcoded, allowing a query to be changed without having to touch the

workflow definition. To realize this requirement, queries will be identified by a unique name

and will be stored in a configuration base. This should make it possible to optimize queries or

even to refactor queries into stored procedures without having to shut down the agent.

Storage of queries should be implemented in a generic fashion, allowing queries to be stored

by various means. An interface to retrieve query definitions will be used to allow this level of

flexibility. For example, queries could be stored in XML or in a database, by implementing the

query retrieval interface in a suitable way.

Parameterization of queries should be straightforward in order to ease composition tasks.

Typically, the parameters required to invoke a query will originate from the outside (e.g. a

patient’s unique identifier) or from other sources inside the same workflow. Furthermore,

parameters should be passed to the data gathering mechanism in a generic fashion,

maximizing the flexibility of data gathering while minimizing composition efforts.

Results produced by invoking a query should be kept in a generic fashion too. This should

make consumption of produced values easy to do, while supporting easy chaining of various

queries as well, i.e. outputs of one query could act as (part of the) inputs for another query.

4.2 Using Local Communication Services WF’s built-in mechanism to flow data from the outside world (the host layer so to speak) to a

workflow instance is called Local Communication Services [13] and has been mentioned multiple

times in this work already. Essentially it allows for a contract-based data exchange mechanism, by

providing an interface that is used inside the workflow definition to call operations, e.g. to retrieve

data.

Such a mechanism is a big requirement when dealing with long-running workflows. Although data for

the current workflow instance could be kept in various properties of the workflow, data in a long-

running workflow is typically highly volatile, ruling out this approach. Using Local Communication

Services, accurate data can be gathered whenever required by the workflow, with the guarantee to

have the latest version of the data available regardless of any possible suspensions that have

happened throughout a workflow instance’s runtime.

In a non-generic workflow definition, one could create an interface containing all of the required

data gathering operations which are to be invoked in the workflow when that specific data is needed.

Parameterization of queries then comes down to creating parameterized methods that are called

from inside a workflow through Local Communication Services. This approach provides a big deal of

strong typing and tight coupling with queries, making it non-generic. Modifying such a workflow

definition dynamically at runtime wouldn’t be trivial too since lots of input and output bindings to

the query invocation activity would have to be applied.

Chapter 4 – Generic workflows | 61

A generic alternative for data gathering through Local Communication Services is to represent query

parameters and query results in a dictionary-based collection type, mapping query parameters or

column names to corresponding objects. Dictionaries are easy to use from code, produce nice-to-

read code using named parameters and have pretty good performance characteristics. In this work,

we’ll refer to such a dictionary as a property bag, defined in Code 41.

[Serializable]

public class PropertyBag : Dictionary<string, object>, IXmlSerializable

{

public new object this[string key]

{

get { return base[key]; }

set { base[key] = value; }

}

...

}

Code 41 - Property bag definition

Notice the XML serialization support that’s required for workflow properties of this type in order to

be persisted properly when the runtime indicates to do so. This data representation format makes

inspection at runtime, e.g. using instrumentation mechanisms, much easier than facing various

values spread across the entire workflow definition.

Next, a contract for query execution and data retrieval has to be created. In order to keep things

inside the workflow definition as simple as possible, we’ll retrieve a query object through Local

Communication Services (LCS) by means of a query manager as shown in Code 42.

[ExternalDataExchange]

public interface IQueryManager

{

IQuery GetQuery(string name);

}

Code 42 - Query manager used by LCS

Finally, the interface for query definition and execution is defined as follows:

public interface IQuery

{

string Name { get; }

List<PropertyBag> Execute(PropertyBag parameters);

}

Code 43 - Query representation and execution interface

Using this query representation, chaining of query results to query inputs becomes pretty easy to do,

because of the use of property bags both as input and as output. The WF designer supports indexing

in List<T> collections, so it’s possible to grab the first row from the query’s results collection just by

using a ‘*0+’ equivalent in the designer.

Chapter 4 – Generic workflows | 62

4.3 The data gathering custom activity The definition of these two interfaces and the property bag forms the core of our data gathering

implementation. However, in order to make data a first class citizen of our workflow definitions, we’ll

wrap all LCS communication and query invocation logic inside a custom activity called the

GatherDataActivity. The core implementation of this activity is outlined below in Code 44.

[ActivityValidator(typeof(GatherDataValidator))]

[DefaultProperty("Query")]

[Designer(typeof(GatherDataDesigner), typeof(IDesigner))]

[ToolboxItem(typeof(ActivityToolboxItem))]

public class GatherDataActivity : Activity

{

public static DependencyProperty ParametersProperty =

DependencyProperty.Register("Parameters",

typeof(PropertyBag),

typeof(GatherDataActivity));

[Browsable(true)]

[Category("Input/output")]

[Description("Parameters for data query.")]

[DesignerSerializationVisibility(

DesignerSerializationVisibility.Visible)]

public PropertyBag Parameters

{

get { return (PropertyBag)base.GetValue(ParametersProperty); }

set { base.SetValue(ParametersProperty, value); }

}

public static DependencyProperty ResultsProperty =

DependencyProperty.Register("Results",

typeof(List<PropertyBag>),

typeof(GatherDataActivity));

[Browsable(true)]

[Category("Input/output")]

[Description("Results of the query.")]

[DesignerSerializationVisibility(

DesignerSerializationVisibility.Visible)]

public List<PropertyBag> Results

{

get { return (List<PropertyBag>)base.GetValue(ResultsProperty); }

set { base.SetValue(ResultsProperty, value); }

}

public static DependencyProperty QueryProperty =

DependencyProperty.Register("Query",

typeof(string),

typeof(GatherDataActivity));

[Browsable(true)]

[Category("Database settings")]

[Description("Name of the query.")]

[DesignerSerializationVisibility(

DesignerSerializationVisibility.Visible)]

public string Query

{

get { return (string)base.GetValue(QueryProperty); }

set { base.SetValue(QueryProperty, value); }

}

Chapter 4 – Generic workflows | 63

protected override ActivityExecutionStatus Execute(

ActivityExecutionContext executionContext)

{

IQueryManager qm =

executionContext.GetService<IQueryManager>();

IQuery query = qm.GetQuery(Query);

Results = query.Execute(Parameters);

return ActivityExecutionStatus.Closed;

}

}

Code 44 - GatherDataActivity implementation

We’ve dropped the implementation of the validator and designer helper classes which provide

validation support during compilation and layout logic for the workflow designer respectively. All of

the parameters to the GatherDataActivity have been declared as WF dependency properties to assist

with binding. For more information on dependency properties we refer to [14].

The core of the implementation is in the Execute method which is fairly easy to understand. First, it

retrieves the query manager to grab the query object from, based on the query’s name. Execution of

the query is then just a matter of calling the Execute method on the obtained IQuery object using the

appropriate parameters. Finally, results are stored in the Results property and the activity signals it

has finished its work by returning the Closed value from the ActivityExecutionStatus enum.

4.4 Implementing a query manager So far, we’ve been looking at the interfaces for the data gathering block. Such an interface-driven

approach helps to boost the generic nature of our framework but without proper implementation it’s

worthless. In this paragraph, an XML-based query manager will be introduced.

4.4.1 An XML schema for query representation

In order to separate the query statements from the executable code, parameterized queries will be

described entirely in XML. The corresponding schema is depicted graphically in Figure 25.

Figure 25 - Query description schema

Each <Query> element is composed of a set of Inputs (the parameters) and Outputs (the columns)

together with a unique name, a category (for administrative purposes) and the parameterized query

statement itself. This parameterized statement will typically be mapped on a parameterized SQL

command that’s executed against the database, grabbing the parameter values from the

GatherDataActivity’s input PropertyBag and spitting out the corresponding query results which are

stored in the GatherDataActivity’s output list of PropertyBag objects subsequently.

Chapter 4 – Generic workflows | 64

A few queries are shown in Listing 2 to set the reader’s mind.

<?xml version="1.0" encoding="utf-8" ?>

<Queries>

<Query Name="AntibioticaIV" Category="Pharma">

<Statement>

SELECT DISTINCT PatientID FROM Patients

WHERE EnterTime BETWEEN @from AND @to

AND PharmaID in (1000505,1000511,1001343,1001181,1000921,

1001175,1001138,1000519,1000520)

</Statement>

<Inputs>

<Parameter Name="@from" Type="datetime" />

<Parameter Name="@to" Type="datetime" />

</Inputs>

<Outputs>

<Column Name="PatientID" Type="int" Nullable="No" />

</Outputs>

</Query>

<Query Name="PatientInfo" Category="Patients">

<Statement>

SELECT PatNumber, FirstName, LastName FROM Patients

WHERE PatientID=@PatientID

</Statement>

<Inputs>

<Parameter Name="@PatientID" Type="int" />

</Inputs>

<Outputs>

<Column Name="PatNumber" Type="varchar" Nullable="No" />

<Column Name="FirstName" Type="varchar" Nullable="No" />

<Column Name="LastName" Type="varchar" Nullable="No" />

</Outputs>

</Query>

<Query Name="BedInfo" Category="Patients">

<Statement>

SELECT b.Abbreviation AS Bed, r.Abbreviation AS Room

FROM Patients AS p, Beds AS b, Rooms AS r, RoomsBeds AS rb

WHERE PatientID=@PatientID AND p.BedID = b.BedID

AND b.BedID = rb.BedID AND r.RoomID = rb.RoomID

</Statement>

<Inputs>

<Parameter Name="@PatientID" Type="int" />

</Inputs>

<Outputs>

<Column Name="Bed" Type="varchar" Nullable="No" />

<Column Name="Room" Type="varchar" Nullable="No" />

</Outputs>

</Query>

<Queries>

Listing 2 - Sample query definitions

The data types supported on parameters and columns are directly mapped to database-specific

types. Another level of abstraction could be introduced to make these types abstract, with automatic

translation of type names into database-specific data types. However, the query definition XML isn’t

part of our core framework, so one can modify the XML schema at will. Our query representation

maps nicely on a Sybase or SQL Server database, as used by the AB Switch agent.

Chapter 4 – Generic workflows | 65

Notice that this XML-based approach allows queries to be changed without touching the workflow

definition, even at runtime. When making changes at runtime however, care has to be taken not to

disturb semantic correctness which could affect running workflow instances and which could possibly

result in bad output results or behavior. For example, if the results of one query are used as the

inputs of another one, there shouldn’t be a mismatch between both data domains. When a workflow

has passed the first data gathering block and all of a sudden subsequent queries are altered in

configuration, parameterization of those queries might become invalid.

As a countermeasure for situations like these, workflow tracking can be used to detect a safe point

for query changes or instrumentation could be used to inject suspension points that provide signaling

to detect that all running instances have reached a point in the workflow that’s considered to be safe

to allow changes. Much more complex mechanisms could be invented, for example allowing new

workflow instances to use modified queries right away, while existing workflow instances continue

their execution using the original query definitions from the time the instance was started. Such an

approach can be implemented by putting version numbers on queries and by providing query version

numbers when launching a workflow instance. During a workflow instance’s lifecycle, the same query

version will be used, ruling out possible mismatches between different queries.

4.4.2 Core query manager implementation

Next, we’ll implement the query manager itself which is really straightforward (Code 45).

class XmlQueryManager : IQueryManager

{

private XmlDocument doc;

public XmlQueryManager(string file)

{

doc = new XmlDocument();

doc.Load(file);

}

#region IQueryManager Members

public IQuery GetQuery(string name)

{

XmlNode query = doc.SelectSingleNode(

"Queries/Query[@Name='" + name + "']");

if (query == null)

return null;

else

return SybaseQuery.FromXml(query);

}

#endregion

}

Code 45 - Query manager using XML query descriptions

This implementation loads an XML file from disk and builds an IQuery object of type SybaseQuery

based on a specified query name. We should point out that the implementation shown in here is

slightly simplified for the sake of the discussion. A production-ready query manager should allow

more flexibility by detecting changes of the file on disk using a FileSystemWatcher component or by

providing some kind of Refresh method that can be called from the outside when needed. Ideally,

Chapter 4 – Generic workflows | 66

the XML document containing queries shouldn’t be read from disk any time a query is requested

since this would degrade performance significantly. A good balance between caching of the query

descriptors and XML file change detection is crucial.

4.4.3 The query object

Finally, we’ve reached the core object in our query manager: the query object itself, implementing

IQuery. A slightly simplified object definition is shown in Code 46.

class SybaseQuery : IQuery

{

private string name;

private OdbcCommand cmd;

private OdbcConnection connection;

private Column[] outputColumns;

private SybaseQuery(string name, OdbcConnection connection,

string statement, OdbcParameter[] parameters,

Column[] outputColumns)

{

this.name = name;

this.connection = connection;

this.outputColumns = outputColumns;

cmd = new OdbcCommand(statement, connection);

foreach (OdbcParameter p in parameters)

cmd.Parameters.Add(p);

}

public static SybaseQuery FromXml(XmlNode query)

{

string name = query.Attributes["Name"].Value;

string statement = query["Statement"].InnerText;

XmlElement parameters = query["Inputs"];

List<OdbcParameter> sqlParameters = new List<OdbcParameter>();

foreach (XmlNode parameter in parameters.ChildNodes)

sqlParameters.Add(

new OdbcParameter(parameter.Attributes["Name"].Value,

GetOdbcType(parameter.Attributes["Type"].Value)

));

XmlElement outputs = query["Outputs"];

List<Column> columns = new List<Column>();

foreach (XmlNode column in outputs.ChildNodes)

columns.Add(

new Column(column.Attributes["Name"].Value,

GetOdbcType(column.Attributes["Type"].Value),

column.Attributes["Type"].Value.ToLower() == "yes"

));

return new SybaseQuery(name,

new OdbcConnection(Configuration.Dsn), statement,

sqlParameters.ToArray(), columns.ToArray());

}

private static OdbcType GetOdbcType(string type)

{

switch (type)

{

Chapter 4 – Generic workflows | 67

case "int":

return OdbcType.Int;

case "datetime":

return OdbcType.DateTime;

}

return default(OdbcType);

}

public string Name

{

get { return name; }

}

public List<PropertyBag> Execute(PropertyBag parameters)

{

foreach (KeyValuePair<string, object> parameter in parameters)

{

string key = parameter.Key;

if (!key.StartsWith("@"))

key = "@" + key;

if (cmd.Parameters.Contains(key))

cmd.Parameters[key].Value = parameter.Value;

}

connection.Open();

try

{

OdbcDataReader reader = cmd.ExecuteReader();

List<PropertyBag> results = new List<PropertyBag>();

while (reader.Read())

{

PropertyBag item = new PropertyBag();

foreach (Column c in outputColumns)

item[c.Name] = reader[c.Name];

results.Add(item);

}

return results;

}

finally

{

if (connection.State == ConnectionState.Open)

connection.Close();

}

}

}

Code 46 - Query object for Sybase

Let’s point out a few remarks concerning this piece of code. First of all, the GetOdbcType helper

method was oversimplified, only providing support for the types ‘int’ and ‘datetime’ which happen to

be the most common ones in the AB Switch agent. For a full list of types, take a look at the OdbcType

enumeration. Next, the Execute method has some built-in logic that allows chaining of query blocks

by translating column names in parameter names suffixed by the @ character. In case more flexibility

is needed, CodeActivity activities can be put in place to provide extended translation battles from

gathered data into query parameterization objects. However, when naming parameters and columns

in a consistent fashion, this kind of boilerplate code can be reduced significantly. For the AB Switch

Chapter 4 – Generic workflows | 68

agent composition we had to apply only a few such parameterization translations. Finally, exceptions

occurring during the Execute method are propagated to the caller, typically the GatherDataActivity.

In there, the exception can be wrapped as an inner exception in another exception type as part of

the GatherDataActivity’s “contract”. This approach wasn’t shown in Code 44 but is trivial to

implement.

4.5 Hooking up the query manager Now we’ve implemented both the query manager and a query object ready for consumption by the

GatherDataActivity activity in workflow definitions. To make this functionality available to the

workflow instances it has to be hooked up in the Local Communication Services as shown in Code 47.

using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())

{

ExternalDataExchangeService edx

= new ExternalDataExchangeService();

workflowRuntime.AddService(edx);

edx.AddService(new XmlQueryManager("Queries.xml"));

Code 47 - Making the query manager available using LCS

4.6 Chatty or chunky? An important aspect to data-driven workflows is the communication methodology employed to talk

to the database. Two extremes are chatty and chunky communication. In the former case, lots of

queries are launched against the database, each returning a bit of information that’s puzzled

together in the workflow’s logic. The latter one pushes much logic to the database tier in order to

optimize the throughput between tiers and to reduce the amount of data sent across the wire. Both

approaches have their pros and cons as outlined below.

4.6.1 Chatty

Chatty database communication puts a high load on the network stack and the database providers by

creating lots of connections and by executing lots of queries. The percentage of network traffic

occupied by queries sent to the database is relatively high and it’s not unlikely to retrieve a bunch of

data that’s thrown away in the middle-tier or somewhere else in the application because of

additional filtering logic outside the database.

On the other hand, chatty communication typically results in a (large) set of easy-to-understand

queries that might prove useful in various middle-tier components or even across applications when

exposed as stored procedures. Because of this, workflows greatly benefit from some kind of chatty

database communication, to assist in boosting a workflow’s readability.

4.6.2 Chunky

When employing chunky communication, each query results in a chunk of data that’s mostly filtered

on the database side and that has high relevance. This decreases the load on the network for what

the submission of queries and corresponding parameters is concerned.

Ultimately, one could implement a big part of the middle-tier logic at the database side itself, for

example in stored procedures. This will however result in an increased load on the DBMS and moves

a big part of the problem’s complexity to another level, ruling out a clean workflow-based approach

to data logic.

Chapter 4 – Generic workflows | 69

4.6.3 Finding the right balance

Of course, there’s not only black or white but lots of gray scale variants in between. For our AB

Switch agent implementation using workflow, quite some small changes were applied to the original

queries that are used in the procedural coding variant.

An example of merging two queries into one chunkier one is the gathering of patient and bed info in

one single operation. Figure 22 shows such a set of chatty queries that can be merged into just one

by applying a database join operation, as illustrated in Code 48. Other optimizations that have been

applied make use of richer SQL constructs or built-in DBMS functions that help to make a query more

efficient or reduce the size of the returned data. For example, checking the presence of data was

done by retrieving the data and checking the number of rows in the middle-tier layer. A more

efficient approach is to use the COUNT aggregate from SQL.

SELECT PatNumber, FirstName, LastName, BedID, RoomID FROM Patients, Beds AS

b, Rooms AS r, RoomsBeds AS rb WHERE PatientID=@PatientID AND p.BedID =

b.BedID AND b.BedID = rb.BedID AND r.RoomID = rb.RoomID

Code 48 - Merge of individual data queries

5 Other useful activities for generic workflow composition Now that we’ve created the core building block for data gathering, it’s time to focus on a few other

useful activities that make the creation of generic workflows easier. While creating the core toolbox

for generic workflow composition during our research, a total of six custom activities were created.

We’ll discuss each of these, except for the GatherDataActivity, which was covered previously.

Figure 26 - Core activities for generic workflow composition

5.1 ForeachActivity WF has one type of loop activity – the WhileActivity – that allows repetitive execution of a sequence

activity based on a given condition. However, when turning workflows into data processing units – as

is the case with the AB Switch agent –data iteration constructs seem useful. To serve this need, the

ForeachActivity was created, allowing a set of data to be iterated over while executing a sequence

activity for each data item in the set, more or less equal to C#’s foreach keyword.

However, before continuing our discussion of this custom activity we should point out that this

activity might not be the ideal choice when dealing with long-running workflow instances that can be

suspended. The limitations imposed by our ForeachActivity are somewhat equivalent to limitations

of iterators in various programming languages: during iteration the source collection shouldn’t

change. In case a workflow instance suspends while iterating over a set of data, that set of data will

Chapter 4 – Generic workflows | 70

be persisted together with the current position of the iteration. However, by the time the workflow

instance comes alive again, the data might be stale and out of sync with the original data source.

The public interface of the ForeachActivity is shown in Figure 27. To the end-users of the activity, the

two properties are the most important members. The Input property has to be bound to an ordered

collection represented by IList, while the IterationVariable is of type object and will hold the current

item of the iteration. Or, in pseudo-code, the ForeachActivity is equivalent to:

foreach (object IterationVariable in Input)

{

//

// Process iteration body activity sequence

//

}

Figure 27 - ForeachActivity

Essentially, the ForeachActivity forms a graphical representation for data iteration tasks in flowchart

diagrams, for example to perform some kind of calculation logic that uses intermediary results based

on individual data items in a set of retrieved data. As mentioned before, it’s key for the body of the

ForeachActivity to be short-running and non-suspending (at least not explicitly – the workflow

runtime can suspend instances for various reasons). If long-running processing is required on data

items, individual workflow instances should be fired to perform further processing on individual

items in an asynchronous manner, causing the main iteration loop to terminate quickly. This

approach can be realized using the InvokeWorkflow activity of WF. However, in such a case one

should take under consideration any possibility to get rid of the ForeachActivity altogether by moving

the iterative task to the host layer where workflow instances can be created for individual data

items.

An example use in the AB Switch agent could be the iteration over the list of patients that have to be

processed for the past 24 hours, as shown in Figure 28.

Chapter 4 – Generic workflows | 71

Figure 28 - Iteration over a patient list

As long as the SequenceActivity nested inside the ForeachActivity is short-running – kind of a normal

procedural coding equivalent – there’s nothing wrong with this approach. However, one shouldn’t

forget about the sequential nature of the ForeachActivity, as it is derived from the SequenceActivity

base class from the WF base class library. Because of this, no parallelism can be obtained right away

while processing records. The IList interface used for the input of the ForeachActivity nails down this

sequential ordering.

Therefore, for the macro-level of the AB Switch agent, we’ll move the patient iteration logic to the

host layer, effectively representing the processing for one single patient in one workflow definition,

enhancing the degree of parallelism that can be obtained at runtime by executing calculations for

multiple patients at the same time. Nevertheless, we can still take advantage of our query manager

object to retrieve the list of patients in a generic fashion on the host layer, even without having to

use the GatherDataActivity.

5.2 YesActivity and NoActivity Two other activities in our generic workflow library are YesActivity and NoActivity. As a matter of

fact, these activities are mostly eye candy albeit very useful one. In Figure 29 these activities are

shown, used as status indicators for Boolean decision logic indicating success or failure in colors.

Both activities essentially are Boolean assignment operators that assign a value of true (YesActivity,

green) or false (NoActivity, red) to a workflow property.

This approach improves the ease of visual inspection for a workflow during and after composition,

but proves valuable at runtime too when using tracking services. In the WorkflowMonitor, executed

activities are marked by tracking services, allowing visual inspection of workflow instances that are

executing or have been executed already. In case of the AB Switch agent where each individual

patient is represented as a single workflow instance, this kind of tracking allows to diagnose the

decisions made by the agent in order to spot errors or for analytical or statistical purposes.

Chapter 4 – Generic workflows | 72

Figure 29 - YesActivity and NoActivity in a workflow

As an example, the YesActivity is shown in the code fragment below.

[DefaultProperty("YesOutput")]

[Designer(typeof(YesDesigner), typeof(IDesigner))]

[ToolboxItem(typeof(ActivityToolboxItem))]

public class YesActivity : Activity

{

public static DependencyProperty YesOutputProperty =

DependencyProperty.Register("YesOutput", typeof(bool),

typeof(YesActivity));

[Browsable(true)]

[Category("Output")]

[Description("Target to set the output value to.")]

[DesignerSerializationVisibility(

DesignerSerializationVisibility.Visible)]

public bool YesOutput

{

get { return (bool)base.GetValue(YesOutputProperty); }

set { base.SetValue(YesOutputProperty, value); }

}

protected override ActivityExecutionStatus Execute(

ActivityExecutionContext executionContext)

{

YesOutput = true;

return ActivityExecutionStatus.Closed;

}

}

Code 49 - A look at the YesActivity implementation

Notice that an alternative approach could be the use of a single activity used to signal either a

positive case or a negative case. However, WF doesn’t support a designer layout mechanism that can

change an activity’s color based on property values set on the activity, so we’ll stick with two

separate custom activities with a hardcoded green and red color respectively.

Chapter 4 – Generic workflows | 73

As another side note, observe that the situation shown in Figure 29 does a trivial mapping from an

IfElseActivity’s conditional check on a Boolean true/false value using the YesActivity and NoActivity.

This might look a little awkward at first, corresponding to a code structure as the one shown below:

if (<condition>)

value = true;

else

value = false;

Indeed, in a procedural coding equivalent, we’d optimize this code by assigning the condition directly

to the value variable. However, such an approach in the world of workflow won’t provide any visuals

that indicate the outcome of a condition. It should be noted however that the condition builder from

WF seems to be reusable for custom activities, but no further attention was paid to this in particular,

especially since that kind of implementation again would hide the “colorful signaling”.

As an example of a more complex IfElseActivity combined with such signaling blocks, take a look at

the epilogue of the AB Switch implementation that makes the final decision for a patient (Figure 30).

The corresponding canSwitch condition for the IfElseActivity is shown in Figure 31.

Figure 30 - Final decision for a patient's applicability to switch antibiotics

Figure 31 - CanSwitch declarative condition

Chapter 4 – Generic workflows | 74

5.3 FilterActivity As an example to illustrate the richness of the presented approach to data-driven workflows, a

FilterActivity has been created to help protecting private patient data that’s required throughout the

workflow itself but shouldn’t be exposed to the outside. Different approaches to realize this goal

exist, ranging from dropping entries from property bags over data replacement with anonymous

values to data encryption so that only authorized parties can read the data.

The skeleton for such a filtering activity is outlined in Code 50 below.

[DefaultProperty("Filters")]

[Designer(typeof(FilterDesigner), typeof(IDesigner))]

[ToolboxItem(typeof(ActivityToolboxItem))]

public class FilterActivity : Activity

{

public static DependencyProperty FiltersProperty =

DependencyProperty.Register("Filters", typeof(string[]),

typeof(FilterActivity));

[Browsable(true)]

[Category("Filtering")]

[Description("Property filters.")]

[DesignerSerializationVisibility(

DesignerSerializationVisibility.Visible)]

public string[] Filters

{

get { return (string[])base.GetValue(FiltersProperty); }

set { base.SetValue(FiltersProperty, value); }

}

public static DependencyProperty ListSourceProperty =

DependencyProperty.Register("ListSource",

typeof(List<PropertyBag>), typeof(FilterActivity));

[Browsable(true)]

[Category("Data")]

[Description("List source.")]

[DesignerSerializationVisibility(

DesignerSerializationVisibility.Visible)]

public List<PropertyBag> ListSource

{

get { return (List<PropertyBag>)base.GetValue(ListSourceProperty); }

set { base.SetValue(ListSourceProperty, value); }

}

protected override ActivityExecutionStatus Execute(

ActivityExecutionContext executionContext)

{

foreach (PropertyBag p in ListSource)

foreach (string f in Filters)

// Performing filtering logic

return ActivityExecutionStatus.Closed;

}

Code 50 - Code skeleton for a filtering activity

The real richness of this activity stems from its possible combination with dynamic updates and

instrumentation. Especially in research-driven environments, it makes sense to be able to use a

Chapter 4 – Generic workflows | 75

(pre)production environment that operates on actual data whilst keeping data as private as possible.

This way, one shouldn’t alter the workflow definition or derive a privacy-friendly variant from the

existing workflow definition.

A more flexible approach using an IFilter interface has been investigated too, allowing for faster

development of a custom filter that plugs directly into a generic FilterActivity. Using Local

Communication Services, the FilterActivity can get access to the IFilter implementation to execute

the corresponding filtering logic.

5.4 PrintXmlActivity A final core building block in our library is the PrintXmlActivity that transforms property bags into an

XML representation and (optionally) applies an XSLT stylesheet to the data. Since property bags are

derived from a built-in collection type, serialization to XML is a trivial thing to do, resulting in a

friendly representation of the name/value pairs, as illustrated in Listing 3.

<?xml version="1.0" encoding="utf-16"?>

<ArrayOfPropertyBag>

<PropertyBag>

<Bed>E307</Bed>

<First>Marge</First>

<Last>Simpson</Last>

<ID>591122</ID>

<Number>078A93</Number>

<Antibiotics>

<PropertyBag>

<Name>Ciproxine IV</Name>

<Dose>200mg/100ml flac</Dose>

</PropertyBag>

<PropertyBag>

<Name>Dalacin C IV</Name>

<Dose>600mg/4ml amp</Dose>

</PropertyBag>

</Antibiotics>

</PropertyBag>

<PropertyBag>

<Bed>E309</Bed>

<First>Homer</First>

<Last>Simpson</Last>

<ID>370818</ID>

<Number>060A39</Number>

<Antibiotics>

<PropertyBag>

<Name>Flagyl IV</Name>

<Dose>500mg/100ml zakje</Dose>

</PropertyBag>

</Antibiotics>

</PropertyBag>

</ArrayOfPropertyBag>

Listing 3 - XML representation of property bags

Applying an XSLT stylesheet isn’t difficult at all using .NET’s System.Xml.Xsl namespace’s

XslCompiledTransform class.

Chapter 4 – Generic workflows | 76

An XSLT stylesheet that’s useful to transform this piece of XML to a text representation ready to be

sent via e-mail to the medical staff is shown in Listing 4. Notice that the resulting XSLT is easy to

understand thanks to the friendly name/value pair mappings in the property bag(s).

<xsl:stylesheet

xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

version="1.0">

<xsl:output method="text" />

<xsl:template match="/">

The following patients are suitable for a switch from intravenous to

per os medication:

<xsl:apply-templates />

</xsl:template>

<xsl:template match="ArrayOfPropertyBag/PropertyBag">

Bed <xsl:value-of select="Bed"/>

<xsl:text> </xsl:text><xsl:value-of select="First"/>

<xsl:text> </xsl:text><xsl:value-of select="Last"/>

<xsl:text> </xsl:text><xsl:value-of select="ID"/>

<xsl:text> </xsl:text><xsl:value-of select="Number"/>

<xsl:text>

</xsl:text>

<xsl:apply-templates select="Antibiotics" />

</xsl:template>

<xsl:template match="Antibiotics/PropertyBag">

<xsl:text> </xsl:text><xsl:value-of select="Name"/><xsl:text>

</xsl:text>[<xsl:value-of select="Dose"/>]

</xsl:template>

</xsl:stylesheet>

Listing 4 - XSLT stylesheet for XML-to-text translation

The result of this transformation is shown below:

The following patients are suitable for a switch from intravenous to

per os medication:

Bed E307 Marge Simpson 591122 078A93

Ciproxine IV [200mg/100ml flac]

Dalacin C IV [600mg/4ml amp]

Bed E309 Homer Simpson 370818 060A39

Flagyl IV [500mg/100ml zakje]

Depending on the workflow’s level of granularity, different XSLTs can be used. If the workflow acts on

a patient-per-patient basis, the XSLT formatting activity can convert the patient’s advice to a textual

representation that acts as an output for the workflow instance. On the other hand, if the workflow

iterates over all patients, the XSLT can create the whole text output for the daily mail advice in one

single operation. Since we’ve chosen to stick with the first approach as explained earlier, the XSLT

will just take care of the individual patient entries of the output.

Also, the AB Switch agent specification mandates results to be grouped based on the outcome of the

switch advice and to be sorted based on the patient’s bed location subsequently. A simple solution to

Chapter 4 – Generic workflows | 77

implement this requirement consists of retrieving the patients and launching a workflow instance for

each individual retrieved patient. The outputs of all patient processing instances is then collected by

the workflow host that investigates the output parameters indicating the advice (negative or

positive), the bed number (since no in-order completion is guaranteed) and the XSLT output. The

host keeps two OrderedDictionary collections: one for positive advices and another one for negative

advices. Both collections are sorted by bed number. The final mail composition comes down to a join

operation for all advices using simple string concatenation with the StringBuilder class.

5.5 An e-mail activity Almost trivial to implement as a simple exercise on workflow activity creation is an e-mail activity

that allows sending mail messages, for example by taking in a string message composed using the

PrintXmlActivity. Thanks to the availability of a MailMessage class in .NET, creating such an activity is

really straightforward and because of the queuing nature of SMTP (for example using the SMTP

server component available in Windows Server 2003) one doesn’t have to bother much about

guaranteed delivery on the application layer itself. However, as explained further on, alternative

mechanisms based on queuing could provide a more flexible approach to report results by moving

the responsibility for result delivery to the host layer.

6 Other ideas

6.1 Calculation blocks Workflows similar to the AB Switch workflow tend to perform quite a bit of calculation work in order

to produce useful results based on the data fed in. The AB Switch workflow contains a good example

a “calculation block” used to calculate the levophed dose, as shown in Figure 32.

Figure 32 - Calculation block for levophed dose in AB Switch

Chapter 4 – Generic workflows | 78

Different approaches for creating such a calculation block exist, the most straightforward one being

the use of a CodeActivity that does all of the calculation logic. This approach was employed in the AB

Switch implementation, as illustrated in Code 51.

private void calcDose_ExecuteCode(object sender, EventArgs e)

{

double dose = 0.0;

foreach (PropertyBag item in LevophedRates)

dose += ((levopheds[(int)item["PharmaID"]] * 4.0 / 50 * 1000)

/ weight / 60) * (double)item["Rate"];

LevophedDose = dose;

}

Code 51 - Levophed dose calculation in code

When retrieving all of the required data for the calculation in only a few data gathering activities, the

code for the calculation block tends to be pretty easy to implement. In case of AB Switch, one well-

chosen query was used to retrieve all of the input values for the calculation block at once. This is

shown in Code 52.

SELECT PharmaID, MAX(Rate) AS Rate FROM PharmaInformation WHERE PatientID =

@PatientID and PharmaID in (1000582, 1000583, 1000584, 1000586) AND

EnterTime BETWEEN @from AND @to GROUP BY PharmaID

Code 52 - Retrieving input values for levophed calculation

In the original design of AB Switch, the maximum dose for each pharma item was retrieved

individually, resulting in four queries. Using some SQL horsepower such as aggregation and grouping,

this was reduced to one single query, making it more agent-specific but much more efficient.

One could even go one step further by writing a stored procedure that does all of the calculation at

the database layer, returning a singleton result value with the calculated value, reducing the

calculation block to a regular GatherDataActivity. This would put additional stress on the database

however and should be considered carefully.

Though the use of a CodeActivity is by far the easiest implementation for (quite complex) calculation

logic, it doesn’t have the advantage of human readability. As long as the calculation algorithm is well-

known by people who inspect the workflow definition, this shouldn’t be a big problem, especially

when the CodeActivity is well-named. In such as case the calculation block can be considered a black

box. Nevertheless, without doubt situations exist where more flexibility is desirable, reducing the

need for recompilation when calculation logic changes.

A first level of relaxation can be obtained by parameterization of calculation logic using a

PropertyBag that’s fed in by the host. Alternatively, Local Communication Services can be used to

query the host for parameterization information during a workflow instance’s execution lifecycle, in

an interface-based fashion.

However, if the logic itself needs to be modifiable at runtime, more complex mechanisms will have to

be put in place to support this. One such approach could be the use of dynamic type loading (keeping

in mind that types cannot be unloaded from an application domain once they have been loaded, see

[4]) through reflection as outlined in Code 53.

Chapter 4 – Generic workflows | 79

interface ICalculatorManager

{

ICalculator GetCalculator(string calculator);

}

interface ICalculator

{

object Calculate(PropertyBag parameters);

}

class LevophedCalculator : ICalculator

{

private Dictionary<int,int> levopheds = new Dictionary<int,int>();

public LevophedCalculator()

{

levopheds.Add(1000582, 2);

levopheds.Add(1000583, 4);

levopheds.Add(1000584, 8);

levopheds.Add(1000586, 12);

}

public object Calculate(PropertyBag parameters)

{

List<PropertyBag> rates

= (List<PropertyBag>)parameters["LevophedRates"];

double dose = 0.0;

foreach (PropertyBag item in rates)

dose += ((levopheds[(int)item["PharmaID"]] * 4.0 / 50 *

1000) / (double)parameters["Weight"] / 60) *

(double)item["Rate"];

return dose;

}

}

Code 53 - Sample implementation of a calculator

The creation of a calculator activity that invokes a calculator through the ICalculator interface is

completely analogous to the creation of our GatherDataActivity that relied on a similar IQuery

interface. The only difference is the use of reflection in the GetCalculator method implementation, in

order to load the requested calculator implementation dynamically (see Code 54). For better

scalability, some caching mechanism is highly recommended to avoid costly reflection operations

each time a calculation is to be performed.

class CalculatorManager : ICalculatorManager

{

public ICalculator GetCalculator(string calculator)

{

// Get the calculator type from the specified assembly.

// The assembly could also live in the GAC.

string assembly = "put some assembly name here";

string type = calculator;

return (ICalculator)Activator.CreateInstance(assembly, type);

}

}

Code 54 - Dynamic calculator type loading

Chapter 4 – Generic workflows | 80

Yet another approach to dynamic calculation blocks would be the description of the logic by means

of some XML-based language. The .NET Framework comes with a namespace that supports code

generation and compilation, known as CodeDOM. It allows for the creation of object graphs that can

then be translated into any .NET language that has a CodeDOM compiler (C# and VB have one that

ships with the .NET Framework itself).

Using this technology, a piece of calculation code could be translated in a corresponding XML

representation that can be compiled dynamically at runtime when the calculation block is triggered.

Changing the XML file would be sufficient to make a change to the calculation logic. WF itself uses

CodeDOM to serialize conditional expressions from IfElseActivity and WhileActivity activities to XML

that is kept in a .rules file.

Although this approach seems very attractive, .rules files in WF look frightening. For example, a

declarative rule condition as simple as “Age >= 18” is translated into a 24-lines XML file. Though

human readability is a top feature of XML, it doesn't shine through in this case… CodeDOM

essentially is a framework-supported abstract syntax tree representation that – without aid of a user-

friendly tool à la Excel to define calculations – isn’t ready for consumption by end-users. Because of

this we didn’t investigate the path of CodeDOM usage for calculation definitions, but future research

and development of a good calculation definition tool might be interesting.

6.2 On to workflow-based data processing? The described set of custom activities is pretty complete to compose the lion’s part of most data-

driven workflows. A slight amount of manual coding is still required though, especially to apply

transformations on property bags or to pass parameters to various places. Most of this plumbing is

caused by the fact that WF doesn’t have a direct notion of dataflow, except for explicit property

binding. Also, workflows are no pipeline-based structures that allow for easy composition and

chaining, something that’s largely solved by our concept of property bags although manual binding of

properties is still required.

During the research of generic workflow composition, an automatic data passing mechanism similar

to a pipeline structure was investigated briefly but there seems to be no direct easy solution except

for automatic code generation of the property binding logic. After all, such properties are required by

WF to keep a workflow instance’s state in order to recover nicely from workflow dehydration.

Nevertheless such a pipeline-based approach for data processing in workflows would be interesting

from a visual point of view, allowing data operations such as joins to be composed using a designer

while leaving a big part of the execution to the runtime, allowing for automatic parallelism which

might be especially interesting given the recent evolution to multi-core machines.

It seems though that the current framework provided by WF doesn’t provide enough flexibility to

make this possible. Tighter integration with a DBMS might be desirable, as well as a more expressive

flowgraph mechanism that allows to express complex constructs like different types of relational

joins, filtering and projection operations.

6.3 Building the bridge to LINQ LINQ stands for Language Integrated Query and is part of Microsoft’s upcoming .NET Framework 3.5

release, codenamed “Orcas”. It enables developers to write query expressions directly in a general

purpose programming language like C# 3.0 or VB 9.0. An example is shown in Code 55.

Chapter 4 – Generic workflows | 81

var res = from patient in db.Patients

where patient.ID == patientID

select new { p.FirstName, p.LastName, p.PatNumber }

Code 55 - A simple LINQ query in C# 3.0

This technology has several benefits including the unification of various data domains ranging from

relational over XML to in-memory objects (even allowing joins between domains), the generation of

efficient queries at runtime, strong type checking against a database model, etc.

From a workflow’s point of view, the LINQ entity frameworks could be useful to build the bridge

towards a strong-typed approach for query execution in a workflow context, using an alternative

GatherDataActivity implementation that binds to a LINQ query or its underlying expression tree

representation in some way.

That being said, the future applicability of LINQ in this case is still to be seen. One question that will

have to be answered is the availability of Sybase database support which is highly unlikely to be

created by Microsoft itself, although third parties can use LINQ’s extensibility mechanism to support

it. Time will tell how LINQ will evolve with respect to WF-based applications, if such a scenario is even

under consideration for the moment.

6.4 Asynchronous data retrieval In section 4 of this chapter, we’ve gone through the process of creating a fairly simple synchronous

data retrieval block. However, to boost parallelism even further we could go through the design

process of creating an asynchronous data retrieval block by leveraging WF’s scheduling mechanisms,

which is very comparable with classic threading but in this case orchestrated by the WF scheduler.

Basically, an activity (comparable to a thread) can indicate to WF that it’s performing some work in

the background by returning the Executing enumeration value in its Execute method. This is more or

less equivalent to the concept of voluntary thread yielding, putting the scheduler in control to run

other activities (from other instances) while waiting for a result to become available. An example of

this mechanism is illustrated in Code 56.

class AsyncGatherDataActivity : GatherDataActivity

{

protected override ActivityExecutionStatus Execute(

ActivityExecutionContext executionContext)

{

IQueryManager qm =

executionContext.GetService<IQueryManager>();

QueryEventArgs e = new QueryEventArgs();

e.Query = qm.GetQuery(Query);

base.Invoke<QueryEventArgs>(Process, e);

return ActivityExecutionStatus.Executing;

}

void Process(object sender, QueryEventArgs e)

{

ActivityExecutionContext context =

sender as ActivityExecutionContext;

Results = e.Query.Execute(Parameters);

context.CloseActivity();

}

}

Chapter 4 – Generic workflows | 82

class QueryEventArgs : EventArgs

{

private IQuery query;

public IQuery Query

{

get { return query; }

set { query = value; }

}

}

Code 56 - Asynchronous data retrieval

For more advanced execution control, one can use WF’s queuing mechanisms to queue work that

can be retrieved elsewhere for further processing. This concept would drive us too far from home

however. For a detailed elaboration, we kindly refer to [14].

6.5 Web services In case reuse of data gathering mechanisms is desirable, different mechanisms can be employed to

establish this. Our workflow-based data processing could act both as a consumer of a data

publication mechanism or as a provider for data itself.

In the former case, the query manager could be implemented to call a data retrieval web service that

exposes a façade with parameterized queries. In such a case, mapping the parameter types from the

property bags is a trivial one-on-one mapping of .NET types from entries in property bags to method

call parameters on the web service proxy. Reflection could be used to find out about the ordering of

parameters. In case one wants to realize a fully dynamic query manager that can adapt to new

queries right away, WSDL or metadata exchange mechanisms will have to be employed to discover

the data retrieval web methods with parameterization information.

When acting as a publisher, many alternative implementation methodologies are applicable. WF

comes with built-in activities for web service communication that are based on classic web services,

otherwise known as .asmx web services in ASP.NET. Using this mechanism is really easy to do but

provides little out-of-the-box flexibility with respect to recent WS-* standards. Therefore, one might

prefer to go through the burden of manual service implementation using WCF, which opens up for

lots of new functionality with support for the latest web service standards. Also, using WCF one can

hook up many addresses and bindings for one single service contract, for example allowing a service

to be called over very efficient TCP/binary communication using .NET Remoting on the intranet,

while providing alternative communication mechanisms towards other platforms using web service

standards. Finally, queue-based communication using MSMQ can be plugged into WCF as well.

Both approaches could be combined as well, publishing (advanced) workflow-based data processing

blocks through web services and consuming these in other workflows again. Combining this with

WCF’s multi-protocol publication mechanisms seems a very attractive option, especially because it

allows for very efficient local communication (even using named pipes between processes on a single

machine) as well as web service standards compliant communication with other hosts, while keeping

the overall distributed system architecture highly modular.

6.6 Queue-based communication Agents like AB Switch are only executed on a periodic basis, making these suitable candidates for

batched processing based on queued requests. While the agent is asleep, processing requests could

Chapter 4 – Generic workflows | 83

be queued up using mechanisms like MSMQ. When the agent is launched, for example by the

Windows Scheduler, the batch of work is retrieved from the queue, processing is done and results

are calculated. Such an approach could prove useful when triggers are used around the system to

determine work that has to be done in a deferred way. For example, the online medical database

could monitor for specific values that are changing or even for certain threshold values. When such

an event happens, work could be queued up to the agent for later (more thorough and expensive)

processing when the system isn’t under high load. This way, preprocessing of data can be performed,

decreasing the agent’s amount of work. In short, we can speak about a “push” mechanism.

The other way around, the agent could operate in a “pull” manner, actively waiting for requests to

come in through a queue on a permanent basis. Although there’s a high correspondence to a regular

synchronous invocation mechanism, this allows callers to act in a “fire-and-forget” fashion by

pushing work to a queue without waiting for a result (similar to a one-way web method invocation).

By leveraging extended queuing techniques available in MSMQ, such as dead-letter queues or

transactional queues, the overall reliability of the agent could be further improved.

Yet another application of queues is the publication of results, similar to a mail queue, ready for

consumption by some other component on the network. Using this technique, the workflow host’s

complexity can be reduced because workflow instances don’t produce results that have to be

captured by the host. By publishing results to a queue, reliability can be increased once more and

there’s less load on the workflow host. The latter benefit is relevant since inappropriate resource

usage in a workflow hosting application could lead to scheduling problems. By queuing results, the

amount of code running on the host is reduced, also reducing the possibility for uncaught exceptions

to bring down the host (and hence the agent) with possible data loss if workflow instances haven’t

been persisted to disk. To set the reader’s mind, we recall the mechanism used in a host to get

output data back from terminated workflow instances:

using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())

{

//...

workflowRuntime.WorkflowCompleted +=

delegate(object sender, WorkflowCompletedEventArgs e)

{

object result = e.OutputParameters["SomeProperty"];

//record result(s)

};

Code 57 - Collecting workflow instance result data

Crashing the thread that retrieves data from the completed workflow instance isn’t a good thing and

can be avoided completely by making the workflow return nothing and having it queue up the

obtained results. Another component (in a separate process or even on another host) can then drain

the queue – optionally in a transactional manner – for further processing and/or result delivery.

7 Putting the pieces together: AB Switch depicted To get an idea of the overall structure of AB Switch as a workflow, take a look at Figure 33. Even

though the workflow isn’t readable on paper, one gets a good idea of the structure of the workflow

because of its graphical nature and the color codes used by our custom activities. Yellow blocks

Chapter 4 – Generic workflows | 84

indicate data gathering, while the green and red blocks act as status indicators driving the decision

process. Observe the inherent parallelization of the workflow too.

Figure 33 - AB Switch in workflow

Chapter 4 – Generic workflows | 85

8 Designer re-hosting: the end-user in control Another interesting feature of WF is called designer re-hosting. The workflow designer that’s

available in Visual Studio 2005 ships as a redistributable component so it can be reused in other

applications. In Chapter 3, paragraph 7 we’ve met the workflow designer already as part of the

WorkflowMonitor where the visualization of running and completed workflow instances is done by

loading the workflow instances from the tracking database, followed by display in a workflow

designer component.

As an example, we’ve extended the Workflow Designer Rehosting Example that’s available online

[15] and ships with the Windows SDK WF samples. The result is visible in Figure 34.

Figure 34 - Workflow designer re-hosting with custom activity libraries

Using designer re-hosting we were capable of composing AB Switch without requiring the Visual

Studio 2005 tools, resulting in a XOML file that can be compiled for execution. As mentioned in

Chapter 2, paragraph 2.3 the WF API has a WorkflowCompiler class aboard that can be used for

dynamic workflow type compilation and that’s exactly what’s happening in the sample application

when calling Workflow, Compile Workflow.

However, to be usable by end-users directly some issues need further investigation, resulting in more

simplification of the tool. The most obvious problem is likely the (lack of) end-user knowledge of

some WF concepts such as property binding. For example, when dragging a GatherDataActivity to

the designer surface, properties have to be set to specify the query name but also to bind the query

parameters and the result object. This is shown in Figure 35.

Chapter 4 – Generic workflows | 86

Figure 35 - WF complexity bubbles up pretty soon

Although all of the workflow designer tools (e.g. the property binding dialog, the rules editor, etc.)

from Visual Studio 2005 are directly available in the designer re-hosting suite, some are too difficult

for use by end-users directly.

One idea to overcome some of these issues is making the re-hosted designer tool more domain-

specific, with inherent knowledge of the query manager in order to retrieve a list of available queries

(notice that the query manager interface will have to be extended to allow this). Once all queries are

retrieved in a strongly-typed fashion (i.e. with full data type information for parameters and

columns), the system can populate the toolbox with all queries available (in the end one could create

a query designer tool in addition to build new queries using the same tool, provided the end-user has

some SQL knowledge). Queries in WF are nothing more than pre-configured GatherDataActivity

blocks, together with a parameterization PropertyBag and a result list of PropertyBags. This approach

reduces the burden of creating and binding input/output properties manually, increasing the

usability dramatically at the cost of a more domain-specific workflow designer host but that should

be just okay.

Nevertheless, some basic knowledge of bindings will remain necessary for end-users in order to bind

one activity’s output to another activity’s input, for example to feed the output from a data retrieval

operation to a PrintXmlActivity. An alternative to make this more user friendly might be the

visualization of all PropertyBag and List<PropertyBag> objects in some other toolbox pane. Bindings

for activities’ default properties could be established then using simple drag-and-drop operations.

Such an approach visualizes the implicit dataflow present in workflow-based applications.

For example, a GatherDataActivity could have its Parameters property decorated with some custom

attribute that indicates it is the default input property. In a similar fashion, the Results property can

then be specified as the default output property. Using reflection, the tool can find out about those

default properties to allow for drag-and-drop based property binding. An early prototype of this

mechanism is shown in Figure 36, where green lines indicate inputs and red lines indicate outputs.

The list on the left-hand side shows all of the (lists of) PropertyBags that are present in the current

workflow, together with their bindings.

Chapter 4 – Generic workflows | 87

Figure 36 - Data flow and property binding visualization

Finally, in order to execute the newly created workflow definition, it has to be compiled and copied

to a workflow runtime host with all of the services configured that enable data gathering. In order to

make it callable from the outside, some kind of interface will need to be provided as well, which

could be generated automatically by inspecting input parameter lists and promoting them to a WSDL

or WCF contract. Building such an auto-deploy tool that takes care of all wiring of the workflow

engine and the web services façade won’t be a trivial thing to do.

Questions unanswered remain, such as the need for some simple debugging support helping end-

users to spot problems in a workflow composition. WF’s activity model supports validators, which

have been implemented for our activities to indicate missing properties. However, validation

messages might look strange in the eyes of a regular end-user. Therefore a good goal is to reduce the

number of properties that have to be set manually, reducing the chance of compilation and/or

validation errors. However, WF base library activities such as IfElseActivity and CodeActivity are likely

to be required in the lion’s part of composed workflows, e.g. to drive decision logic. Such activities

will still require manual configuration, possibly resulting in the weakest link of the chain: the

composition tool is only as simple as the most complex activity configuration…

Also, activities like PrintXmlActivity require more complex parameters, such as complete XSLT files,

which are far from easy to create even for most developers.

In the end, we believe there’s lots of room for research around this topic, in order to make workflows

easily “composable” by end-users themselves, especially in a data-processing workflow scenario.

Chapter 4 – Generic workflows | 88

9 Performance analysis

9.1 Research goal An important question when applying workflow as a replacement for procedural coding is the overall

performance impact on the application. Though performance degradations are largely overshadowed

in long-running workflows, applying workflows for short-running data processing operations can be

impacted by performance issues, especially when these operations are triggered through some

request-response RPC mechanism such as web services. This being said, efficient resource utilization

in long-running workflows matters as well.

In this paragraph we’ll investigate the impact of workflow-based programming on data processing

applications. More specifically we’ll take a look at the performance of the ForeachActivity and the

GatherDataActivity compared their procedural equivalents, the performance characteristics of per-

record based processing using individual workflow instances versus iterative workflows and the

impact of intra-workflow and inter-workflow parallelization.

9.2 Test environment All tests performed were conducted on a Dell Inspiron 9400 machine, with dual core Intel Centrino

Duo processor running at 2.16 GHz with an L1 cache of 32 KB and an L2 cache of 2 MB. The machine

has 2 GB of RAM and is running Windows Vista Ultimate Edition (32 bit). Tests were conducted with

persistence services disabled, with the workflow runtime hosted by a simple console application,

compiled in Release mode with optimizations turned on and started from the command-line without

a debugger attached.

The DBMS engine used throughout our tests is Microsoft SQL Server 2005 Developer Edition with

Service Pack 2, installed on the local machine. Connections to the database are using Windows

authentication and are established via named pipes using the ADO.NET built-in SqlClient. As a sample

database, the well-known Northwind database was installed [16]. We didn’t use the Sybase server in

our tests because of the network speed variance at the test location, possible influences caused by

the VPN network and because of unpredictable loads on the target database. However, tests can be

conducted again easily by putting another query manager in the test application and by replacing the

query definitions to match the target database schema.

Nevertheless, because data-driven workflows are highly dependent on the DBMS performance and

throughput as well as network traffic, we’ll model these processing delays in some of our tests as

outlined further on.

9.3 A raw performance comparison using CodeActivity It might look trivial but it’s interesting nevertheless: what about the performance of a simple “Hello

World!” application written in regular procedural coding versus the workflow-based equivalent? In

this scenario, we’re doing nothing more or less than wrapping one line of code in a CodeActivity

inside a workflow definition.

In order to reduce the effects of launching the workflow runtime, one runtime instance is created to

serve multiple workflow instances during testing (which is also the natural way of operating WF). The

code of the basic test is shown in Code 58, where Workflow1 is a trivial sequential workflow

definition with one CodeActivity.

Chapter 4 – Generic workflows | 89

class Program

{

static int N = 10000;

static void Main(string[] args)

{

Stopwatch sw = new Stopwatch();

sw.Start();

for (int i = 0; i < N; i++)

Console.WriteLine("Hello World!");

sw.Stop();

long l1 = sw.ElapsedMilliseconds;

sw.Reset();

sw.Start();

using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())

{

AutoResetEvent waitHandle = new AutoResetEvent(false);

int n = 0;

workflowRuntime.WorkflowCompleted +=

delegate(object sender, WorkflowCompletedEventArgs e) {

if (++n == N)

waitHandle.Set();

};

for (int i = 0; i < N; i++)

{

WorkflowInstance instance =

workflowRuntime.CreateWorkflow(typeof(Workflow1));

instance.Start();

}

waitHandle.WaitOne();

}

sw.Stop();

Console.WriteLine(l1);

Console.WriteLine(sw.ElapsedMilliseconds);

}

}

Code 58 - Simple procedural performance measurement

Notice this piece of code isn’t thread-safe since the WorkflowCompleted event handler isn’t

synchronized. However, locking introduces a significant overhead that would affect the performance

results for a workflow-based application negatively. Therefore, we don’t fix this issue at the risk of

having a blocked test program when the counter ‘n’ doesn’t reach its final value because of

overlapped write operations. In such a case we simply restart the test application.

We measured results like the following:

Procedural: 1015 ms

Workflow: 3983 ms

So, the difference is about a factor four due to various mechanisms inside WF that are quite resource

hungry, especially the scheduler that has to schedule individual workflow instances as well as each

individual activity inside a workflow.

Chapter 4 – Generic workflows | 90

However, a large part of the overhead is caused by the workflow instance creation cost. To measure

this influence we’ve increased the workflow definition’s complexity by appending a sequence of

additional CodeActivity blocks. The results for 1000 executions are shown in Graph 10.

Graph 10 - Influence of the workflow complexity on execution time

Using a default workflow scheduler configuration, we observe that the gap between procedural and

workflow-based execution is shrinking slightly from a factor 4 down to a factor that drops under 2. In

absolute figures however, workflow remains slower than the procedural equivalent. By tweaking the

number of scheduler threads a slight performance increase can be realized for regular code-based

workflows, as shown in Graph 11 for a workflow definition with a sequence of two CodeActivity

blocks. In this case, it doesn’t make much sense to parallelize write operations to the Console but

when facing database operations with latency, parallelization will pay off as we’ll see further on,

especially when combined with the ParallelActivity from the WF toolbox.

Graph 11 - Tweaking the number of scheduler threads

0

500

1000

1500

2000

2500

1 2 3 4 5 6 7 8 9 10

Exe

cuti

on

tim

e (

ms)

Number of sequential activities

Procedural

Workflow

0

100

200

300

400

500

600

700

800

1 2 3 4 5 6 7 8 9 10

Exe

cuti

on

tim

e (

ms)

Number of scheduler threads

Chapter 4 – Generic workflows | 91

9.4 Calculation workflows with inputs and outputs An important aspect in our approach to data-driven workflows is performing calculations based on

input values that flow in to the workflow instance, producing output values during execution.

Therefore it is good to get an idea about the cost to create workflow instances that take in data and

produce data. To measure this cost, we’ve built a simple workflow that takes in two integer values

and calculates their sum. The code is trivial, consisting of three dependency properties and one

CodeActivity that calculates the sum. The result for 1000 calculations is:

Procedural: 2566 ticks (0 ms)

Workflow: 5728119 ticks (400 ms)

The cost of thousand integer operations can be neglected, giving us a good idea of the overhead

caused by workflow instance creation and input/output communication. To separate the costs of

workflow creation from the code execution inside, we measured the time it takes to create and

execute thousand instances of a void non-parameterized workflow, resulting in 287 ms. Based on

this, the remaining 133 ms can be considered an approximation for the input/output costs for the

three data values used in and produced by the workflow.

In addition we measured the cost associated with pushing the calculation logic outside the workflow

definition using Local Communication Services with a simple calculator as shown in Code 59.

[ExternalDataExchange]

interface ICalculator

{

int Sum(int a, int b);

}

class Calculator : ICalculator

{

public int Sum(int a, int b)

{

return a + b;

}

}

Code 59 - LCS service for a simple calculation

This time the result for 1000 instances is:

Workflow: 7416802 ticks (517 ms)

Subtracting the workflow instance creation cost results in a total processing cost of 230 ms where

the communication cost is the most significant due to the trivial cost for an addition operation. From

this we can conclude that LCS introduces a significant cost. Nevertheless, the flexibility of LCS is much

desired so one should be prepared to pay this cost to establish communication with the hosting

layer.

9.5 Iterative workflows In paragraph of 5.1 of this chapter we introduced a ForeachActivity to allow iterative workflow

definitions without having to rely on a more complex WhileActivity as available in WF directly. For

data-driven workflow definitions, the ability to iterate over a set of data seems an attractive solution

Chapter 4 – Generic workflows | 92

although a more fine-grained approach (e.g. a workflow instance for each individual patient that

requires data processing) might be desirable as pointed out previously.

In this section, a naïve workflow-based implementation is created that uses a ForeachActivity to

iterate over a sequence of numbers that are printed on the screen using the Console class. The goal

is to find out about the overhead introduced by the ForeachActivity when iterating over a sequence

of data, without counting in any cost to retrieve data (which is subject of subsequent analysis).

In Graph 12 the result for various iteration lengths is shown, comparing procedural code and the

workflow-based counterpart. From this test we can conclude that the cost of workflow-based

collection iteration grows faster in function of the number of iterations, compared to a classic

foreach loop. For 100 iterations the workflow variant is about 2.8 times more costly than procedural

code; for 2500 iterations this factor has grown to 3.7 already.

Graph 12 - Performance of the ForeachActivity

This growing gap between procedural and workflow can be explained by the nature of WF work

scheduling. Our ForeachActivity implementation uses the activity event model to schedule the

execution of the loop’s body. The core of the ForeachActivity implementation is shown in Code 60.

When the loop body finishes execution for an iteration, an event is raised that’s used to call the loop

body again as long there are items remaining in the source sequence. For the execution of the loop

body, the WF scheduler is triggered indirectly by calling the ExecuteActivity method on the

ActivityExecutionContext object. This scheduling overhead accumulates over time, causing the gap

between procedural and workflow-based iteration to grow.

Notice that this scheduling mechanism operates on the level of the workflow engine, across many

workflow instances. This means that individual workflow instances might be paused temporarily in

order to give other instances a chance to make progress. Because of this, the average execution time

of an individual workflow instance will increase. Strictly spoken, the ForeachActivity gives the WF

scheduler an opportunity to schedule another workflow instance every time it schedules the work for

the next iteration of the loop. One can compare this with voluntary yielding of threads in systems

with cooperative scheduling. Furthermore, such switching between instances can occur inside the

0

100

200

300

400

500

600

700

800

900

10

0

30

0

50

0

70

0

90

0

11

00

13

00

15

00

17

00

19

00

21

00

23

00

25

00

Exe

cuti

on

tim

e (

ms)

Number of iterations

Procedural

Workflow

Chapter 4 – Generic workflows | 93

loop’s body as well, each time a child activity has completed its work. Essentially, activities in WF are

atomic units of work, which can be subdivided in smaller work units again by using scheduling

mechanisms directly (e.g. through ActivityExecutionContext).

private int index = 0;

protected override ActivityExecutionStatus Execute(

ActivityExecutionContext context)

{

if (Input.Count != 0 && EnabledActivities.Count != 0)

{

ExecuteBody(context);

return ActivityExecutionStatus.Executing;

}

else

return ActivityExecutionStatus.Closed;

}

void ExecuteBody(ActivityExecutionContext context)

{

ActivityExecutionContextManager mgr

= context.ExecutionContextManager;

ActivityExecutionContext ctx

= mgr.CreateExecutionContext(EnabledActivities[0]);

IterationVariable = Input[index++];

Activity activity = ctx.Activity;

activity.Closed += ContinueAt;

ctx.ExecuteActivity(activity);

}

void ContinueAt(object sender, ActivityExecutionStatusChangedEventArgs e)

{

e.Activity.Closed -= this.ContinueAt;

ActivityExecutionContext context = sender as ActivityExecutionContext;

ActivityExecutionContextManager mgr = context.ExecutionContextManager;

ActivityExecutionContext ctx = mgr.GetExecutionContext(e.Activity);

mgr.CompleteExecutionContext(ctx);

if (this.ExecutionStatus == ActivityExecutionStatus.Executing

&& index < Input.Count)

ExecuteBody(context);

else

context.CloseActivity();

}

Code 60 - The ForeachActivity relies on the WF scheduler for loop body execution

At first glance, this loop mechanism seems to be a disadvantage from a performance point of view.

However, it really depends on the contents of the loop. If long-running operations inside the loop

construct are launched in an asynchronous manner, the scheduler can switch active execution to

another workflow instance or a parallel branch in the same workflow instance. When the background

work has been completed, the activity is rescheduled to allow activity execution completion.

Chapter 4 – Generic workflows | 94

9.6 Data processing Now that we’ve gained a better idea about the overall performance impact of workflow creation,

procedural code execution inside workflows, communication mechanisms and loop constructs, it’s

time to move our focus to the data processing side of the story. We’ll investigate a few different

options in detail to decide on the best possible approach to write workflow-based data processing

applications that inherit most of WF’s benefits such as suitability for human inspection, tracking

services, etc.

9.6.1 Iterative data processing

A first option is to leverage the power of our ForeachActivity in order to process a batch of records by

iterating over them. Despite the fact that this approach has some limitations on the field of tracking

(as discussed before in paragraph 5.1) it is a good candidate for agents that have to process a large

set of items resulting in an information aggregation. An example is a reporting engine that collects

data from various sources, iterates over these sources and presents aggregated information about all

of the processed records in a nice report.

To analyze the performance cost of such a construct, a simple one-loop workflow definition was

built, as shown in Figure 37. Data is retrieved from the Northwind sample database’s Products table

using a simple non-parameterized SELECT statement that retrieves a set of records using a TOP

clause.

Figure 37 - An iterative data processing workflow

The cost of the loop body is kept to a minimum, calculating the product of each product’s unit price

and the number of items still in stock. These values are summed up to result in a total product stock

value. Although such a data processing mechanism could be written in a much more efficient manner

by means of (server-side) DBMS features such as aggregates and cursors, a workflow-based variant

might be a better choice to clarify the processing mechanism by means of a sequential diagram. Of

Chapter 4 – Generic workflows | 95

course more advanced scenarios exist, for example where data is grabbed from the database as input

to much more complex processing, possibly in a distributed manner across multiple agents.

In order to have some ground to compare on, an alternative implementation was created using

regular procedural ADO.NET code, which is shown in Code 61.

using (SqlConnection conn = new SqlConnection(dsn))

{

decimal total = 0;

SqlCommand cmd = new SqlCommand(

"SELECT TOP " + N + " p.ProductID, p.ProductName, " +

"p.UnitsInStock, p.UnitPrice, p.SupplierID, p.CategoryID " +

"FROM Products AS p, Products AS p1", conn);

conn.Open();

SqlDataReader reader = cmd.ExecuteReader();

while (reader.Read())

{

total += (decimal)reader["UnitPrice"]

* (short)reader["UnitsInStock"];

}

}

Code 61 - Procedural coding equivalent for iterative data processing

Notice the little trick in the SELECT statement’s FROM clause that takes the Cartesian product of two

(identical) tables just to get a massive amount of rows so that the TOP row restriction clause doesn’t

run out of juice when measuring performance for large sets of data, exceeding the original table’s

row count.

For the query manager implementation that’s hooked up to the workflow engine we use the same

ADO.NET APIs as well as the same connection string. This causes data gathering to take place using

the same underlying connection technology and parameterization.

The results of the performance measurement are shown in Graph 13.

Graph 13 - Iterative data processing performance results

0

500

1000

1500

2000

2500

3000

10

0

30

0

50

0

70

0

90

0

11

00

13

00

15

00

17

00

19

00

21

00

23

00

25

00

Exe

cuti

on

tim

e (

ms)

Number of data records

Procedural

Workflow

Chapter 4 – Generic workflows | 96

We clearly see a much bigger cost associated with the workflow-based approach, accumulating a lot

of performance killers, for a large part caused by the fact that all data results are gathered in one

single operation, ruling out the high efficiency of the forward-only SqlDataReader, replacing it by a

more complex in-memory iteration mechanism. While the procedural code executes in only a few

milliseconds, workflow takes about 1 millisecond per iteration. Also the effects of putting data in a

PropertyBag and getting it out again shouldn’t be underestimated, even though these operations

have only O(1) complexity. This cycle of putting data in during the data gathering phase and getting it

back out is causing costly boxing/unboxing and casting operations to take place, due to our use of the

type “object” for values in the PropertyBag.

From this we can conclude that for easy data retrieval operations the raw ADO.NET reader-based

loop is unbeatable by far, at the cost of black-boxed code that doesn’t reflect the original intention

very well to end-users.

9.6.2 Nested data gatherings

Simple data retrieval operations as the one above don’t gain much benefit from a workflow-based

representation. Once things get more complex, e.g. when data gathering activities are nested in

another data gathering’s iteration mechanism, workflow becomes more and more attractive.

An example of such a structure is depicted in Figure 38. At the root level, products are retrieved

using a similar query as the one used in the previous section. As part of the processing of these

product records, other queries are launched in order to retrieve data from tables that have a

relationship with the parent table. In the Northwind database, each product has a foreign key

pointing to a category and another one pointing to the product’s supplier. These queries are

parameterized ones that take in the current product’s PropertyBag as a parameter, in order to obtain

the foreign key values for “relationship traversal”.

Notice that both nested queries are executed in parallel, creating a level of intra-workflow

parallelism without having to bother about thread safety ourselves. On the procedural side we didn’t

implement such a parallel data retrieval mechanism due to the complexity it imposes. The procedural

code used for performance comparison is shown in Code 62.

using (SqlConnection conn = new SqlConnection(dsn))

{

decimal total = 0;

SqlCommand cmd = new SqlCommand(

"SELECT TOP " + N + " p.ProductID, p.ProductName, " +

"p.UnitsInStock, p.UnitPrice, p.SupplierID, p.CategoryID " +

"FROM Products AS p, Products AS p1", conn);

conn.Open();

SqlDataReader reader = cmd.ExecuteReader();

while (reader.Read())

{

SqlCommand cmd2 = new SqlCommand(

"SELECT SupplierName FROM Suppliers " +

"WHERE SupplierID = @SupplierID", conn);

cmd2.Parameters.Add("@SupplierID", SqlDbType.Int).Value

= reader["SupplierID"];

SqlDataReader reader2 = cmd2.ExecuteReader();

while (reader2.Read())

;

SqlCommand cmd3 = new SqlCommand(

Chapter 4 – Generic workflows | 97

"SELECT CategoryName FROM Categories " +

"WHERE CategoryID = @CategoryID", conn);

cmd3.Parameters.Add("@CategoryID", SqlDbType.Int).Value

= reader["CategoryID"];

SqlDataReader reader3 = cmd3.ExecuteReader();

while (reader3.Read())

;

total += (decimal)reader["UnitPrice"]

* (short)reader["UnitsInStock"];

}

}

Code 62 - Nested data gathering using procedural code

Figure 38 - Nested data gathering operations

Chapter 4 – Generic workflows | 98

However, this code won’t execute correctly unless the connection string is altered to enable Multiple

Active Result Sets (MARS), a feature available in ADO.NET 2.0 with SQL Server 2005 [17]. The

modified connection string should contain the following:

MultipleActiveResultSets=True

This feature allows multiple readers to share one database connection and overcomes former

limitations of the Tabular Data Stream (TDS) protocol of SQL Server, which is also used by Sybase.

However, SQL Server 2005 is the only product today that supports MARS out of the box. For similar

nested DbDataReader implementations using other database products, one would have to open

multiple connections to the database to perform parallel data retrieval. Alternatively, the parent

query results could be loaded in memory first – just like the GatherDataActivity does – ready for

subsequent iteration that executes the child queries for each parent-level record.

Again, this sample could be implemented more efficiently by means of SQL join operations but such

an approach might be impossible if advanced calculation logic or condition-based branching has to

be performed as part of the data processing job, as is the case in AB Switch. Also, if the data

processing spans multiple databases or different kinds of data sources (e.g. calling a web service

inside the query manager implementation) or if cross-domain joins need to be performed (e.g.

combining data from a relational database with data from an XML source), such optimizations will

have to leave the scene. We should mention however that future technologies like LINQ might

overcome such limitations, especially the cross-domain querying one.

The results of this test are presented in Figure 39. This time we observe that the performance of the

procedural code variant is hurt by the nested query operations, compared to the results in the

previous section. At the same time, the workflow based approach keeps its linear trend but it still

lags behind with a factor 4 or so.

Figure 39 - Nested data gathering operations performance results

The inherent parallelism of the workflow doesn’t help much; most likely it even hurts the

performance a bit because we’re putting a high load on the scheduler inside the outer loop’s body by

creating two parallel activities that both have a relatively short execution time.

0

1000

2000

3000

4000

5000

6000

7000

8000

9000

10000

100 300 500 700 900 1100 1300 1500 1700 1900 2100 2300 2500

Exe

cuti

on

tim

e (

ms)

Number of data records

Procedural

Workflow

Chapter 4 – Generic workflows | 99

9.6.3 Intra-workflow parallelism

In the previous section, we investigated the result of two parallel data gathering activities inside a

workflow definition. A logical question to ask is whether or not an increased parallelism boosts

performance much. To investigate this, we conducted a series of tests that put additional data

gathering branches in the core ParallelActivity of Figure 38. The results of this test can be found in

Graph 14.

Graph 14 - Performance of parallel data gathering branches

Though the workflow-based variant doesn’t catch up with the procedural coding – which is still doing

serialized data retrieval operations by the way – the situation is improving compared to the original

tests with no or little parallelism inside a workflow.

However, we don’t expect much more progression when staying in an iterative data processing

model where only intra-workflow parallelism can be employed. The overhead of the iteration logic

seems to be a limiting factor and sequential processing of records doesn’t allow for much flexibility,

neither for the developer nor for the workflow runtime. Therefore, we move our focus towards inter-

workflow parallelism.

9.6.4 Inter-workflow parallelism

Instead of using a huge outer loop mechanism inside a workflow definition it makes more sense to

use one workflow instance per unit of work, e.g. the processing of one patient in a medical agent. In

this section, we’ll investigate this approach which was also taken for the AB Switch agent. For

aggregation kind of tasks, data can be reported back to the workflow host where it’s collected for

subsequent aggregation and/or reporting, optionally even by triggering another workflow that takes

in the collected set of data results and returns the aggregated result.

This time, our workflow definition is reduced to a very simple set of two parallel queries that return

some data based on a workflow parameter (in this case the product’s unique identifier) passed as a

dependency property. The result produced by the workflow instance is reported back to the “caller”

by means of yet another dependency property. Such a fine-grained workflow definition is shown in

Figure 40.

0

2000

4000

6000

8000

10000

12000

2 3 4 5 6 7 8 9 10

Exe

cuti

on

tim

e (

ms)

Number of parallel data gathering branches

Procedural

Workflow

Chapter 4 – Generic workflows | 100

Figure 40 - Fine-grained workflow definition

Next, we’ll move the outer iteration logic to the workflow host, using a SqlDataReader iterating over

the products that require processing. For each such product in the result set, a workflow instance is

created that will perform subsequent processing for the individual product, in this case reduced to

some data gathering operations with little calculation involved. This is illustrated in Code 63.

using (SqlConnection conn = new SqlConnection(dsn))

{

SqlCommand cmd = new SqlCommand("SELECT TOP " + N +

" p.ProductID, p.SupplierID, p.CategoryID" +

" FROM Products AS p, Products AS p1", conn);

conn.Open();

SqlDataReader reader = cmd.ExecuteReader();

while (reader.Read())

{

Dictionary<string, object> args

= new Dictionary<string, object>();

args["Parameters"] = new PropertyBag();

args["Parameters"]["CategoryID"] = (int)reader["CategoryID"];

args["Parameters"]["SupplierID"] = (int)reader["SupplierID"];

WorkflowInstance instance = workflowRuntime

.CreateWorkflow(typeof(Workflow3), args);

instance.Start();

}

}

Code 63 - Moving the outer loop to the workflow host

Chapter 4 – Generic workflows | 101

Notice that the iteration logic on the host can be implemented manually or can call into the query

manager as well. In other words, the use of the query manager isn’t an exclusive right for

GatherDataActivity blocks, making it even more “generic” in the broad sense of the word because it

hasn’t tight bindings with WF.

The performance results of this workflow-based approach compared to a procedural equivalent are

shown in Graph 15. This time workflow clearly is the winner because of the inherent parallelism

between workflow instances. Although such parallelism could be created manually in procedural

code too, it’s much easier to do in a workflow-based design where one doesn’t have to worry about

thread safety and so on.

Graph 15 - Inter-workflow parallelism

9.6.5 Simulating processing overhead

The results shown so far do not take possible network delays and database processing latency into

account. After all, when facing delays on the arrival of data requested by a query, serial query

execution accumulates such delays really fast, bringing down the entire application’s scalability. The

inherent parallelism that can be realized using workflow seems an attractive escape from this issue,

so it’s the subject of this last performance analysis case.

In order to simulate such a delay, we apply a simple trick to the query definitions by means of a

WAITFOR construct from SQL Server. An altered query is shown in Code 64.

<Query Name="GetSupplier" Category="Demo">

<Statement>

SELECT CompanyName FROM Suppliers WHERE SupplierID = @SupplierID;

WAITFOR DELAY '00:00:01'

</Statement>

<Inputs>

<Parameter Name="@SupplierID" Type="int" />

</Inputs>

<Outputs>

<Column Name="CompanyName" Type="nvarchar" Nullable="No" />

</Outputs>

</Query>

Code 64 - Query with simulated data retrieval delay

0

200

400

600

800

1000

1200

1400

1600

1800

2000

10

0

30

0

50

0

70

0

90

0

11

00

13

00

15

00

17

00

19

00

21

00

23

00

25

00

Exe

cuti

on

tim

e (

ms)

Number of data records

Procedural

Workflow

Chapter 4 – Generic workflows | 102

This suspends execution of the query for a specified amount of time, mimicking the influence of

some delay during data gathering. The result of this query when applied to workflow instances as

defined in Figure 40 is represented in Graph 16. As expected, the procedural code execution time

shows the serialized data gathering costs, about 2 seconds for each parent record. In the workflow-

based variant, all instances are started at the same point in time causing multiple queries to run in

parallel, shadowing each other’s processing delay. Depending on the network bandwidth and

possible restrictions on the number of available database connections in the connection pool, results

will vary but workflow-based processing has a serious advantage in here.

Graph 16 - Speeding up overall processing by reducing data retrieval delay

Furthermore, the number of parallel threads available to WF can be configured using the

DefaultWorkflowSchedulerService, as outlined below.

using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())

{

workflowRuntime.AddService(new DefaultWorkflowSchedulerService(n));

Code 65 - Tweaking the workflow scheduler

In Code 65, n stands for the number of worker threads available to the WF scheduler. By default, this

number is 5 for uni-processor machines and 4 times the number of processors for multi-processor

machines, including multi-core ones [18]. On my machine with a dual core processor, this results in a

total of 8 worker threads when not overriding this default.

We expect a better throughput when increasing the number of available threads to the WF

scheduler. A little test was conducted to investigate this based on the workflow definition from

Figure 40, running 10 to 100 parallel workflow instances with 5 to 25 threads. The result of this test is

depicted in Graph 17. Similarly, the overall performance of the system was measured for 100 parallel

workflow instances with threads numbers ranging from 5 to 50 (see Graph 18).

From these graphs we can conclude that it is highly recommended to tweak the default settings

when optimizing for performance. However, increasing the number of threads does put a higher load

on the system which has to be considered carefully on production servers. Also, there’s a ceiling to

0

5000

10000

15000

20000

25000

1 2 3 4 5 6 7 8 9 10

Exe

cuti

on

tim

e (

ms)

Number of workflow instances

Procedural

Workflow

Chapter 4 – Generic workflows | 103

the improvement that can be established by inflating the thread pool, in our case around 25 threads.

As usual, the proof of the pudding is in the eating, so general recommendations are almost

impossible to formulate: there is no silver bullet and testing should be the ultimate guide to

determine an optimal configuration for a workflow application on a specific machine.

Graph 17 - Tweaking the workflow scheduler thread pool

Graph 18 - Adding threads to the workflow scheduler thread pool

10 Conclusion The creation of a set of generic building blocks to assist in the definition of domain-specific

workflows is definitely a good idea to improve the approachability of workflow definition by people

from the field, e.g. medical staff. In this chapter we took a closer look at such an approach to create

data-driven workflows for medical agents. Using just a few custom blocks – including one to gather

data, another one to iterate over sets of data and one to format output data – we were capable of

expressing fairly complex agent structures that have a non-trivial procedural equivalent.

0

5000

10000

15000

20000

25000

30000

35000

40000

45000

10 20 30 40 50 60 70 80 90 100

Exe

cuti

on

tim

e (

ms)

Number of workflow instances

5 threads

Default (8)

10 threads

15 threads

20 threads

25 threads

0

5000

10000

15000

20000

25000

30000

35000

40000

45000

5 10 15 20 25 30 35 40 45 50

Exe

cuti

on

tim

e (

ms)

Number of threads

Chapter 4 – Generic workflows | 104

However, at this point in time it is not possible to get rid of all procedural aspects of such an agent

application because of various reasons. For instance, calculation blocks are still to be written as

regular code, nested inside a CodeActivity or callable through Local Communication Services. Also,

the dataflow in a workflow-based application is realized through manual property bindings which

require a bit of code (though it can be auto-generated). Nevertheless, by black-boxing the remaining

pieces of code in custom activities, code in a workflow definition can be reduced to (IDE-supported)

property bindings and a minor amount of code for manipulation of PropertyBag objects where

appropriate.

Furthermore, having such a set of custom activities opens the door to composition by the end-user

using workflow designer re-hosting. More research has to be carried out to evaluate the feasibility of

this approach in practice but all required pieces of the puzzle are there to make it possible.

Using a generic approach to data, a big deal of flexibility can be realized because of the clean

separation between the query manager back-end and the GatherDataActivity front-end. Developers

can implement the query manager and query interfaces at will to match their needs, without

touching the inside of any workflow definition whatsoever. Our approach to query storage using XML

is minimalistic but sufficient for the scope of this work. Additional implementation efforts are likely

desirable in order to optimize performance or to store queries in some central query store. We

should also point at the possibilities for optimization by tuning the queries and by moving more

complex logic to the database tier. However, in the latter case we get trapped by procedural coding

again, this time on the database layer, until workflow extends its horizons to the DBMS.

Another important consideration when applying this approach in practice is the level of granularity of

workflow definitions. Using the ForeachActivity block it is possible to translate an entire agent into

just one single workflow definition, iterating over the gathered data to perform processing. However,

in various cases it makes more sense to make the workflow definition fine-grained, e.g. representing

the processing for one single patient record. Philosophically, this builds the bridge between workflow

instances and data records in a kind of workflow-relational “WR” mapping. Although this moves a bit

of database processing logic to the host layer (e.g. to iterate over the sequence of records to be

processed by workflow instances) this approach greatly benefits from WF’s inherent cross-workflow

instance parallelism, making the workflow-based agent outperform its procedural equivalent.

This granularity consideration also applies on the hosting level when considering a web services

façade. It’s perfectly possible to publish an entire workflow through a web service, optionally using

asynchronous one-way method call semantics for long-running workflows. However, one could also

wrap individual blocks, such as the GatherDataActivity block, inside web services. This can be done

either by encapsulating these blocks inside another workflow definition (kind of a “singleton”

workflow definition) or by bypassing WF completely, exposing the inner workings of the custom

activity directly to the outside world. The former approach is well-suited for blocks that have a long-

running nature, while the latter approach reduces load on the workflow engine.

Using WCF, the best of both worlds can be combined for (web) service publication. Workflows or

single activities can be exposed through services while on their turn consuming other services

internally. Because WCF is protocol-agnostic, wrapping an activity’s functionality in a service contract

shouldn’t hurt performance too much when being used on the same machine since fast inter-process

communication mechanisms can be used. We didn’t elaborate much on the WCF aspect of the story

Chapter 4 – Generic workflows | 105

in this work, so a more thorough investigation of WCF-based SOA architectures combined with WF

might be a good candidate for future research.

Performance-wise, workflow is much slower than procedural equivalents if one doesn’t exploit WF’s

rich capabilities such as parallel activities and inter-workflow parallelism. Therefore, replacing chunks

of complex procedural code by a workflow-equivalent doesn’t pay off unless some degree of

parallelism can be reached. A good level of workflow definition granularity combined with parallel

data gathering operations seems to be a good case where a workflow equivalent is beneficial. In such

a case, writing a multi-threaded procedural equivalent would be more costly and time-consuming to

get it right, while WF can provide parallelism out of the box.

Even if a workflow-equivalent for some system results in performance degradation, one shouldn’t

forget about additional advantages of WF such as long-running stateful processing mechanisms,

tracking services and the graphical representation of application logic. These could outweigh the

performance costs by far.

Putting the pieces together, generic building blocks make dynamic instrumentation as discussed in

the previous chapter even more approachable and useful. For example, when injecting inspection

blocks in a workflow dynamically in order to perform production debugging or state inspection, the

generic approach to data makes inspection (of PropertyBag objects) easier to do. Furthermore, such

an inspection point could be injected without any additional property bindings too, reflecting against

its predecessor and/or successor activity to grab a PropertyBag property for inspection. Such

methodology exploits the implicit dataflow of workflows by making an inspection point context

aware in a positional manner (e.g. an inspection point after a GatherDataActivity could report the

output data from its predecessor).

Finally, one should be extremely cautious when combining large data gathering operations with long-

running workflow instances. After all, GatherDataActivity stores the retrieved data in a property on

the workflow instance which might be subject of dehydration (i.e. persistence of workflow instance

state information) during a suspension operation, typically happening in long-running workflows.

This doesn’t only make the suspended workflow instance bigger in size; it also introduces the risk of

working with outdated information once the workflow instance is resumed. Therefore, lightweight

queries are more desirable and a fine granularity of workflow instances will help too. Otherwise, it’s

not unimaginable to build a workflow that creates copies of the source tables, causing performance

bottlenecks and possible side-effects. If the considered workflows are long-running by nature, it’s

best to avoid the storage of retrieved data inside a workflow instance altogether, replacing it by live

queries through LCS every time data is needed, avoiding outdated information to be read.

Chapter 5 - Conclusion | 106

Chapter 5 - Conclusion

In this work we investigated the Windows Workflow Foundation platform of the .NET Framework 3.0

on two fields: dynamic adaptation and its suitability as a replacement for procedural coding of data-

driven processing agents.

To support dynamic adaptation of workflow instances, we created an instrumentation engine layered

on top of the Dynamic Updates feature provided by WF. We distinguished between three update

types: the ones conducted from inside the workflow instance itself (internal modification), updates

applied to a workflow instance from the outside on the hosting layer (external modification) as well

as a combination of both, where external modification is used to inject an internal modification in a

workflow instance. Using this instrumentation mechanism, aspects can be injected in a workflow to

keep the workflow definition clean and smooth. Examples include logging, state inspection, time

measurement and authorization. It was found that the instrumentation engine provides enough

flexibility to adapt workflow instances in various ways and at various places. From a performance

point of view, internal modification outperforms the other options. On the other hand, external

modification is more flexible because of its direct access to contextual information from the host and

because workflows don’t have to be prepared for possible changes. Injection of adaptation logic

using the instrumentation framework combines the best of both worlds.

The second part of this work covered the creation of generic building blocks for easy composition of

data-driven processing agents, applied to the AB Switch agent which is used by the Intensive Care (IZ)

department of Ghent University (UZ Gent). It was shown that with a relatively low number of basic

building blocks a great level of expressiveness can be realized in a generic manner. The core block of

this design is without doubt the GatherDataActivity that retrieves data from some data source based

on a query manager that allows for flexible implementation. Such a set of building blocks also opens

up for granular publication through (web) services, for example using WCF. Other communication

mechanisms such as queue-based message exchange have been touched as well.

We should mention that WF is the youngest pillar of the .NET Framework 3.0 and maybe the most

innovative one for a general-purpose development framework. Till the advent of WF, most workflow

engines were tied to some server application like BizTalk that applies workflows in a broader context,

e.g. to allow business orchestrations. With WF, the concept of workflow-driven design has shifted to

all sorts of application types with lots of practical use case scenarios. Nevertheless it remains to be

seen how workflow will fit in existing application architectures, also in combination with SOA. One

could question WF’s maturity but to our humble opinion WF’s BizTalk roots contribute to this

maturity a lot. During the research, we saw WF transition out of the beta phase to the final release in

November 2006, each time with lots of small improvements.

To some extent, the impact of the introduction of workflow in a day-to-day programming framework

could be compared to the impact of other programming paradigms back in history, such as OO

design. With all such evolutions, the level of abstraction is raised. In the case of WF, things such as

persistence of long-running state and near real-time state tracking are introduced as runtime

services that free developers from the burden of implementing these manually. Nevertheless, one

has to gain some basic knowledge – and feeling over time – concerning how these runtime services

Chapter 5 - Conclusion | 107

impact the overall application’s performance and other quality attributes. It’s pretty normal to get

caught by these implications when facing a first set of application designs where workflow seems an

attractive solution. As developers gain more real-life experiences with the framework, the evaluation

process for workflow applicability will get sharper and more accurate.

Furthermore, some of WF’s runtime services will prove more valuable over time, such as the WF

scheduling service. This scheduler can be compared to the operating system scheduler, but applied

on another level: OS thread scheduling corresponds somewhat to activity scheduling in WF. In

today’s world of multi-core and many-core processors, the need for easier parallel programming

increases every day. WF can help to answer this need because of its inherent parallelism both inside

a workflow instance and across multiple workflow instances. Our performance analysis has shown

that such a statement is realistic, especially in data-driven workflows that gather data from various

data sources and that process records in parallel workflow instances.

In conclusion, one has to put all of the useful WF features on the balance to weigh them against

possible performance implications (or improvements), a different development methodology, etc. A

decision balance, containing the most crucial features and considerations, is depicted in Figure 41.

For long-running processing mechanisms, such as processes that require manual approval from

human beings or that have to wait for external services to provide responses that can suffer from

serious delays, workflow-based development should be a no-brainer. For short-running data

processing agents making a decision is more complex. In such a case, one should take performance

implications into account while considering advantages such as visual representation, runtime state

inspection and the possibility for easy dynamic adaptation and aspect cross-cutting using an

instrumentation engine.

Figure 41 - Workflow or not? A decision balance

WF features Considerations

Updates

Performance

Granularity

SOA

Persistence

Scheduling

Tracking

Designer

Appendix A – ICSEA’07 paper | 108

Appendix A – ICSEA’07 paper

The paper “Dynamic workflow instrumentation for Windows Workflow Foundation” is included on

the following pages. It was submitted to ICSEA’07 and accepted for publication and presentation at

the conference.

I want to thank K. Steurbaut, S. Van Hoecke, F. De Turck and B. Dhoedt for their support in writing

this paper and making it successful thanks to their contributions and incredible eye for detail.

Appendix A – ICSEA’07 paper | 109

Dynamic Workflow Instrumentation for Windows Workflow Foundation

Bart J.F. De Smet, Kristof Steurbaut, Sofie Van Hoecke, Filip De Turck, Bart Dhoedt

Ghent University, Department of Information Technology (INTEC), Gent, Belgium

{BartJ.DeSmet, Kristof.Steurbaut, Sofie.VanHoecke, Filip.DeTurck, Bart.Dhoedt}@UGent.be

Abstract

As the complexity of business processes grows, the

shift towards workflow-based programming becomes

more attractive. The typical long-running

characteristic of workflows imposes new challenges

such as dynamic adaptation of running workflow

instances. Recently, Windows Workflow Foundation

(in short WF) was released by Microsoft as their

solution for workflow-driven application development.

Although WF contains features that allow dynamic

workflow adaptation, the framework lacks an

instrumentation framework to make such adaptations

more manageable. Therefore, we built an

instrumentation framework that provides more

flexibility for applying workflow adaptation batches to

workflow instances, both at creation time and during

an instance’s lifecycle. In this paper we present this

workflow instrumentation framework and

performance implications caused by dynamic

workflow adaptation are detailed.

1. Introduction

In our day-to-day life we‟re faced with the concept

of workflow, for instance in decision making

processes. Naturally, such processes are also reflected

in business processes. Order processing, coordination

of B2B interaction and document lifecycle

management are just a few examples where workflow

is an attractive alternative to classic programming

paradigms. The main reasons to prefer the workflow

paradigm over pure procedural and/or object-oriented

development are:

Business process visualization: flowcharts are a

popular tool to visualize business processes;

workflow brings those representations alive. This

helps to close the gap between business people and

the software they‟re using since business logic is

no longer imprisoned in code.

Transparency: workflows allow for human

inspection, not only during development but even

more importantly during execution, by means of

tracking.

Development model: building workflows consists

of putting together basic blocks (activities) from a

toolbox, much like the creation of UIs using IDEs.

Runtime services free developers from the burden

of putting together their own systems for

persistence, tracking, communication, etc. With

workflow, the focus can be moved to the business

case itself.

Macroscopically, the concept of workflow can be

divided into two categories. First, there‟s human

workflow where machine-to-human interaction plays a

central role. A typical example is the logistics sector,

representing product delivery in workflow. The

second category consists of so-called machine

workflows, primarily used in B2B scenarios and inter-

service communication. On the technological side, we

can draw the distinction between sequential

workflows and state machine workflows. The

difference between them can be characterized by the

transitions between the activities. In the former

category, activities are executed sequentially, whilst

state machines have an event-driven nature causing

transitions to happen between different states. Many

frameworks that implement the idea of workflow

exist, including Microsoft‟s WF in the .NET

Framework 3.0. The architecture of WF is depicted in

Figure 1.

Figure 1. The architecture of WF [2]

A detailed overview of WF and its architecture can

be found in [1]. The WF framework allows developers

to create intra-application workflows, by hosting the

runtime engine in a host process. Windows

applications, console applications, Windows Services,

web applications and web services can all act as hosts

and façades for a workflow-enabled application. A

workflow itself is composed of activities which are the

atoms in a workflow definition. Examples of activities

include communication with other systems,

transactional scopes, if-else branches, various kinds of

loops, etc. Much like controls in UI development, one

can bring together a set of activities in a custom

activity that encapsulates a typical unit of work.

Finally, there‟s the workflow runtime that‟s

responsible for the scheduling and execution of

workflow instances, together with various runtime

services. One of the most important runtime services

is persistence, required to dehydrate workflow

instances becoming idle. This reflects the typical long-

Appendix A – ICSEA’07 paper | 110

running nature of workflow. Our research focuses on

building an instrumentation tool for WF, allowing

activities to be injected flexibly into an existing

workflow at the instance level.

This paper is structured as follows. In section 2,

we‟ll cover the concept of dynamic adaptation in more

detail. The constructed instrumentation framework is

then presented in section 3 and a few of its sample

uses in section 4. To justify this instrumentation

technique, several performance tests were conducted,

as explained in section 5.

2. Dynamic adaptation

2.1. Static versus dynamic: workflow

challenges

In lots of cases, workflow instances are long-lived:

imagine workflows with human interaction to approve

orders, or systems that have to wait for external

service responses. Often, this conflicts with ever

changing business policies, requiring the possibility to

adapt workflow instances that are in flight. Another

application of dynamic workflow adaptation is the

insertion of additional activities into a workflow. By

adding logic for logging, authorization, time

measurement, state inspection, etc dynamically, one

can keep the workflow‟s definition pure and therefore

more readable.

2.2. WF’s approach to dynamic adaptation

To make dynamic adaptation possible, WF has a

feature called “dynamic updates”. Using this

technique it‟s relatively easy to adapt workflow

instances either from the inside or the outside. We‟ll

refer to these two approaches by internal modification

and external modification respectively. A code

fragment illustrating a dynamic update is shown in

Figure 2. Internal modifications are executed from

inside a running workflow instance. An advantage is

having access to all internal state, while the main

drawback is the need to consider internal

modifications upfront as part of the workflow

definition design process. External modifications are

performed at the host layer where one or more

instances requiring an update are selected. Advantages

are the availability of external conditions the

workflow is operating in and the workflow definition

not needing any awareness of the possibility for a

change to happen. This flexibility comes at the cost of

performance because a workflow instance needs to be

suspended in order to apply an update. Also, there‟s

no prior knowledge about the state a workflow

instance is in at the time of the update.

WorkflowChanges changes = new WorkflowChanges(instance); // Add, remove, modify activities

changes.TransientWorkflow.Activities.Add(...);

foreach (ValidationError error in changes.Validate())

{ if (!error.IsWarning) { //Report error or fix it.}

}

instance.ApplyWorkflowChanges(changes);

Figure 2. Apply a dynamic update to an instance

3. An instrumentation framework for WF

3.1. Design goals

In order to make the instrumentation of workflow

instances in WF easier and more approachable, we

decided to build an instrumentation framework based

on WF‟s dynamic update feature. Our primary design

goal is to realize a framework as generic as possible,

allowing for all sorts of instrumentation. All

instrumentation tasks are driven by logic added on the

host layer where the workflow runtime engine is

hosted; no changes whatsoever are required on the

workflow definition level. This allows the

instrumentation framework to be plugged into existing

workflow applications with minimum code churn. It‟s

important to realize that instrumentations are

performed on the instance level, not on the definition

level. However, logic can be added to select a series of

workflow instances to apply the same instrumentation

to all of them. At a later stage, one can feed back

frequently performed instrumentations to the

workflow definition itself, especially when the

instrumentation was driven by a request to apply

functional workflow changes at runtime. This

feedback step involves the modification and

recompilation of the original workflow definition.

Another use of workflow instrumentation is to add

aspects such as logging to workflow instances. It‟s

highly uncommon to feed back such updates to the

workflow definition itself because one wants to keep

the definition aspect free. However, such aspects will

typically be applied to every newly created instance.

In order to make this possible, instrumentation tasks

can be batched up for automatic execution upon

creation of new workflow instances. The set of

instrumentation tasks is loaded dynamically and can

be changed at any time.

3.2. Implementation

Each instrumentation task definition consists of a

unique name, a workflow type binding, a parameter

evaluator, a list of injections and optionally a set of

services. We‟ll discuss all of these in this section. The

interface for instrumentation tasks is shown in Figure

3. The instrumentation tool keeps a reference to the

workflow runtime and intercepts all workflow

instance creation requests. When it‟s asked to spawn a

new instance of a given type with a set of parameters,

all instrumentation tasks bound to the requested

workflow type are retrieved. Next, the set of

parameters is passed to the parameter evaluator that

Appendix A – ICSEA’07 paper | 111

instructs the framework whether or not to instrument

that particular instance. If instrumentation is

requested, the instrumentation task is applied as

described below. The tool accepts requests to

instrument running instances too; each such request

consists of a workflow instance identifier together

with the name of the desired instrumentation task.

Filtering logic to select only a subset of workflow

instances has to be provided manually. When asked to

apply an instrumentation task to a workflow instance,

the instrumentation tool performs all injections

sequentially in one dynamic update so that all

injections either pass or fail. Every injection consists

of an activity tree path pointing to the place where the

injection has to happen, together with a before/after

indicator. For example, consider the example in Figure

4. In order to inject the discount activity in the right

branch, the path priceCheck/cheap/suspend1 (after)

will be used. This mechanism allows instrumentation

at every level of the tree and is required to deal with

composite activities. Furthermore, each injection

carries the activity that needs to be injected (the

subject) at the place indicated, together with

(optionally) a set of data bindings. Finally, every

instrumentation task has an optional set of services.

These allow injected activities to take advantage of

WF‟s Local Communication Services to pass data

from the workflow instance to the host layer, for

example to export logging messages. When the

instrumentation task is loaded in the instrumentation

tool, the required services are registered with the

runtime.

interface IInstrumentationTask {

// Parameter evaluator

bool ShouldProcess(Dictionary<string,object> args); // Instrumentation task name

string Name { get; }

// Type name of target workflow definition string Target { get; }

// Local Communication Services list

object[] Services { get; } // Set of desired injections

Injection[] Injections { get; } }

class Injection {

public string Path { get; set; }

public InjectionType Type { get; set; } public Activity Subject { get; set; }

public Dictionary<ActivityBind,DependencyProperty>

Bindings { get; set; } }

enum InjectionType { Before, After }

Figure 3. Definition of an instrumentation task

3.3. Design details

In order to apply workflow instrumentation

successfully, one should keep a few guidelines in

mind, as outlined below:

Instrumentations that participate in the dataflow of

the workflow can cause damage when applied

without prior validation. Invalid bindings, type

mismatches, etc might cause a workflow to fail or

worse, producing invalid results. Therefore, a

staging environment for instrumentations is highly

recommended.

Faults raised by injected activities should be

caught by appropriate fault handlers; dynamic

instrumentation of fault handlers should be

considered.

Dynamic loading of instrumentation tasks and

their associated activities should be done with care

since the CLR does not allow assembly unloading

inside an app domain. Therefore, one might extend

our framework to allow more intelligent resource

utilization, certainly when instrumentations are

applied on an ad hoc basis. More granular

unloading of (instrumented) workflow instances

can be accomplished by creating partitions for

(instrumented) workflow instances over multiple

workflow engines and application domains.

There‟s currently no support in our

instrumentation framework to remove activities

(injected or not) from workflow instances, which

might be useful to “bypass” activities. A

workaround is mentioned in 4.2.

When applying instrumentations on running

workflow instances, it‟s unclear at which point the

instance will be suspended prior to the

instrumentation taking place. Unless combined

with tracking services, there‟s no easy way to find

out about an instance‟s state to decide whether or

not applying certain instrumentations is valuable.

For example, without knowing the place where a

workflow instance is suspended, it doesn‟t make

sense to add logging somewhere since execution

might have crossed that point already. This

problem can be overcome using suspension points

as discussed in paragraph 4.2.

4. Sample uses of instrumentation

4.1. Authorization and access control

In order not to poison a workflow definition with

access control checks, distracting the human from the

key business process, authorization can be added

dynamically. Two approaches have been

implemented, the first of which adds “authorization

barriers” to a workflow instance upon creation. Such a

barrier is nothing more than an access denied fault

throwing activity. The instrumentation framework

uses the parameter evaluator to decide on the

injections that should be executed. For example, when

a junior sales member is starting a workflow instance,

a barrier could be injected in the if-else branch that

processes sales contracts over $ 10,000. This approach

is illustrated in Figure 4. An alternative way is to add

Appendix A – ICSEA’07 paper | 112

authorization checkpoints at various places in the

workflow, all characterized by a unique name

indicating their location in the activity tree. When

such a checkpoint is hit, an authorization service is

interrogated. If the outcome of this check is negative,

an access denied fault is thrown.

4.2. Dynamic adaptation injection

Using the instrumentation tool, internal adaptation

activities can be injected into a workflow instance.

This opens up for all of the power of internal

adaptations, i.e. the availability of internal state

information and the fact that a workflow doesn‟t need

to be suspended in order to apply an update.

Furthermore, we don‟t need to think of internal

adaptations during workflow design, since those can

be injected at any point in time. This form of

instrumentation has a Trojan horse characteristic,

injecting additional adaptation logic inside the

workflow instance itself, allowing for more flexibility.

However, internal modifications still suffer from the

lack of contextual host layer information. This can be

solved too, by exploiting the flexibility of dynamic

service registration to build a gateway between the

workflow instance and the host. Still, scenarios are

imaginable where external modifications are preferred

over injected internal modifications, for example when

much host layer state is required or when more tight

control over security is desirable. In order to allow this

to happen in a timely fashion, we can inject

suspension points in the workflow instance at places

where we might want to take action. When such a

suspension point is hit during execution, the runtime

will raise a WorkflowSuspended event that can be

used to take further action. Such an action might be

instrumentation, with the advantage of having exact

timing information available. As an example,

suspend1 is shown in Figure 4. This suspension point

could be used to change the discount percentage

dynamically or even to remove the discount activity

dynamically, as a workaround for the lack of activity

removal in our instrumentation framework. Notice that

the discount activity itself was also added

dynamically, exploiting the flexibility of dynamic

activity bindings in order to adapt the data in the

surrounding workflow, in this case the price.

4.3. Time measurement

Another instrumentation we created during our

research was time measurement, making it possible to

measure the time it takes to execute a section of a

workflow instance‟s activity tree, marked by a “start

timer” and “stop timer” pair of activities.

Instrumentation for time measurement of an order

processing system‟s approval step is depicted in

Figure 4. This particular case is interesting because of

a few things. First of all, both timer activity injections

have to be grouped and should either succeed or fail

together. This requirement can be enforced using

additional logic too, i.e. by performing a check for the

presence of a “start timer” activity when a “stop

timer” activity is added. However, it‟s more

complicated than just this. Situations can arise where

the execution flow doesn‟t reach the “stop timer”

activity at all, for instance because of faults. Another

problem occurs when the injections happen at a stage

where the “start timer” activity location already

belongs to the past; execution will reach the “stop

timer” activity eventually, without accurate timing

information being available. Implementing this sample

instrumentation also reveals a few more subtle issues.

When workflow instances become suspended,

persistence takes place. Therefore, non serializable

types as System.Diagnostics.Stopwatch can‟t be used.

Also, rehydration of a persisted workflow could

happen on another machine too, causing clock skews

to become relevant.

Instrumentation before (left) and after (right)

Figure 4. An example instrumentation

5. Performance evaluation of

instrumentation

5.1. Test methodology

To justify the use of workflow instrumentation, we

conducted a set of performance tests. More

specifically, we measured the costs imposed by the

dynamic update feature of WF in various scenarios. In

order to get a pure idea of the impact of a dynamic

update itself, we didn‟t hook up persistence and

tracking services to the runtime. This eliminates the

influences caused by database performance and

communication. In real workflow scenarios however,

services like persistence and tracking will play a

prominent role. For a complete overview of all factors

that influence workflow applications‟ performance,

see [3]. All tests were performed on a machine with a

Appendix A – ICSEA’07 paper | 113

2.16 GHz Intel Centrino Duo dual core processor and

2 GB of RAM, running Windows Vista. Tests were

hosted in plain vanilla console applications written in

C# and compiled in release mode with optimizations

turned on. To perform timings, the

System.Diagnostics.Stopwatch class was used and all

tests were executed without a debugger attached. For

our tests, we considered a workflow definition as

shown in Figure 4. One or more activities were

inserted in corresponding workflow instances, at

varying places to get a good image about the overall

impact with various injection locations. The injected

activity itself was an empty code activity in order to

eliminate any processing cost introduced by the

injected activity itself, also minimizing the possibility

for the workflow scheduler to yield execution in favor

of another instance. This way, we isolate the

instrumentation impact as much as possible.

5.2. Internal versus external modification

First, we investigated the relative cost of internal

versus external modification. Without persistence

taking place, this gives a good indication about the

impact introduced by suspending and resuming a

workflow instance when applying ad hoc updates to a

workflow instance from the outside. In our test, we

simulated different workloads and applied the same

dynamic update to a workflow both from the inside

using a code activity and from the outside. Each time,

the time required to perform one injection was

measured and best case, worst case and average case

figures were derived. The results of this test are shown

in Figure 5. The graphs show the time to apply the

changes as a function of the number of concurrent

workflow instances (referred to as N). One clearly

observes the much bigger cost associated with external

modification caused by the suspension-update-

resumption cycle, even when not accounting for

additional costs that would be caused by persistence

possibly taking place. Also, these figures show that

internal modification is hurt less by larger workloads

in the average case, while external modification is

much more impacted. These longer delays in the

external modification case can be explained by

workflow instance scheduling taking place in the

runtime, causing suspended workflows to yield

execution to other instances that are waiting to be

serviced. In the internal modification case, no

suspensions are required, so no direct scheduling

impact exists. However, one should keep in mind that

the suspension caused by external modification is only

relevant when applying updates to running workflow

instances. When applying updates at workflow

instance creation time, no suspension is required and

figures overlap roughly with the internal modification

case. Because of this, instrumentation at creation time

is much more attractive than applying ad hoc

injections at runtime.

a) Internal modification

b) External modification

Figure 5. Overhead of applying dynamic modifications to workflows

5.3. Impact of update batch sizes

Since instrumentation tasks consist of multiple

injections that are all performed atomically using one

dynamic update, we‟re interested in the relationship

between the update batch size and the time it takes to

apply the update. To conduct this test, we applied

different numbers of injections to a series of workflow

instances using external modification, mimicking the

situation that arises when using the instrumentation

framework. Under different workloads, we observed a

linear correspondence between the number of

activities added to the workflow instance (referred to

as n) and the time it takes to complete the dynamic

update. The result for a workload of 100 concurrent

workflow instances is shown in Figure 6. Based on

these figures, we decided to investigate the impact of

joining neighboring injections together using a

SequenceActivity and concluded that such a join can

impact the update performance positively. However,

designing an efficient detection algorithm to perform

these joins isn‟t trivial. Moreover, wrapping activities

in a composite activity adds another level to the

activity tree, which affects other tree traversal jobs but

also subsequent updates.

Figure 6. Impact of workfow update batch size

on update duration

Appendix A – ICSEA’07 paper | 114

5.4. The cost of suspension points

In order to get a better indication about the cost

introduced by the use of suspension points for exact

external update timing, we measured the time it takes

to suspend and resume a workflow instance without

taking any update actions in between. This simulates

the typical situation that arises when inserting

suspension points that are rarely used for subsequent

update actions. Recall that suspension points are just a

way to give the host layer a chance to apply updates at

a certain point during the workflow instance‟s

execution. The result of this test conducted for various

workloads (referred to as N) in shown in Figure 7. We

observe similar results to the external modification

case, depicted in Figure 5. From this, we can conclude

that the major contributing factor to external

modification duration is the suspend-resume cycle.

Naturally, it‟s best to avoid useless suspension points,

certainly when keeping possible persistence in mind.

However, identifying worthy suspension points is not

an exact science.

Figure 7. Workflow instance suspend-resume

cost for suspension points

5.5. Discussion

Based on these results, we conclude that ad hoc

workflow instance instrumentation based on external

modification has a significant performance impact,

certainly under heavy load conditions. Instrumentation

taking place at workflow instance creation time or

adaptation from the inside is much more attractive in

terms of performance. Injection of dynamic (internal)

adaptations as explained in section 4.2 should be taken

under consideration as a valuable performance booster

when little contextual information from the host layer

is required in the update logic itself. Overuse of

suspension points, though allowing a big deal of

flexibility, should be avoided if a workflow instance‟s

overall execution time matters and load on the

persistence database has to be reduced. These results

should be put in the perspective of typically long

running workflows. Unless we‟re faced with time-

critical systems that require a throughput as high as

possible, having a few seconds delay over the course

of an entire workflow‟s lifecycle shouldn‟t be the

biggest concern. However, in terms of resource

utilization and efficiency, the use of instrumentation

should still be considered carefully.

6. Conclusions

Shifting the realization of business processes from

pure procedural and object-oriented coding to

workflow-driven systems is certainly an attractive

idea. Nevertheless, this new paradigm poses software

engineers with new challenges such as the need for

dynamic adaptation of workflows without

recompilation ([4]), a need that arises from the long-

running characteristic of workflows and the ever

increasing pace of business process and policy

changes. To assist in effective and flexible application

of dynamic updates of various kinds, we created a

generic instrumentation framework capable of

applying instrumentations upon workflow instance

creation and during workflow instance execution. The

former scenario typically applies to weaving aspects

into workflows, while the latter one can assist in

production debugging and in adapting a running

workflow to reflect business process changes. The

samples discussed in this paper reflect the flexibility

of the proposed instrumentation framework.

Especially, the concept of suspension points allowing

external modifications to take place in a time-precise

manner opens up for a lot of dynamism and flexibility.

From a performance point of view, instrumentations

taking place at workflow instance creation time and

internal modifications are preferred over ad hoc

updates applied on running workflow instances. Also,

applying ad hoc updates is a risky business because of

the unknown workflow instance state upon

suspension. This limitation can be overcome by use of

suspension points, but these shouldn‟t be overused

7. Future work

One of the next goals is to make the

instrumentation framework easier and safer by means

of designer-based workflow adaptation support and

instrumentation correctness validation respectively.

Workflow designer rehosting in WF seems an

attractive candidate to realize the former goal but will

require closer investigation. Furthermore, our research

focuses on the creation of an activity library for

patient treatment management using workflow, in a

data-driven manner. Based on composition of generic

building blocks, workflow definitions are established

to drive various processes applied in medical practice.

Different blocks for data gathering, filtering,

calculations, etc will be designed to allow the creation

of data pipelines in WF. Our final goal is to combine

this generic data-driven workflow approach with the

power of dynamic updates and online instrumentation.

The need for dynamic adaptation in the health sector

was pointed out in [5]. This introduces new challenges

to validate type safety with respect to the dataflowing

through a workflow, under the circumstances of

Appendix A – ICSEA’07 paper | 115

activity injection that touches the data. Tools to assist

in this validation process will be required.

References

[1] D. Shukla and B. Schmidt, Essential Windows

Workflow Foundation. Addison-Wesley Pearson

Education, 2007.

[2] Windows SDK Documentation, MS Corp., Nov. 2006.

[3] M. Mezquita, “Performance Characteristics of WF,” on

the Microsoft Developer Network (MSDN), 2006.

[4] P. Buhler and J.M. Vidal. Towards Adaptive Workflow

Enactment Using Multiagent Systems. Information

Technology and Management Journal, 6(1):61--87,

2005.

[5] J. Dallien, W. MacCaull, A. Tien, “Dynamic Workflow

Verification for Health Care,” 14th Int. Symposium on

Formal Methods, August 2006.

Bibliography | 116

Bibliography

1. Microsoft Corporation. Windows SDK. Windows SDK. s.l. : Microsoft Corporation, 2006.

2. De Smet, Bart. WF - Working with Persistence Services. B# .NET Blog. [Online] October 14, 2006.

[Cited: April 12, 2007.]

http://community.bartdesmet.net/blogs/bart/archive/2006/10/14/4580.aspx.

3. De Smet, Bart. WF - Working with Tracking Services. B# .NET Blog. [Online] October 15, 2006.

[Cited: April 12, 2007.]

http://community.bartdesmet.net/blogs/bart/archive/2006/10/15/4582.aspx.

4. Richter, Jeffrey. CLR cia C# Second Edition. s.l. : Microsoft Press, 2006.

5. team, Microsoft's AzMan. Authorization Manager Team Blog. MSDN Blogs. [Online] Microsoft

Corporation. [Cited: May 28, 2007.] http://blogs.msdn.com/azman/.

6. De Smet, Bart. WF - Using the WorkflowMonitor in combination with Dynamic Updates. B# .NET

Blog. [Online] October 16, 2006. [Cited: April 27, 2007.]

http://community.bartdesmet.net/blogs/bart/archive/2006/10/16/4585.aspx.

7. Microsoft Corporation. Performance Characteristics of Windows Workflow Foundation. MSDN.

[Online] November 2006. [Cited: March 14, 2007.] http://msdn2.microsoft.com/en-

us/library/aa973808.aspx.

8. De Smet, Bart. Performance measurement in .NET 2.0 - The birth of Stopwatch. B# .NET Blog.

[Online] March 24, 2006. [Cited: May 17, 2007.]

http://community.bartdesmet.net/blogs/bart/archive/2006/03/24/3838.aspx.

9. Steurbaut, Kristof. Intelligent software agents for healthcare decision support - Case 1: Antibiotics

switch agent (switch IV-PO). Ghent : UGent - INTEC, 2006.

10. De Turck, F, et al. Design of a flexible platform for execution of medical decision support agents

in the Intensive Care Unit. Comput Biol Med. 37, 2007, 1.

11. Corp., Sybase. Adaptive Server Enterprise. Sybase. [Online] Sybase Corp. [Cited: May 21, 2007.]

http://www.sybase.com/products/databasemanagement/adaptiveserverenterprise.

12. Skeet, Jon. Why doesn't C# have checked exceptions? . MSDN Blogs. [Online] March 12, 2004.

[Cited: May 14, 2007.] http://blogs.msdn.com/csharpfaq/archive/2004/03/12/88421.aspx.

13. De Smet, Bart. WF - Introducing External Data Exchange, the CallExternalMethodActivity and

Local Communications Services. B# .NET Blog. [Online] October 17, 2006. [Cited: May 03, 2007.]

http://community.bartdesmet.net/blogs/bart/archive/2006/10/17/4584.aspx.

Bibliography | 117

14. Shukla, Dharma and Schmidt, Bob. Essential Windows Workflow Foundation. s.l. : Addison

Wesley, 2006.

15. Vihang Dalal. Windows Workflow Foundation: Everything About Re-Hosting the Workflow

Designer. MSDN. [Online] Microsoft, May 2006. [Cited: May 22, 2007.]

http://msdn2.microsoft.com/en-us/library/aa480213.aspx.

16. Corp., Microsoft. Northwind and pubs Sample Databases for SQL Server 2000. [Online] Microsoft

Corporation. [Cited: May 08, 2007.]

http://www.microsoft.com/downloads/details.aspx?familyid=06616212-0356-46a0-8da2-

eebc53a68034&displaylang=en.

17. Kleinerman, Christian. Multiple Active Result Sets (MARS) in SQL Server 2005. MSDN. [Online]

Microsoft Corp., June 2005. [Cited: May 24, 2007.] http://msdn2.microsoft.com/en-

us/library/ms345109.aspx.

18. Allen, Scott. Hosting Windows Workflow. OdeToCode.com. [Online] August 6, 2006. [Cited: May

24, 2007.] http://www.odetocode.com/Articles/457.aspx.

19. Microsoft Corporation. .NET Framework 3.0. .NET Framework 3.0. [Online] 2007. [Cited: March

26, 2007.] http://www.netfx3.com.