Declarative vs. Imperative WCF Services

Applies to: Windows Communication Foundation

Published: June 2011

Author: Alex Culp

Referenced Image

This topic contains the following sections.

  • Introduction
  • Comparing the Two Approaches
  • Imperative WCF Services
  • XAML and Workflows

Introduction

There are basically two approaches to programming. One is the imperative approach, and the other is the declarative approach. Imperative programming is a traditional way to write applications. Declarative programming focuses on what you want to accomplish, and lets the compiler, interpreter, or runtime solve the problem for you. An example of imperative programming is a traditional for loop in C#. An example of declarative programming is writing the same loop using LINQ.

The following example of imperative programming loops through the numbers 1 through 10, and finds the even numbers.

var numbersOneThroughTen = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var evenNumbers = new List<int>();
foreach (var number in numbersOneThroughTen)
{    if (number % 2 == 0)
    {
        evenNumbers.Add(number);
    }
}

The following code uses declarative programming to accomplish the same thing.

var numbersOneThroughTen = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var evenNumbers = numbersOneThroughTen.Select(number => number % 2 == 0);

Both examples yield the same result, and one is neither better nor worse than the other. The first example requires more code, but the code is testable, and the imperative approach gives you full control over the implementation details. In the second example, the code is arguably more readable; however, LINQ does not give you control over what happens behind the scenes. You must trust that LINQ will provide the requested result.

Similarly, there are two ways to implement Windows Communication Foundation (WCF) services. Most developers are familiar with the imperative programming model, where you create a service and use a series of instructions, and control logic to implement it. However, with the advent of the Extensible Application Markup Language (XAML), developers can now use a declarative approach. Again, either approach can produce the correct result, and one is not better than the other. This article introduces the declarative workflow services approach that uses Workflow (WF) 4.0, discusses the trade-offs between the declarative and imperative approaches, and presents some best practices for choosing an approach.

Comparing the Two Approaches

Traditionally, most developers have used the imperative approach to write web services. This meant using service and operation contracts to create the service, along with a service implementation.However, much custom development is neither purely declarative nor imperative. It contains a mixture of both, although it generally shows a preference for one or the other. Development of services certainly adheres to this pattern. For example, if a developer uses an imperative approach to implement a service, the logic may include if statements or other conditional logic, but the program may still include a SQL or a LINQ query, which are considered declarative.

The following table compares service development in terms of the imperative and declarative approaches.

Imperative Approach

Declarative Approach

Small services are easier to implement.

The control flow is easier for a business analyst to understand.

It is easier to create unit tests.

It is difficult to create unit tests.

It is a familiar model for many developers.

Many developers must understand a new technology.

There is a fine degree of control over service contracts.

There is little control over service contracts.

Changes require that you deploy new assemblies.

Changes may require only that you deploy a new XAML file.

Imperative WCF Services

In an imperative WCF service, the developer must write all of the logic to implement a particular operation. The following code is an example of how to submit an order.

public void SubmitOrder(Order order)
{
    if (ItemIsInStock(order))
    {
        BillCustomer(order);
        ShipOrder(order);
        UpdateInventory(order);
    }
}

The developer must explicitly write the control logic, and code all of the operations that are included in this example. This is the approach most developers know. However, you can accomplish the same thing with the declarative approach.

XAML and Workflows

If you examine the imperative code sample, it is clear that the model works well when you have a few steps. However, what happens to the SubmitOrder service when there are tens, or even hundreds of steps involved? The code quickly becomes exceedingly difficult to read and maintain. To further complicate matters, consider what happens when you introduce manual steps into the process. For example, as part of the process of submitting an order, someone must actually pull an item from a shelf and then update the status of the order. The imperative approach does not take these manual steps into account. Clearly, there must be a better way to solve this problem. This is where XAML and workflows come in.

The Problems that Workflows Solve

WF 4.0 allows you to declaratively define each step in a workflow. Typically, you begin to write a workflow with either a flowchart control or a sequence activity. The flowchart control is a new feature in WF 4.0. Use it to create a workflow that incorporates the concepts and operations that describe a business process. A workflow made with the flowchart control is much easier for a business analyst or stakeholder to understand. A sequence activity is an even simpler approach that groups together a series of steps that are performed in order.

Another problem that workflows solve is that of long-running transactions, which require considerable development effort with the imperative approach. With WF 4.0, the workflow runtime can serialize the entire workflow to a Microsoft® SQL Server® database, and leave it in that persisted state until an event on the workflow occurs. Examples of events include the expiration of a delay activity or an action taken by a user. If you had to write the equivalent code using the imperative approach, you would probably need to develop a task scheduler service or batch job that periodically wakes up, evaluates all of the workflows that are in the database, and moves them to their next states.

Things to Watch Out For

While workflows have enormous power, and provide many benefits, they also have limitations. The following sections discuss some issues to consider.

Limited Control over WSDL and No Support for REST

Workflows give you very little control over the Web Service Definition Language (WSDL) files that they generate. You can change the namespace, and the contract name, and you can make changes to the data contracts classes that you use, but you do not have the same flexibility that imperatively written services give you. If you want to use workflows but need a finer degree of control over WSDL generation, consider creating a wrapper service that uses a new service to expose the operations of the workflow. Creating a wrapper involves three steps:

  1. Create a WCF wrapper service (optionally, it can be a REST-style service)

  2. In the wrapper service, add a service reference to the workflow service, or generate a proxy class with the Svcutil.exe tool.

  3. Add mapping logic from the WCF wrapper service to the workflow service.

Although this approach works, there are several drawbacks.

  • Network overhead. The new layer between the services introduces a delay between hops from one service to another. Putting both services on the same machine reduces but does not eliminate this overhead.

  • Extra code. A wrapper service is additional code that must be maintained. This increases the overall cost of a project.

  • Potential errors. Because the service reference does not reference a common service interface, it is quite possible for a workflow to be updated but not the corresponding proxy classes generated by reference. To mitigate this problem, use the Svcutil.exe /reference switch to at least share the common data contracts.

Mixed Code / Control Logic

Workflows can be difficult to troubleshoot because they often use different languages for different sections of the program, and there is a mix of the declarative and imperative styles. For example, WF 4.0 only supports Visual Basic for expressions, but the related activities and dependencies can be written in any .NET language. Consistency in how you structure the application is important. Here are a few best practices:

  • As much as is possible, keep control flow in the workflow itself. This makes it easier to visualize logical errors.

  • Custom activities should perform discrete tasks, and should not cause side effects or affect control flow. A good example of a custom activity is one that sends email. A bad example is an activity that routes purchase orders over $5000 for additional approval. (Ideally, this should be in the workflow.)

Difficult to Unit Test

  • As was stated earlier, it is difficult to unit test a workflow because it is not really code, but XAML. A typical workflow also has external dependencies on components such as services, or a database. To work around this, you must first dynamically load the XAML, perhaps by creating an instance of the System.Xml.XmlDocument class, and then using the Load method. Next, replace activities that have external dependencies with mock implementations. In practice, this can be a great deal of work. For more information on how to test a workflow, and how to create mock implementations, see How to Unit Test a Workflow that Calls a WCF Service at https://blogs.msdn.com/b/endpoint/archive/2011/01/20/wf4-how-to-unit-test-a-workflow-that-calls-a-wcf-service.aspx

An alternative approach is to do the following:

  • Use custom activities for discrete tasks and create unit tests for them.

  • Use integration tests to test the workflow itself. Integration tests can be time consuming to prepare because not only must you set up and tear down the data, but you must also ensure that other dependencies such as services are running before you execute the tests. For efficiency, you might want to limit integration tests to "sunny day" scenarios, which are scenarios that catch any critical or high-severity defects.

Visual Basic / No C# Support

To use WF 4.0, A C# developer must be familiar with VisualBasic. Custom activities can be written in any .NET language that you want, but expressions in the workflow itself must be written in VB. The need to support multiple languages in an application will probably increase the cost to maintain the solution.

Other Workflow Challenges

Workflow XAML is very difficult to read outside of the designer and unfortunately, there is a good chance that you will need to spend some quality time looking at the raw XAML when you try to troubleshoot this familiar and dreaded error message that appears in the designer.

Referenced Image

Fortunately, the warnings in the integrated development environment (IDE) are fairly informative about the nature of the problem. The following XAML example is incorrect.

<p1:If DisplayName="If (Valid Order)" sap:VirtualizedContainerService.HintSize="579,580" bogusAttribute="bogus">

It yields the following error message.

Referenced Image

Previous article: Manual Testing / QA Testing

Continue on to the next article: Exposing a Declarative WCF Service