WCF and Interfaces

I went back to my Road Alert project and I wanted to add modifiers to the geolocations to account for the seasonality of the alter types.  For example, there are 36% more crashes in May and 27% more crashes in November.

 image

To that end, I added some more tables to my database – a table for each of the seasonal factors.  I kept the database in third normal form:

image

and the updated the Entity Model on my service layer:

:image

I then need to update my service interface to account for the new data.  I noticed that all of the tables all had the same format: PK (Int32), FKs(Int32), TableValueId(Int32), and ModifierValue(Float).  The only variation is the TableValue – in the Month table it is 1-12, in the DayOfWeek it is 0-6, DayOfMonth is 1-31 and Hour is 0-23)

Channeling my Inner Uncle Bob, I created an interface for these data structures like so:

  1. public interface IAlertTypeModifier
  2. {
  3.     Int32 AlertTypeModifierId { get; set; }
  4.     Int32 AlertTypeId { get; set; }
  5.     Int32 ModifierId { get; set; }
  6.     Int32 ValueId { get; set; }
  7.     double Modifier { get; set; }
  8. }

 

I then updated the service interface like this:

  1. [ServiceContract(Namespace = "http://schemas.tff.com/2013/07/RoadAlert.Services")]
  2. public interface IRoadAlert
  3. {
  4.     [OperationContract]
  5.     Location GetLocation(Int32 locationId);
  6.  
  7.     [OperationContract]
  8.     List<Location> GetLocations();
  9.  
  10.     [OperationContract]
  11.     List<Location> GetLocationsForMultipleAlertTypes(List<Int32> alertTypeIds);
  12.  
  13.     [OperationContract]
  14.     AlertType GetAlertType(Int32 alertTypeId);
  15.  
  16.     [OperationContract]
  17.     List<AlertType> GetAlertTypes();
  18.  
  19.     [OperationContract]
  20.     IAlertTypeModifier GetAlertTypeModifier(Int32 alertTypeId, Int32 modifierId, Int32 valueId);
  21.     
  22.     [OperationContract]
  23.     List<IAlertTypeModifier> GetAlertTypeModifiers();
  24.  
  25.     [OperationContract]
  26.     List<IAlertTypeModifier> GetAlertTypeModifiersForAnAlertType(Int32 alertTypeId);
  27.  
  28.     [OperationContract]
  29.     List<IAlertTypeModifier> GetAlertTypeModifiersForAModifierId(Int32 modifierId);
  30.  
  31.     [OperationContract]
  32.     List<IAlertTypeModifier> GetAlertTypeModifiersForAnAlertTypeAndModifierId(Int32 alertTypeId, Int32 modifierId);
  33.  
  34. }

 

I then created the supporting internal methods to these public methods and ran them though my unit tests and the public methods ran green.  For example:

  1. [TestMethod]
  2. public void GetModifiers_ReturnExpectedValue()
  3. {
  4.     RoadAlert roadAlert = new RoadAlert();
  5.     List<IAlertTypeModifier> modifiers = roadAlert.GetAlertTypeModifiers();
  6.  
  7.     Int32 notExpected = 0;
  8.     Int32 actual = modifiers.Count;
  9.  
  10.     Assert.AreNotEqual(notExpected, actual);
  11. }

image

I then deployed the service to my web hosting provider and updated the service interface on my other tests.  Whn I did that, I ran into trouble:

 

  1. [TestMethod]
  2. public void GetModifiers_ReturnExpectedValue()
  3. {
  4.     RoadAlertClient client = new RoadAlertClient();
  5.     List<IAlertTypeModifier> modifiers = client.GetAlertTypeModifiers();
  6.  
  7.     Int32 notExpected = 0;
  8.     Int32 actual = modifiers.Count;
  9.  
  10.     Assert.AreNotEqual(notExpected, actual);
  11. }

 

With the exception:

Cannot implicitly convert type ‘System.Collections.Generic.List

And When I Googled on Bing, I found out that you can’t return interfaces because you can’t serialize interfaces.  Crap!  Looks like I need to concrete classes if I want to use WCF.

So now I have a choice:

1) Return each concrete type so I add 8 methods to the service interface (GetMonth, GetMonths, etc…)  Put these together on the client side

2) Return a more generic concrete class (equiv to an abstract class) and parse the results on the client

3) Use REST

Since I am already down the SOAP path on this project, I do not want to pivot to REST right now.  In order to offload processing from the client, I decided to clutter up my Service interface with more methods.  So I changed my interface like this:

  1. [ServiceContract(Namespace = "http://schemas.tff.com/2013/07/RoadAlert.Services&quot;)]
  2. public interface IRoadAlert
  3. {
  4.     [OperationContract]
  5.     Location GetLocation(Int32 locationId);
  6.  
  7.     [OperationContract]
  8.     List<Location> GetLocations();
  9.  
  10.     [OperationContract]
  11.     List<Location> GetLocationsForMultipleAlertTypes(List<Int32> alertTypeIds);
  12.  
  13.     [OperationContract]
  14.     AlertType GetAlertType(Int32 alertTypeId);
  15.  
  16.     [OperationContract]
  17.     List<AlertType> GetAlertTypes();
  18.  
  19.     [OperationContract]
  20.     AlertTypeDayOfMonthModifier GetAlertTypeDayOfMonthModifier(Int32 AlertTypeDayOfMonthModifierId);
  21.  
  22.     [OperationContract]
  23.     List<AlertTypeDayOfMonthModifier> GetAlertTypeDayOfMonthModifiers();
  24.  
  25.     [OperationContract]
  26.     AlertTypeDayOfWeekModifier GetAlertTypeDayOfWeekModifier(Int32 AlertTypeDayOfWeekModifierId);
  27.  
  28.     [OperationContract]
  29.     List<AlertTypeDayOfWeekModifier> GetAlertTypeDayOfWeekModifiers();
  30.  
  31.     [OperationContract]
  32.     AlertTypeHourModifier GetAlertTypeHourModifier(Int32 AlertTypeHourModifierId);
  33.  
  34.     [OperationContract]
  35.     List<AlertTypeHourModifier> GetAlertTypeHourModifiers();
  36.  
  37.     [OperationContract]
  38.     AlertTypeMonthModifier GetAlertTypeMonthModifier(Int32 AlertTypeMonthModifierId);
  39.  
  40.     [OperationContract]
  41.     List<AlertTypeMonthModifier> GetAlertTypeMonthModifiers();
  42. }

 

And because I don’t need to abstract the valueId, I renamed it to be more reflective of its intent.  For example:

AlertTypeDayOfWeekModifier.ValueId is now AlertTypeDayOfWeekModifier.DayOfWeekId

Also, I changed the PK to more intention revealing also:

AlertTypeDayOfMonthModifier.AlertTypeModifierId is now AlertTypeDayOfMonthModifier.AlertTypeDayOfMonthModifierId

I then ditched the interface and put the common fields into a base class:

  1. public abstract class AlertTypeModifier
  2. {
  3.     public Int32 AlertTypeId { get; set; }
  4.     public Int32 ModifierId { get; set; }
  5.     public double Modifier { get; set; }
  6.  
  7. }

 

And had each of the implementations like this:

  1. public class AlertTypeDayOfMonthModifier : AlertTypeModifier
  2. {
  3.     public Int32 AlertTypeDayOfMonthModifierId { get; set; }
  4.     public Int32 DayOfMonthId { get; set; }
  5. }

 

Which severs well enough.  Then it was just a question of updating the service.cs file with the implementation (an incredibly boring 15 minutes) and adding some tests.  I got green locally and then green via the web service.

image

So I guess the lesson learned is that you really can’t apply the Open/Closed principle using WCF Web Services.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: