Chapter 23. Services

WHAT'S IN THIS CHAPTER?

  • Defining service contracts

  • Hosting F# services

  • Implementing contracts

  • Consuming services

It is amazing to see that, even at the time of publication in 2010, most applications currently in production tend to interface with other systems through mechanisms that create extraneous coupling. It is still common to see mechanisms such as file polling (system A polls an FTP server until system B drops a file in the right place) and shared databases. The former is wasteful, and the latter presents all sorts of pain as systems grow and evolve. Shared databases are particularly bad because they present what is often an "unprotected door" through which data can enter without any sort of validation or business rules applied. They also effectively become a giant API, with every table, every column, and every row adding to the surface area of what must be tested when changes are made.

OVERVIEW

To avoid this fate and to provide a better alternative for interoperation, Service Oriented Architecture (SOA) was invented. Services are developed that provide interfaces between platforms so that we can open the functionality of a system to other systems, while preserving the integrity of those systems. Services can take the form of web services, but they do not always need be served up via the web, an idea that has deep support in the Microsoft Windows Communication Foundation. The form of the service is less important than what it does, and making sure it is written in the right way to be relevant (meet a business need), reusable, scalable, and secure.

An important aspect of many services is that, all things being equal, stateless services are preferred over stateful ones. As has been emphasized many times in this book, F# is a great fit for applications that do not require state. In the world of services, there is almost never a need for state. Even in the case of stateful services, state is usually persisted in either an application database, or some sort of state server so that if the next requests comes in from a different server, it can pick up the context and continue work. This makes F# programs particularly well oriented toward service-oriented applications, as was pointed out in Chapter 21, where a comparison is made between services that serve HTML over HTTP, and services that serve other stuff (be it XML, JSON, or something else) on some other protocol over application service boundaries.

This chapter focuses on implementation of services in F#. Particular attention is paid toward using F# to define service contracts in WCF, implementing contracts, and fulfilling those contracts through an F #-based domain model. It then goes into how to configure and host services. Finally, it presents an example of F#-based service consumption.

AN F#-BASED WEATHER SERVICE

Although having a basic site that provides a forecast may be of some utility, it would not be inconceivable that there would be other uses of weather data in other systems. Say, for example, there is a need for a service that alerts farmers in Florida about the need to protect their citrus crop. Such a system may want to call out to a weather service that averages the expected temperature over a short-term period, so that the crops can be protected with reasonable lead time.

The Service Contract

Just like it is considered good practice to use tests to make sure development "begins with the end in mind" (apologies to Stephen Covey), it also makes sense to do so when writing services. In other words, it is helpful to ask the question, before writing implementation code, what is it that this service should do?

To that end, start with an assumption that the service should provide the user with information about a weather forecast, including such items as an average expected temperature, recommended attire, and weather to expect. If the general shape of what is required is known, the next step is to start by writing a data contract to define the shape of the result:

namespace ProFSharp.Services
open System
open System.ServiceModel
open System.Runtime.Serialization

[<DataContract>]
type Forecast() =
    let mutable _city : string = String.Empty
    let mutable _averageExpectedTemperature : double = 0.0
    let mutable _recommendedAtture : string = String.Empty
    let mutable _weather : string = String.Empty
    [<DataMember>]
    member public f.City
      with get() = _city and set(v) = _city <- v
[<DataMember>]
    member public f.AverageExpectedTemperature
      with
        get() = _averageExpectedTemperature and
        set(v) = _averageExpectedTemperature <- v
    [<DataMember>]
    member public f.RecommendedAttire
      with get() = _recommendedAtture and set(v) = _recommendedAtture <- v
    [<DataMember>]
    member public f.Weather
      with get() = _weather and set(v) = _weather <- v
                                                             
The Service Contract

Note the presence of a couple things here that are not normally present in idiomatic F# code. First, in the world of Microsoft WCF Services, things that are not primitive types (that is, strings, ints, doubles, and the like) need to be described in a DataContract. The type gets marked as a [<DataContract>], and each member that is to be passed over the wire is marked with the [<DataMember>] attribute.

It is also worth noting that these data contracts take the form of mutable types. The chief reason this is needed is for purposes of serialization, because it is expected that the client using the service will need to use serialization to repopulate the object when it is received. WCF, as of this writing, does not hydrate objects through means F# would consider typical (that is, the way you might populate a record), and therefore, to allow for the client to set the properties, the data contract has to define mutable properties.

A data contract can also be defined as an interface, which would allow us to build a nonmutable version on the F# side for the concrete implementation of the data contract. Although it would be slightly more idiomatic for F#, the added code in such a case for an object that acts as a simple DTO is typically not worth the trouble.

Of course, a DataContract alone isn't terribly useful. Also needed is a means to retrieve an object or objects that will match the DataContract. These are defined using ServiceContract and OperationContract:

[<ServiceContract>]
type IWeatherService =
  [<OperationContract>]
  abstract member GetForecastFor: place:string -> Forecast

Generally, a weather service will implement something that turns a string into a Forecast that matches the DataContract definition. On the surface, OperationContract is pretty straightforward. However, it is important to note that, as a consequence of the way WCF works, any parameters in an OperationContract must be named, or else exceptions will be raised when the service is hosted.

When a ServiceContract is in place, the next step is to think about how to fulfill the conditions of the ServiceContract. A good place to start is to use any domain models that might have been developed previously.

LEVERAGING THE DOMAIN MODEL

One of the nice things about domain models is that they can easily be reused in different contexts. The forecast domain model was previously used in the context of an ASP.NET MVC website in Chapter 21. The domain model was expressed as follows:

namespace FSharpBook.Models
type SkyType =
  | Sunny
  | Overcast
  | PartlyCloudy
  | Snow
  | Rain
  | Hurricane
  | Zombies

type Forecast = { Location:string; AverageTemperature:double; Skies:SkyType }

type Attire =
  | Bikini
  | Normal
  | LightJacket
  | Coat
  | Raincoat
  | Parka
  | BodyArmor

type Forecast with
  member forecast.ToAttire() =
    match forecast.AverageTemperature,forecast.Skies with
      | temp,sky when temp > 75.0 && sky = Sunny || sky = PartlyCloudy -> Bikini
      | temp,sky when temp > 65.0 && sky = Sunny || sky = PartlyCloudy -> Normal
      | temp,sky
        when temp > 45.0 && sky = Sunny || sky = PartlyCloudy -> LightJacket
      | _,Rain -> Raincoat
      | temp when temp < 20.0 -> Parka
      | _,Hurricane -> Coat
      | _,Snow -> Parka
      | _,Zombies -> BodyArmor
      | _ -> Coat //when in doubt, wear a coat...
                                                               
LEVERAGING THE DOMAIN MODEL

The preceding model constitutes the recommendation engine that the service will provide an interface to. In a sense, all that is being done is replacing the user interface developed in the context of an ASP.NET MVC website with a system interface that will provide the same information.

It is worth noting that the ability to reuse a model from the previous example in the MVC context is a big reason why staying true to the principle of separation of concerns is so important. Had things like HttpRequest objects slipped into the model, or worse, tied the model to specific types of presentation logic, it would have required significant rework to reuse the model. Moreover, if someone wanted to update the model, the updates would have to be done in two places. Very bad indeed!

WRITING THE SERVICE CONTROLLER

Just as one developer is a controller for MVC applications so that requests in http can be converted to the appropriate calls into the domain model, when developing services, controllers are used that can take external requests and route them appropriately. Although it is tempting to reuse the same MVC controller, chances are, it likely brings in more complexity than we need, because the needs in MVC to map a complex set of parameters from an http request into routes is not present. As such, a much less complex controller model for a service controller shall be used.

Rendering Weather

There are some needs on the service controller that are going to be similar to things that were needed on the ASP.NET MVC controller. Specifically, a means to convert from algebraic data types on the domain into the appropriate string in the service contract will be needed. The following methods are carried over from the view helper project in Chapter 21 to help with that task:

module WeatherRendering
open FSharpBook.Models

let ToWeatherString sky =
 match sky with
   | Sunny -> "Sunny"
   | Overcast -> "Overcast"
   | PartlyCloudy -> "Partly Cloudy"
   | Snow -> "Snow"
   | Rain -> "Rain"
   | Hurricane -> "Hurricane"
   | Zombies -> "Oh NO! ZOMBIES!!!"

let ToAttireString attire =
  match attire with
    | Bikini -> "Bikini"
    | Normal -> "Normal"
    | LightJacket -> "Light Jacket"
    | Coat -> "Coat"
    | Raincoat -> "Raincoat"
    | Parka -> "Parka"
    | BodyArmor -> "Body Armor"
                                              
Rendering Weather

The preceding code converts the weather types into strings for ease of consumption on the client. Of course, it is conceivable that future versions of the service could specify a preferred culture for results to be rendered in. Were that to happen, it would simply mean that these methods would take a parameter, and likely, look up the appropriate string in a resource file.

Helping the Service Controller

ASP.NET MVC comes stocked with objects like ModelBinder that help the programmer take a model and, so long as various conventions are followed, renders that model in a view. In the world of service controllers, there is no such concept. As a result, service controllers tend to use libraries, or otherwise contain routines, that convert model concepts into terms that the service contract can understand. In this case, a Forecast from the model is needed to convert into the type Forecast that was defined in the data contract:

namespace ProFSharp.Services
open FSharpBook.Models
open WeatherRendering

type ForecastServiceRenderer() =
  static member Render (forecast:Forecast) =
    let renderedForecast = new ProFSharp.Services.Forecast()
    renderedForecast.AverageExpectedTemperature <- forecast.AverageTemperature
    renderedForecast.City <- forecast.Location
    renderedForecast.RecommendedAttire <- forecast.ToAttire()
      |> WeatherRendering.ToAttireString
    renderedForecast.Weather <- forecast.Skies |> WeatherRendering.ToWeatherString
    renderedForecast
                                                            
Helping the Service Controller

The goal here is to convert an FSharpBook.Models.Forecast into a ProFSharp.Services.Forecast. Thankfully, doing so is not that hard, merely a matter of providing a function that creates a ProFSharp.Services.Forecast and maps the properties from the FSharpBook.Models.Forecast. This is a fairly common type of thing to do when converting from the F# world of records to the world of objects in the broader CLR and beyond.

Service Controller Implementation

Thankfully, in the context of a WCF service that does not have to worry about rendering to such complex creatures as humans, the implementation of a service controller is far simpler:

namespace ProFSharp.Services
open FSharpBook.Repositories

type ForecastServiceController() =
  member forecastController.For(locationQuery:string) =
    locationQuery
      |> YahooForecastRepository.GetForecastByLocation
      |> ForecastServiceRenderer.Render
                                                             
Service Controller Implementation

There are two routines that are particularly interesting. We are reusing our YahooForecastRepository .GetForecastByLocation (see Chapter 21) to provide a means to retrieve the model. The next step is to pass said model to ForecastServiceRenderer.Render so that they can pass back something in the form of an Object that conforms to the DataContract back at the service host.

Service Implementation

The last piece, a controller is in place, is to write something that will implement the IWeatherService:

namespace ProFSharp.Services
open System.ServiceModel
[<ServiceBehavior(ConfigurationName="Weather")>]
type YahooWeatherService() =
  interface IWeatherService with
    member s.GetForecastFor place =
      (new ForecastServiceController()).For(place)
                                                       
Service Implementation

The service implementation usually defers to a controller that will provide most of the details. The purpose here is to map what the controller does with specific operations from the service contract. It is hard not to notice the lack of code here — the service implementation itself is not unlike a view in the world of ASP.NET MVC — it is something ideally composed of as little code as possible. Most of the action for dealing with a domain should (and is) deferred to the controller. With the implementation in place, it is time to get into the implementation of the service host itself.

Implementing the Service Host

Services have come a long way since 2001, when most people in the world of .NET thought the word Service was synonymous with Web Service, and that SOA was a matter of sprinkling a WebMethod attribute in various places around a codebase. WCF is an important technology for moving away from having to bring the baggage of IIS and all its administrative overhead to host a service. In fact, the code that is written to host a service is amazingly simple:

module ServiceHost
open System.ServiceModel
open ProFSharp.Services

let startServicing() =
  let host = new ServiceHost( typeof<YahooWeatherService>, [||] )
  host.Open()
  printf "Press the 'any' key to stop hosting the service"
  System.Console.ReadKey() |> ignore

startServicing()
                                                                
Implementing the Service Host

This example has been provided because it works in the context of a console application. We could just as easily run this in the context of a Windows Service (aka a Daemon running in the background, not to be confused with a WCF Service), and not interact with an end user. The really important lines of code are not the ones that print to the console and ask you to press the 'any' key, but the following lines:

let host = new ServiceHost( typeof<YahooWeatherService>, Array.empty )
  host.Open()

The ServiceHost class knows how to read a configuration file and use that to start servicing requests according to said configuration. It is only at this point that all those attributes that were included in prior code written in this chapter start to get interpreted. Of course, the .Open() method will fail if there is not a configuration file present that tells the ServiceHost what to do:

<?xml version="1.0" encoding="utf-8" ?>
  <configuration>
    <system.serviceModel>
      <behaviors>
        <serviceBehaviors>
          <behavior name="serviceBehavior">
            <serviceMetadata httpGetEnabled="false" />
          </behavior>
        </serviceBehaviors>
      </behaviors>
      <services>
        <service behaviorConfiguration="serviceBehavior" name="Weather">
        <clear />
        <endpoint
          address="WeatherService"
          binding="basicHttpBinding"
          name="basicHttp"
          contract="ProFSharp.Services.IWeatherService" />
        <endpoint binding="mexHttpBinding" name="mex"
contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8000/WeatherService" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>
                                                                    
Implementing the Service Host

The preceding code, typically in something like app.config, specifies how a service host will do its work. Much of this is boilerplate, but of particular importance are the attributes in <endpoint> and <host>. The <endpoint> maps a URL to a contract (in this case, WeatherService to ProFSharp.Services.IWeatherService). The <host> provides information to the ServiceHost about what port and URL will be used to access the service.

Of course, there are many ways to configure a service, and the goal here isn't to go through each one. The book Wrox Professional WCF 4: Windows Communication Foundation with .NET 4 is a great resource for further understanding of the details of WCF Service Host configuration.

Now, there are some provisions we need to account for before this stuff will actually work. Namely, most systems are not configured to simply let any identity start serving requests at any random port. One thing that needs to be done is to set up permissions that allow the app — or the Windows Service, if that route is chosen — to host requests, which in this case, are http requests. The following command is typical for allowing a given identity to service http requests:

netsh http add urlacl url=http://+:8000/WeatherService user=YourUserId

When permissions allow for the ServiceHost to serve up http requests, the service host can be started. To make sure the service is responsive, open a browser and point it to http://localhost:8000/WeatherService to make sure a valid response is returned from the http request.

CONSUMING SERVICES

Consuming services, in the world of WCF, is typically a matter running svcutil.exe against a URL, which will generate a C# file that can be compiled into something usable for consuming a service.

Generating a Service Stub

If the URL http://localhost:8000/WeatherService returns a valid response, it can pass that as a parameter to svcutil.exe (from your .NET Framework SDK), which will generate a file with the following code:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:2.0.50727.4927
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace ProFSharp.Services
{
    using System.Runtime.Serialization;

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization",
 "3.0.0.0")]
    [System.Runtime.Serialization.DataContractAttribute(Name="Forecast",
Namespace="http://schemas.datacontract.org/2004/07/ProFSharp.Services")]
    public partial class Forecast : object,
System.Runtime.Serialization.IExtensibleDataObject
    {
        private System.Runtime.Serialization.ExtensionDataObject
extensionDataField;
        private double AverageExpectedTemperatureField;
        private string CityField;
private string RecommendedAttireField;
        private string WeatherField;

        public System.Runtime.Serialization.ExtensionDataObject ExtensionData
        {
            get
            {
                return this.extensionDataField;
            }
            set
            {
                this.extensionDataField = value;
            }
        }

        [System.Runtime.Serialization.DataMemberAttribute()]
        public double AverageExpectedTemperature
        {
            get
            {
                return this.AverageExpectedTemperatureField;
            }
            set
            {
                this.AverageExpectedTemperatureField = value;
            }
        }

        [System.Runtime.Serialization.DataMemberAttribute()]
        public string City
        {
            get
            {
                return this.CityField;
            }
            set
            {
                this.CityField = value;
            }
        }

        [System.Runtime.Serialization.DataMemberAttribute()]
        public string RecommendedAttire
        {
            get
            {
                return this.RecommendedAttireField;
            }
            set
            {
                this.RecommendedAttireField = value;
            }
        }
[System.Runtime.Serialization.DataMemberAttribute()]
        public string Weather
        {
            get
            {
                return this.WeatherField;
            }
            set
            {
                this.WeatherField = value;
            }
        }
    }
}


[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="IWeatherService")]
public interface IWeatherService
{

    [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/
IWeatherService/GetForecastFor", ReplyAction="http://tempuri.org/IWeatherService/
GetForecastForResponse")]
    ProFSharp.Services.Forecast GetForecastFor(string place);
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public interface IWeatherServiceChannel : IWeatherService,
System.ServiceModel.IClientChannel
{
}

[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public partial class WeatherServiceClient :
System.ServiceModel.ClientBase<IWeatherService>, IWeatherService
{

    public WeatherServiceClient()
    {
    }

    public WeatherServiceClient(string endpointConfigurationName) :
            base(endpointConfigurationName)
    {
    }

    public WeatherServiceClient(string endpointConfigurationName, string
remoteAddress) :
            base(endpointConfigurationName, remoteAddress)
    {
    }
public WeatherServiceClient(string endpointConfigurationName,
System.ServiceModel.EndpointAddress remoteAddress) :
            base(endpointConfigurationName, remoteAddress)
    {
    }

    public WeatherServiceClient(System.ServiceModel.Channels.Binding binding,
System.ServiceModel.EndpointAddress remoteAddress) :
            base(binding, remoteAddress)
    {
    }

    public ProFSharp.Services.Forecast GetForecastFor(string place)
    {
        return base.Channel.GetForecastFor(place);
    }
}
                                                          
Generating a Service Stub

Yes, it is a lot of generated code; when a closer look is taken, it is simply a lot of boilerplate that hides the ugly implementation details of putting together the appropriate request to the service that was just implemented. It can be ignored for the most part, other than to use it, it needs to be put into a .NET C# Windows DLL project so that it can be easily used from F# code that needs to consume this functionality.

The generated code defines a WeatherServiceClient that is capable of reading a configuration — telling it what address to make requests to — and handling the response so that the result matches the form specified in the DataContract.

Writing the Service Consumer

Assuming the previous example has been put in a project we can reference from an F# project, calling the service is not too difficult. However, to use this, there is the matter of configuring the client with a mapping so it can know where to make the call to:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="basicHttp" closeTimeout="00:01:00" openTimeout="00:01:00"
            receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false"
            bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
            maxBufferSize="65536" maxBufferPoolSize="524288"
maxReceivedMessageSize="65536"
            messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
            useDefaultWebProxy="true">
          <readerQuotas maxDepth="32" maxStringContentLength="8192"
maxArrayLength="16384"
              maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <security mode="None">
            <transport clientCredentialType="None" proxyCredentialType="None"
                realm="">
              <extendedProtectionPolicy policyEnforcement="Never" />
            </transport>
            <message clientCredentialType="UserName" algorithmSuite="Default" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:8000/WeatherService/WeatherService"
          binding="basicHttpBinding" bindingConfiguration="basicHttp"
          contract="IWeatherService" name="basicHttp" />
    </client>
  </system.serviceModel>
</configuration>
                                                               
Writing the Service Consumer

The preceding example provides a means for the code that will create a WeatherServiceClient to know what address to make calls to, as well as various parameters around things like timeouts and the like. The <endpoint> configuration is of particular note and should match the <endpoint> that was specified on the host configuration. Most good WCF books will provide adequate detail on how to use all the provided settings. Thankfully, the default settings are good enough for us to see how F# code will work when using a WeatherServiceClient:

open ProFSharp.Services

let getWeatherFor place =
    use myWeatherService = new WeatherServiceClient()
    let theWeather = place |> myWeatherService.GetForecastFor

    printf "The weather in %s should average %g degrees with %s, wear %s!"
      theWeather.City
      theWeather.AverageExpectedTemperature
      theWeather.Weather
      theWeather.RecommendedAttire
    printf "

Press any key to continue"
    System.Console.ReadKey() |> ignore

getWeatherFor "Romeoville, IL"
                                                        
Writing the Service Consumer

The client itself is an IDisposable resource, so it is important to make sure to use the use binding when retrieving it. Outside of that, the mechanics of using the service are mere idiomatic F# code.

SUMMARY

Services, among other things, are a way that programmers can provide outside systems a window into a domain model. They are a key to making interoperability happen in a more predictable manner. Without reliable services, experience demonstrates interoperability will happen — through the database, which for anyone who has maintained a legacy "integration database" knows, is far more error prone than it is through services. If you doubt that, ask yourself what happens when you start to have programs that read data that is frequently modified in hard-to-predict ways by other programs, and what the testing load on programs like that is like.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset