by Kurt Madsen

Introduction

I delivered the following document to a client’s technical team (architects and programmers) in Tampa and the overseas. It is a template and an example of how to solicit proposed solutions to challenging problems from technical staff within a limited timeframe.

Once proposed solutions to issues have been documented using this approach, they can be indexed and stored in a simple knowledge base of ideas (documents, database, index cards, whatever). This approach can be applied to many artifacts throughout the SDLC (e.g., analysis, design, code, etc.)

Tracking Architectural Defects – Almost all projects track software defects (and resolution) in the deployment phase of the software development lifecycle (e.g., QA and User acceptance testing). We should do the same thing to track architecture defects during the initial phases of the SDLC. Doing so will formalize architecture validation and give us a sense of progress as defects (hard problems) are resolved.

Consensus – All interested parties will have the opportunity to speak or forever hold their peace once a decision has been made. This is not the same as design by committee! We can agree on a problem statement, but disagree on approaches to solutions.

Clarity and well thought-out ideas -- Reducing an idea to a few pages or slides (handwritten OK) forces us to validate our ideas before presenting them to others. This improves communication and should reduce time spent on unstructured discussions in meetings. The objective is to concisely convey an idea, not spent a lot of time documenting it.

More brains – This bubble-up approach scales to include staff overseas: they can research a problem in more depth to validate (or refute) proposed solutions. They, in turn, will identify new problems and new solutions. Management can limit the number of iterations to balance value vs. time so we don’t rehash ideas indefinitely.

Separating problem analysis vs. decision-making – This makes efficient use of everyone’s time. If there is a dispute, management can resolve it quickly without being subject to unstructured, lengthy discussion on problem analysis (i.e., we do our homework before presenting them with options).

Design Decision Database – This approach serves to document important design decisions throughout the SDLC so that the project team does not revisit the same issues six months later.

DD.360 – Composite Pattern for Recursive Process Hierarchy

Problem

It would be useful to invoke processes, sub-processes, and their subordinate activities with a common interface while providing for strong type safety so that models can be validated to properly handle different types of activities.

Proposed Solution

The Composite design pattern [VLIS95] is a good pattern for PRODUCT_NAME’s processes, sub-processes, and their subordinate activities, which are defined recursively (collectively referred to as the process hierarchy.) This pattern strikes a balance between WfActivity type safety and interface simplicity (which is realized by WfEngine through WfProcess). Figure 1 illustrates the Composite pattern applied to PRODUCT_NAME.

Figure 1. Composite Design Pattern Applied To Recursive Process/Task Hierarchy

There are several approaches to instantiating process definitions and executing processes against them. This example assumes that the process definition is stored as a tree of objects (e.g., parsing the WfXML process definition into a DOM tree) Alternatively, the process definition could be implemented by loading DB tables into memory.

When the WfEngine loads the process definitions, (at startup() or reload() ), it builds an object hierarchy in memory that represents the definition for a particular process. This is illustrated in Figure 2. This collections-of-collections defines process and sub-processes as intermediate nodes with activities as terminal nodes.

Figure 3 shows the final process definition as it is executed by the Activity Activation Algorithm.

Figure 4 shows the thread model, which illustrates that the finite state machine of a sub-process at any level can have multiple exit points (i.e., terminal states) without violating the recursive nature of the Composite pattern.

Alternative Solutions

The most likely alternative would be to treat a process and a activity as different classes.

Consequences

GOOD -- The current sample code demonstrates the recursive nature of the Composite pattern. Because this pattern provides a unified interface to any node within the process hierarchy, it provides support for nesting of sub-processes at any depth.

GOOD – The Composite pattern is extensible because processes can easily evolve over time. In the code example, the second activity in process P2 could evolve over time from a single activity to a sub-process. In this way, customers can extend METATECH process templates to evolve with their organizational needs. Further, a composite task can be modeled an activity with several tasks or a sub-process whose activities each have only one task. Process execution would remain the same.

GOOD – An activity can be implemented as an intermediate node or a terminal node (i.e. leaf). This approach is forgiving in that with the

QUESTIONABLE –Microsoft Project supports the ability to have multiple entry and exit points to or from tasks within a summary task (i.e. sub-process). While one could argue that this is poor project planning (a.k.a. modeling), it might be tricky to implement this using the Composite pattern due to its recursive nature.

BAD – I’m sure someone will think of something. Fire away.

Code Example

The attached files provide a example that implements the three figures above.

Further Work

The following activities could be done overseas to validate or refute this approach:

Investigate other approaches to implementing process hierarchies (i.e., recursive sub-processes). What do other workflow products do? Is there current academic research in this area that we can use (e.g., graph theory, performance)?

Should clients (external to ExEngine) instantiate proxies that represent processes, or invoke the WfProcess interface, or should the ExEngine hide these details and only publish and subscribe to events?

How should global IDs be implemented in distributed computing environments? Should sub-processes have IDs that reflect their relative position in the process hierarchy (e.g., ID=3.5.7 is the 7th node in sub-process 5 of process 3).

The WfActivity class hierarchy must implement other things such as states. This work could be done overseas. At what point do we start using helper classes and libraries to avoid an overloaded WfActivity class that does everything?

If we choose to use relational tables cached in memory (which does not preclude the Composite pattern), how does this affect polymorphism and model integrity?

How will this approach help (or hinder) execution of the Activity Activation Algorithm?

References

VLIS95 – Vlisides, John et al (“Gang of Four”), “Design Patterns,” Addison Wesley, 1995.

Attachments

Files: WfActivity.java, WfAutomatedActivity.java, WfManualActivity.java, WfProc.java, WfEngine.java, and composite-pattern.mdl.

Source Code

I distributed the following working Java code with this document to the entire team during my first week on the job. It uses the composite pattern and can be arbitrarily extended. The code is very simple, but it highlighted an important design decision. Specifically, in our workflow management system’s object model: which class is the parent and which is the child, the PROCESS class or the ACTIVITY Class?

/**** File 1 – WfActivity.java ************************/

package Engine;

public abstract class WfActivity {

private static int nextId = 0;

protected int id;

protected WfActivity() {

super();

id = ++nextId;

}

public void initialize() {

}

public void run() {

}

public Integer getId() {

return new Integer(id);

}

// DEBUG

public void printId() {

System.out.println("WfActivity " + id + " has finished execution.");

}

}

/**** File 2 – WfProc.java ***************************/

package Engine;

import java.util.*;

public final class WfProc extends WfActivity {

private Map activityMap;

// Protected CTOR

protected WfProc() {

activityMap = new HashMap();

}

public void initialize() {

// build graph from PDT's WfXML

// forall nodes in children, node.initialize();

}

public void run() {

System.out.println("***************************");

System.out.println("Process number " + getId() + " started execution.");

Iterator itr = activityMap.keySet().iterator();

while ( itr.hasNext() ) {

Integer key = ( Integer ) itr.next();

WfActivity tmp = ( WfActivity ) activityMap.get( key );

if ( tmp != null ) {

tmp.run();

}

}

System.out.println("Process number " + getId() + " finished execution.");

System.out.println("***************************");

}

public void add(WfActivity a) {

activityMap.put(a.getId(), a);

}

public void remove() {

}

}

/**** File 3 – WfManualActivity.java *************************/

package Engine;

public final class WfManualActivity extends WfActivity {

private String[][] form;

public WfManualActivity() {

}

public void initialize() {

}

public void run() {

System.out.println("Manual activity number " + getId() + " just executed.");

}

}

/**** File 4 – WfAutomatedActivity.java **************/

package Engine;

public final class WfAutomatedActivity extends WfActivity {

public WfAutomatedActivity() {

}

public void initialize() {

}

public void run() {

System.out.println("Automated activity number " + getId() + " just executed.");

}

}

/**** File 5 – WfEngine.java ****************************/

package Engine;

public final class WfEngine {

public static void main(String[] args) {

WfActivity a1 = new WfManualActivity();

WfActivity a2 = new WfAutomatedActivity();

WfProc p3 = new WfProc();

p3.add(a1);

p3.add(a2);

WfActivity a4 = new WfAutomatedActivity();

WfActivity a5 = new WfManualActivity();

WfActivity a6 = new WfManualActivity();

WfActivity a7 = new WfAutomatedActivity();

WfProc p8 = new WfProc();

p8.add(p3);// note, p3 is a process

p8.add(a4);

p8.add(a5);

p8.add(a6);

p8.add(a7);

p8.initialize();

p8.run();

}

}