Sunday, July 4, 2010

WCF Is An Implementation

Recently I’ve found myself in a number of conversations about best practices for consuming WCF services in client applications. It is my position that WCF is an implementation detail and the vast majority of uses either ignore or abandon one of the core principals in Object-Oriented Program for the sake of ease or because developers simply don’t give it a second thought. By reminding ourselves that we should be following solid OO principals first, our best practices for using WCF becomes clear.

Always Code Against Interfaces
I’m sure you’ve heard it before: we should always code against interfaces rather than concrete implementations. While this sounds great in theory, there are practical benefits to this principal as well.

Interfaces allow us to decouple implementations so calling code doesn’t have to be concerned with how an operation will be performed – only that it will satisfy that contract established by the interface. This decoupling is key to Dependency Injection where the actual implementation of the interface is determined externally at runtime. Breaking dependencies allows us to create mocks and stubs for testing purposes or change the concrete class at runtime.

In fact, interfaces provide the most robust way for us to adhere to the OO principal of polymorphism in the .NET world. Regardless of the actual type we are working with, we are able to treat the object as if it were the interface we need.

Making WCF Testable
One of the most common problems I’ve seen when consuming WCF services in client applications is testing client code without actually calling service methods. One reason for this is that most developers simply reference the proxy service class and call the desired service method(s). This creates tight coupling between client code and the service proxy. A better solution would be to have client code use an interface that represents the service then we could mock the service for testing purposes.
Unfortunately, the lack of a readily available interface to work with serves as a deterrent for most developers and they simply relegate this as a future refactoring task. This reduces the code coverage of your unit tests and degrades the overall confidence in the quality of the application.

I won’t entertain the debate between using the auto-generated proxy types created using the Visual Studio “Add Service Reference” wizard or sharing code between the server and client in this article. Suffice it to say, in either case, we still have to create an interface to serve our needs.

Back To The Point
We can now come full-circle to the point of this article: WCF is an implementation. Following the logic that an interface hides the actual implementation from calling code and our need to decouple the WCF service from the client by working with an interface instead of directly using the service proxy, we can ascertain that WCF really is an implementation detail and our client code really doesn’t and shouldn’t care that it is calling a WCF service. From the client’s perspective, it simply needs an object that implements the interface. The implementation could be a WCF service, mock object for testing, a traditional XML web service, a class from a shared library or some new technology or approach that has yet to be announced.

Give this a little more thought and you’ll see how this applies to many other OO principals and best practices. Decoupling the services means our client code can better adhere to the single-responsibility principal. If we want to change the service implementation from WCF to some other technology, we only have to make that change in one place, the service implementation, which keeps code DRY (Do not Repeat Yourself!). Service implementation is also encapsulated in one place making the code reusable and easier to maintain.

Putting It All Together
I find having actual code to demonstrate a concept helps drive the information home, so let’s use the example of a client application that is consuming a Customers service which provides simple methods to retrieve and save information about customers in our fictitious application. We will create a Silverlight application using the Model-View-ViewModel (MVVM) design pattern for the user interface.

Instead of designing the WCF service, let’s start with the use-case for our client application. When our application starts, we will display a list of existing customers in the user interface. Following the MVVM pattern, our list will be data bound to the CustomerListViewModel which uses our service to retrieve the list of customers from the server.

public class CustomerListViewModel : ViewModelBase
{
  private ICustomerService _service

  public CustomerListViewModel(ICustomerService service) : base()
  {
    _service = service;
  }
}


We use constructor injection to obtain a reference to an implementation of the ICustomerService interface used by the view model to perform service operations.

The view model uses lazy-loading to retrieve the list of customers. Because Silverlight forces us to make all calls asynchronously, we will return an empty collection initially but could just as easily return null since Silverlight data-binding will handle null values gracefully.

private ObservableCollection<CustomerViewModel> _items;

public ObservableCollection<CustomerViewModel> Items
{
  get
  {
    if (_items == null)
    {
      _items = new ObservableCollection<CustomerViewModel>

      BeginLoad();
    }

    return _items;
  }
  private set
  {
    _items = value;

    RaisePropertyChanged(“Items”);
  }
}


The BeginLoad method is responsible for starting the asynchronous call to the service. Because Silverlight only supports asynchronous communication with the server, our service pattern must provide an easy way to handle all method calls. This is accomplished by passing a callback delegate to the service method that will be called when the operation completes.

private void BeginLoad()
{
  _service.ListCustomers(OnLoadComplete);
}


When the operation completes, the OnLoadComplete method is called. After checking if the operation was cancelled on the server, we translate the results and update the view model property.

private void OnLoadComplete(IEnumerable<ICustomer> customers, bool cancelled)
{
  if (!cancelled)
  {
    var list = from customer in customers
               select new CustomerViewModel(customer);

    Items = new ObservableCollection<CustomerViewModel>(list);
  }
}


One thing to note is that we are not persisting references to the data objects returned from the service. This is consistent with a pure-MVVM approach were we only ever expose view models to our UI.

As you can see, nothing in the code above requires WCF or cares if that is the technology used to perform the service operation. However, we can easily mock the ICustomerService interface for unit testing purposes. And if something changes in the way the service is implemented, we won’t have to make a single change to our view model class – as long as the contract isn’t broken.

Let’s take a look at the interface for the service our view model uses:

public interface ICustomerService
{
  void DeleteCustomer(Guid id, DeleteCustomerCompleteCallback callback);

  void GetCustomer(Guid id, GetCustomerCompleteCallback callback);

  void ListCustomers(ListCustomersCompleteCallback callback);

  void SaveCustomer(ICustomer customer, SaveCustomerCompleteCallback callback);
}


Each method accepts whatever parameters are needed to perform the operation as well as a callback method that is executed when the operation completes. The delegates are strongly-typed to make coding easier.

public delegate void DeleteCustomerCompleteCallback(bool cancelled);

public delegate void GetCustomerCompleteCallback(ICustomer customer, bool cancelled);

public delegate void ListCustomersCompleteCallback(IEnumerable<ICustomer> customers, bool cancelled);

public delegate void SaveCustomerCompleteCallback(bool cancelled);


I’ve left all error handling out of the interface which means it is left to the service implementation to handle any exceptions (or faults) that occur during the service call.

Let’s take a look at how we will implement the ICustomerService interface to make use of a WCF service for the actual operation.

public class DefaultCustomerService : ICustomerService
{
  public void ListCustomers(ListCustomersCompleteCallback callback)
  {
    var wcfService = new WcfCustomerServiceClient();

    wcfService.ListCustomersCompleted += ListCustomersCompleted;
    wcfService.ListCustomersAsync(callback);
  }
}


Here is where we finally see our WCF proxy come into play. Our service implementation also abstracts away the async pattern used by the auto-generated proxy class by wiring a local handler to the completed event and passing the callback method to the service method. The callback delegate will be passed automatically to the completed handler in the event args.

private void ListCustomersCompleted(object sender, ListCustomersCompleteEventArgs e)
{
  if (e.Error != null)
    throw e.Error;

  var callback = e.UserState as ListCustomerCompletedCallback;

  if (callback != null)
  {
    var list = Mapper.Map<List<CustomerContract>, List<ICustomer>>(e.Results);

    callback.Invoke(list, e.Cancelled);
  }
}


In the handler for the completed event, we check for any error that may have occurred on the server then obtain the callback method from the event arguments. The callback is invoked with the results of our service method call.  (I'm using AutoMapper to simplify mapping the DTO returned by the service to the interface expected by our code.)

Conclusion
The point of this article was to illustrate that from the perspective of the client application, WCF is an implementation detail and a simple pattern can be used that makes our code more adherent to solid object-oriented principals. Hopefully the explanation and sample walk-through have demonstrated how this approach will also make our code more testable and flexible in the process. While it may seem like additional work and code, the benefits gained far outweigh the effort required and after putting it into place, I’m sure you’ll see for yourself how much easier it is to test and maintain your code as your application changes.

No comments:

Post a Comment