Previous Page
Next Page

Chapter 1. Deconstructing WF

Introductory programming texts often begin with a "Hello, World" program that prints a simple message to the standard output device. Here's that program in C#:

using System;
            class Program
            {
            static void Main()
            {
            Console.WriteLine("hello, world");
            }
            }
            

Hello, World is a popular starting point because it avoids a number of hard problems that most real-world programs need to address. Practitioners know that it doesn't take much work to expand even Hello, World to induce thorny problems rather quickly. For example, consider a simple variationlet's call it "Open, Sesame"that requires the user to enter a key before the traditional greeting is printed:

using System;
            class Program
            {
            static void Main()
            {
            // Print the key
            string key = DateTime.Now.Millisecond.ToString();
            Console.WriteLine("here is your key: " + key);
            string s = Console.ReadLine();
            // Print the greeting if the key is provided
            if (key.Equals(s))
            Console.WriteLine("hello, world");
            }
            }
            

The Open, Sesame console program is unremarkable in all aspects but one: Because it is dependent upon the user entering a line of text at the console, it takes an arbitrarily long period of time to complete its execution. You can compile and run the program, let it sit there for a few weeks, and then enter the key that causes it to print the expected greeting. This is by design.

Open, Sesame is an example of a reactive program. A reactive program is responsive to, or indeed dependent upon, stimulus from an external entity during the course of its execution. Sometimes that external entity is a person. Sometimes that external entity is another program. Either way, reactive programs spend most of their time stuck waiting for input and therefore present challenges that are not part of the equation when writing programs like Hello, World.

Most computer programs are reactive. Software is used everywhere you look within real-world processes of all kinds: collaborative editing of documents, management of customer orders, raw materials procurement, preparation of tax returns, provisioning, administration of product development efforts, online shopping, management of customer relationships, and coordination of plant and warehouse operations. The list goes on and on. Programs in the midst of these processes need to react to input provided both by people and other programs.

Some reactive programs are developed using frameworks such as ASP.NET and Java Servlets. Others are homegrown solutions that are built directly upon execution environments such as the Common Language Runtime (CLR) and Java Virtual Machine (JVM). Still others are written in languages like C or (unmanaged) C++.

However, if we look at how these reactive programs are written, we find that most are not at all like our simple Open, Sesame console program. Let's see how we can write a web service (a web application would be an equally illuminating choice) that does the same thing as our Open, Sesame console program.

Here is a translation of Open, Sesame into an ASP.NET web service:

using System;
            using System.Web.Services;
            [WebService]
            public class Service : WebService
            {
            [WebMethod(EnableSession = true)]
            public string PrintKey()
            {
            string key = DateTime.Now.Millisecond.ToString();
            Session["key"] = key;
            return "here is your key: " + key;
            }
            [WebMethod(EnableSession = true)]
            public string PrintGreeting(string s)
            {
            if (Session["key"].Equals(s))
            return "hello, world";
            return null;
            }
            }
            

This web service has two operations, and is not very complicated. However, we have entirely lost the control flow of Open, Sesamethe cold hard fact that PrintKey must occur before PrintGreeting, and that each of these steps must run exactly once in order for the program to be considered complete. To establish constraints on the ordering of its operations, we can modify the web service by adding the following code shown in boldface:

using System;
            using System.Web.Services;
            [WebService]
            public class Service : WebService
            {
            [WebMethod(EnableSession = true)]
            public string PrintKey()
            {
            bool alreadyDidStep1 = (Session["key"] != null);
            if (alreadyDidStep1)
            throw new InvalidOperationException();
            string key = DateTime.Now.Millisecond.ToString();
            Session["key"] = key;
            return "here is your key: " + key;
            }
            [WebMethod(EnableSession = true)]
            public string PrintGreeting(string s)
            {
            bool didNotDoStep1Yet = (Session["key"] == null);
            if (didNotDoStep1Yet)
            throw new InvalidOperationException();
            bool alreadyDidStep2 = (Session["programDone"] != null);
            if (alreadyDidStep2)
            throw new InvalidOperationException();
            Session["programDone"] = true;
            if (Session["key"].Equals(s))
            return "hello, world";
            return null;
            }
            }
            

We are now using a set of runtime checks to guarantee the correct control flow for our web service, but that logic is diffuse, implicit, and error prone. The straightforward sequencing of program statements in our Open, Sesame console program has dissolved into opaque logic that is spread throughout our web service operations. Imagine being asked to work out the control flow (not to mention the flow of data) if you are given only the web service source code. It can be done with a few seconds of code inspection for this simple example since there are only two operations, but think about doing it for a web service ten times this size, where the control flow involves branching and looping.

Why can't we use natural C# control flow constructs (we are programming in C#, after all) to specify the relationships between web service operations, and constrain what can happen when? The Open, Sesame console program has exactly the control flow and manipulation of local variables that we need for this simple program. Why is it impossible to write our web service, or a similar web applicationor just about any real-world programin this way?

Here are two good reasons:

  • In the Open, Sesame console program, Console.ReadLine blocks the caller's thread of execution. As written, the program might spend days stuck waiting for input. If many programs like this one were run simultaneously, and all were awaiting input, the system would grind to a halt. Dedicating threads in this way is not an option for real-world programs, especially those deployed in multiuser environments.

  • Real-world processes take a long timedays, weeks, or even months. It is wishful thinking to assume that the operating system process (or CLR application domain) in which the program begins execution will survive for the required duration.

For a console program that is used for pedagogical purposes, these issues rarely worry us. As a consequence, we are able to write Open, Sesamejust like Hello, Worldin a very natural way. Reading the program code tells us exactly what the program does. But precisely the opposite is true for our web service. Scalability and robustness are usually of paramount concern when developing web services and web applications.

The ASP.NET runtime is designed to efficiently manage multiple services and applications, and can robustly maintain state for individual sessions (with the right configuration, a session can fail over from one machine to another). However, the code does not lie. The scalability and robustness benefits come at a price. In the preceding web service, the key variable that is shared by a pair of operations is manipulated as a weakly typed name-value pair. Moreover, the logic that enforces appropriate ordering of operations is clumsily expressed by testing, at the beginning of each operation, whether variables such as key have been previously assigned.

It appears that scalability and robustness concerns are at odds with the desire to have a natural way of expressing the state and control flow of reactive programs. The fact that there are millions of web applications and web services in the world confirms that developers are willing to work within the limitations of today's programming models to get their jobs done. Even so, web paradigms are appropriate choices for only a subset of solutions requiring reactive programs. The challenge at hand is to find a better, and general-purpose, approach to developing reactive programs, web-based or otherwise. Such an approach must provide the following:

  1. A way to write reactive programs that does not sacrifice, and in fact enriches, the natural imperative style of control flow.

  2. A way to run reactive programs in a scalable and robust manner.


Previous Page
Next Page