Yesterday, I took a look at a couple of options to fix a problem with a fluent API in an application: Fixing Problems with a (Specific) Fluent API. I got a couple of really good comments, so I'm going to explore things a little further.
Note: This probably won't make much sense unless you look at the previous article. To get an overview of the entire project, check here: Rewriting a Legacy Application.
Opinions of Other Developers
I elicited comments from other developers. As usual, this is a great chance to get other points of view and also learn about some tools or techniques that maybe I don't know about.
First, a comment from Mackenzie Zastrow:
This is actually the direction that I'm leaning. It's hard to argue about the utility (or lack of utility) in the custom types. I'm not totally attached the the fluent API; I put it in as more of an experiment. So, I wouldn't be sad to see it go.
But then I got a good comment from Cliff Chapman:
New to Me
I hadn't heard about Semantic Types. There is an article on CodePlex by Matt Perdeck that describes the concepts: Introducing Semantic Types in .NET. And he's also created a NuGet package with the base types so that you don't need to code it up yourself.
This is really interesting to me. I think it's probably too much for this particular application, but it will be good to explore the concepts a little more.
Let's take a quick run through the code, describe the general idea, and look at a simple implementation.
Original Custom Types
The custom types that we started with didn't have much functionality:
These types just hold a single string value. The advantage that we get from this is that we now have custom types that we can use for our extension methods. So instead of extending all strings, we can extend just these types.
But we also have a problem. There's nothing to prevent us from putting whatever string value we want in these objects. So even though we get a bit of safety in our extension methods, we have a lot of danger in the objects themselves.
If we create these objects with invalid strings, then our code would blow up further down the line.
The primary idea behind Semantic Types is that we can have a class that contains a "string" value, but we can constrain that string value to make sure it conforms to a particular format or value.
For example, we can have an EmailAddress object that will only accept values that pass a regex check for email address formatting. In addition, the Semantic Types classes deal with the issues of equality, comparison, and even checking valid values without creating a custom object.
It's an interesting article; I'd recommend a read. Here's the link again: Introducing Semantic Types in .NET.
For my project, I decided I didn't want to go all the way with using the Semantic Types base classes (but I'll definitely keep this in mind for future projects). Instead, I just added some simple validation to the existing types.
The code for this is in the FluentAPI-Updated branch on GitHub. The custom types are in the "SunriseSunsetOrg.cs" file in the "HouseControl.Sunset" project. Direct link to the file: SunriseSunsetOrg.cs.
Let's start with the UTCTimeString object. Here's the updated code:
We've just added some parameter checks to the constructor. If the value is "null", then we throw an ArgumentNullException. Then we try to parse the string as a DateTime object. If the parse fails, we throw an ArgumentException. If it passes that parse, then we assign the value to our property.
The ResponseContentString object has been updated in a similar way:
This has the same "null" check, and then it tries to deserialize the string as a JSON object. If this fails, we throw an ArgumentException. If it does not fail, then we assign the value to our property.
Note: This will work with *any* JSON object; it's not looking for specific fields in the object (which we might want to do). But we get the general idea of how we can add validation here.
The nice thing about these objects is that we can test the validation very easily. For this, there is a new "HouseControl.Sunset.Test" project. The "SunriseSunsetOrg.Test.cs" file contains our tests (direct link: SunriseSunsetOrg.Test.cs).
Here are the tests for the UTCTimeString class;
These are pretty straight-forward. The first test is the "success" state. We pass in a valid time string and check that it is reflected in the "Value" property of the object. (As a reminder, the "Value" property is a string, not a parsed DateTime.)
The second test checks a "null" parameter. And as we can see from the "ExpectedException" attribute, we expect that this will throw an ArgumentNullException.
And the third test checks an invalid string value. This expects to get an ArgumentException. When we set up "ExpectedException" like this, the test will *only* pass if that particular exception is thrown. If there is no exception (or a different exception is thrown), then the test will fail.
With these in place, we can see that all 3 of these pass:
Now, let's take a look at the tests for the ResponseContentString:
These are similar to the previous tests. They test a valid value, a "null" value, and an invalid value.
And we get similar results when we run the tests:
So we can see that our code is working as expected at this point.
[Update 1/29/2015: I just realized that the test names don't reflect what we're actually testing. This is left over from a previous iteration of the code which had an "IsValid" property that was checked by the tests (if you're curious, it's in this commit). I changed the code to throw an exception instead and updated the unit tests (but I forgot to update the names). Here are the better test names. These are in the FluentAPI-Updated branch:
The tests with valid parameters are now titled with "HasValue", and the tests with invalid parameters are now titled with "ThrowsException". This better reflects what's going on in the tests.]
So this is very simple validation for the custom types. I didn't go with Semantic Types for this example. I'll be experimenting with it in other projects by using the base types provided in the NuGet package.
What makes me most interested in it is that it seems similar to what F# has built in with Units of Measure. This allows you to make a type distinction between an integer that holds a temperature and an integer that holds a distance in miles.
I'm very interested in functional programming and cool features offered by various functional languages (and I really should be spending more time with it). Several of the concepts jump out at me, and this is one of them.
Semantic Types isn't exactly the same thing as Units of Measure, but it does give us the custom types that are based on more primitive types but are still distinct from one another.
(And I know that the similarities between these two concepts breaks down pretty quickly, but it's something that came to mind as I was looking at this.)
After all of this, I'm still leaning toward Option #2 (which is to not use custom types and remove the fluent API). For this project, I think it is the right choice since we still don't get much utility from the custom types. It will be interesting to explore Semantic Types in other scenarios.
I still haven't merged back to the "master" branch yet. I'll probably be doing that in the next few days. If you have an opinion one way or the other, be sure to leave a comment.
I value opinions of other developers. We all have different experiences in different environments. It's really great when we can leverage that experience without having to fumble through things on our own.