WCF and Interfaces
June 18, 2013 Leave a comment
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.
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:
and the updated the Entity Model on my service layer:
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:
- public interface IAlertTypeModifier
- {
- Int32 AlertTypeModifierId { get; set; }
- Int32 AlertTypeId { get; set; }
- Int32 ModifierId { get; set; }
- Int32 ValueId { get; set; }
- double Modifier { get; set; }
- }
I then updated the service interface like this:
- [ServiceContract(Namespace = "http://schemas.tff.com/2013/07/RoadAlert.Services")]
- public interface IRoadAlert
- {
- [OperationContract]
- Location GetLocation(Int32 locationId);
- [OperationContract]
- List<Location> GetLocations();
- [OperationContract]
- List<Location> GetLocationsForMultipleAlertTypes(List<Int32> alertTypeIds);
- [OperationContract]
- AlertType GetAlertType(Int32 alertTypeId);
- [OperationContract]
- List<AlertType> GetAlertTypes();
- [OperationContract]
- IAlertTypeModifier GetAlertTypeModifier(Int32 alertTypeId, Int32 modifierId, Int32 valueId);
- [OperationContract]
- List<IAlertTypeModifier> GetAlertTypeModifiers();
- [OperationContract]
- List<IAlertTypeModifier> GetAlertTypeModifiersForAnAlertType(Int32 alertTypeId);
- [OperationContract]
- List<IAlertTypeModifier> GetAlertTypeModifiersForAModifierId(Int32 modifierId);
- [OperationContract]
- List<IAlertTypeModifier> GetAlertTypeModifiersForAnAlertTypeAndModifierId(Int32 alertTypeId, Int32 modifierId);
- }
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:
- [TestMethod]
- public void GetModifiers_ReturnExpectedValue()
- {
- RoadAlert roadAlert = new RoadAlert();
- List<IAlertTypeModifier> modifiers = roadAlert.GetAlertTypeModifiers();
- Int32 notExpected = 0;
- Int32 actual = modifiers.Count;
- Assert.AreNotEqual(notExpected, actual);
- }
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:
- [TestMethod]
- public void GetModifiers_ReturnExpectedValue()
- {
- RoadAlertClient client = new RoadAlertClient();
- List<IAlertTypeModifier> modifiers = client.GetAlertTypeModifiers();
- Int32 notExpected = 0;
- Int32 actual = modifiers.Count;
- Assert.AreNotEqual(notExpected, actual);
- }
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:
- [ServiceContract(Namespace = "http://schemas.tff.com/2013/07/RoadAlert.Services")]
- public interface IRoadAlert
- {
- [OperationContract]
- Location GetLocation(Int32 locationId);
- [OperationContract]
- List<Location> GetLocations();
- [OperationContract]
- List<Location> GetLocationsForMultipleAlertTypes(List<Int32> alertTypeIds);
- [OperationContract]
- AlertType GetAlertType(Int32 alertTypeId);
- [OperationContract]
- List<AlertType> GetAlertTypes();
- [OperationContract]
- AlertTypeDayOfMonthModifier GetAlertTypeDayOfMonthModifier(Int32 AlertTypeDayOfMonthModifierId);
- [OperationContract]
- List<AlertTypeDayOfMonthModifier> GetAlertTypeDayOfMonthModifiers();
- [OperationContract]
- AlertTypeDayOfWeekModifier GetAlertTypeDayOfWeekModifier(Int32 AlertTypeDayOfWeekModifierId);
- [OperationContract]
- List<AlertTypeDayOfWeekModifier> GetAlertTypeDayOfWeekModifiers();
- [OperationContract]
- AlertTypeHourModifier GetAlertTypeHourModifier(Int32 AlertTypeHourModifierId);
- [OperationContract]
- List<AlertTypeHourModifier> GetAlertTypeHourModifiers();
- [OperationContract]
- AlertTypeMonthModifier GetAlertTypeMonthModifier(Int32 AlertTypeMonthModifierId);
- [OperationContract]
- List<AlertTypeMonthModifier> GetAlertTypeMonthModifiers();
- }
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:
- public abstract class AlertTypeModifier
- {
- public Int32 AlertTypeId { get; set; }
- public Int32 ModifierId { get; set; }
- public double Modifier { get; set; }
- }
And had each of the implementations like this:
- public class AlertTypeDayOfMonthModifier : AlertTypeModifier
- {
- public Int32 AlertTypeDayOfMonthModifierId { get; set; }
- public Int32 DayOfMonthId { get; set; }
- }
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.
So I guess the lesson learned is that you really can’t apply the Open/Closed principle using WCF Web Services.