all Technical posts

F# Domain Model Validation with Active Patterns

In this post, we will look at how F#'s feature Active Patterns can help build a clear, declarative solution for the Validation of Domain Models. By using a Partial Pattern for each Business Rule, we can clearly see how the input is restricted to verify each rule.

Introduction

The reason I wrote this post was to learn more about F# Active Patterns, and how I can use this for certain, specific problems. They say this feature is a real “killer” feature of the F# environment, so I found it a good exercise to think about how I can use this in my daily practice.

Scott Wlaschin has an amazing blog post series where he writes about this topic. He shows how we regularly miss the true definition of the domain and how we can fix this with the simplicity of F#.

My blog post builds upon that idea and looks how we can validate our models in the same simplicity.

Domain Modeling

When thinking about the modeling of the domain, F# has a very nice way to express this. Throughout this post, I will be using F# for my modeling and for the validation of my model. Sometimes I will show your what the alternative would look like in an Object-Oriented Language like C#.

Book

Ok, let’s define our model. We want to define a “Book” in our domain. A book in our domain has several items which defines “a book”; but for the sake of this exercise we’ll keep it very short:

type Book =
{ ISBN13 : string
Author : string
Pages : int }
view raw Book.fs hosted with ❤ by GitHub

Just like Scott Wlaschin has asked the question, I’ll ask it again: “What’s wrong with this design?”.

Several things, as a Security Expert you could say that we’re could have a problem if someone enters negative pages, or special chars for the ISBN or the Author.
As a Domain Expert, you could say that this model doesn’t actually represent the domain.

PositiveInt

Let’s start with a simple one: we can’t have negative pages; so, let’s define a new type for this. Note that we have cleared it “private” so we can’t call this new type directly via its Value Constructor. Because we have made it private; we need another function that will create this type for us. When we enter a negative number, we can’t create a type. That’s sounds like an Option to me:

type PositiveInt = private PositiveInt of int
let posInt x = function
| x when x < 0 -> None
| x -> Some <| PositiveInt x
view raw PositiveInt.fs hosted with ❤ by GitHub

FYI: At this point, Scott’s talk stops because the talk is about the domain itself and not to specify how we can refactor the validation of the models.

Now we can start with the refactoring to Active Patterns. Because this is a simple type, I think you can’t see the immediate benefit of this approach; so, hang on. We use the Partial Pattern approach for these Business Rules because we can’t wrap all possible values in a single pattern.

The Partial Pattern approach needs a return type of unit option. We can use the Some branch to return the pattern itself and the None branch can be used to specify that the input doesn’t match this pattern.

let (|Negative|_|) x = if x < 0 then Some Negative else None
let posInt x = function
| Negative -> None
| x -> Some <| PositiveInt x

One can argue about the over-engineering of this approach; but personally, I find this a way more simplistic approach than the inlined Guard Clauses in the Match Expression.

Our book looks now like this:

type Book =
{ ISBN13 : string
Author : string
Pages : PositiveInt }

String50

Next up, is the author’s name. It reasonable to think that the length of the name will be no longer than 50 chars.

We can specify all these rules in our model the same way as we did with the pages:

type String50 = private String50 of string
let ifTrueThen succ = function
| true -> Some succ
| false -> None
let (|NullOrEmpty|_|) =
String.IsNullOrEmpty
>> ifTrueThen NullOrEmpty
let (|StringLength|_|) l s =
(String.length s) > l
|> ifTrueThen StringLength
let string50 = function
| NullOrEmpty
| StringLength 50 -> None
| s -> String50 s |> Some
view raw String50.fs hosted with ❤ by GitHub

Notice that we now have two branches that cover our type. By extracting the rules into Partial Patterns, we have made it clear, in our Constructor Function, that we need a string that isn’t “null” or empty and is a maximum of 50 characters long.

Now, how would we specify this in C#? Because we do not have an option type by default, only a less stronger Nullable<T> type; we normally use exceptions.

public class String50
{
public String50(string value)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException(nameof(value));
}
if (value.Length > 50)
{
throw new InvalidOperationException("String must be longer than 50 chars");
}
Value = value;
}
public string Value { get; }
}
view raw String50.cs hosted with ❤ by GitHub

Note that we can reuse the pre-conditions for the empty string and the length across our application in the F# version, while we must redefine them for every class in C# (except off course we extract this functionality in some “utility” classes.

Now, our book type looks like this:

type Book =
{ ISBN13 : string
Author : String50
Pages : PositiveInt }
view raw BookString50.fs hosted with ❤ by GitHub

ISBN13

The last type is the most interesting and the reason why I would use Active Patterns for my Domain Model Validation.

If we have some more complex type for example an ISBN13 number; how would we model that? First of all, let’s specify some requirements:

  • Number must have length of 13
  • Last number is checksum

The checksum is calculated by evaluating the following steps:

  1. Take the 12 first chars
  2. Multiply the even numbers in the sequence with 3
  3. Take the sum of all the results
  4. Modulo 10 the result
  5. Substract 10 if the outcome isn’t zero
  6. Final result must be the same as 13th number

I came up with this:

type ISBN13 = private ISBN13 of string
let ifTrueThen x = function
| true -> Some x
| false -> None
let (|NullOrEmpty|_|) =
String.IsNullOrEmpty
>> ifTrueThen NullOrEmpty
let (|StringLength|_|) f s =
f (String.length s)
|> ifTrueThen StringLength
let (|NotMatches|_|) pattern s =
Regex.IsMatch (pattern, s, RegexOptions.Compiled)
|> ifTrueThen NotMatches
let flip f x y = f y x
let (|CheckSum|_|) s =
let multiplyEvenWith3 i x = if i % 2 <> 0 then x * 3 else x
let substract10IfNotZero x = if x <> 0 then 10 - x else x
let numbers = [ for c in s -> c |> string |> int ]
numbers
|> List.take 12
|> List.mapi multiplyEvenWith3
|> List.sum
|> flip (%) 10
|> substract10IfNotZero
|> (<>) <| List.last numbers
|> ifTrueThen CheckSum
let isbn13 = function
| NullOrEmpty
| StringLength ((<>) 13)
| NotMatches "[0-9]{13}"
| CheckSum -> None
| s -> ISBN13 s |> Some
view raw ISBN13.fs hosted with ❤ by GitHub

What I like about this, is the declarativity of the checksum calculation and the fact that you can see immediately what rules we have in our ISBN validation.

Note that I changed the Active Pattern for the length of the string by passing in a function; this way I can reuse it for my String50 type and for this one AND can you see more clearly what exactly we’re trying to validate with the string’s length (greater than, equal to, …).

Now, I wanted to check this with C#. To achieve the same level of simplicity; we would extract each rule in it’s own method:

public class ISBN13
{
public ISBN13(string value)
{
ValidateStringNullOrEmpty(value);
ValidateStringLength(value, 13);
ValidateStringMatches(value, "[0-9]{9}");
ValidateISBNCheckSum(value);
}
private static void ValidateStringNullOrEmpty(string value)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException(nameof(value));
}
}
private static void ValidateStringLength(string value, int length)
{
if (value.Length != length)
{
throw new InvalidOperationException($"String must be longer than {length} chars");
}
}
private static void ValidateStringMatches(string value, string pattern)
{
if (Regex.IsMatch(value, pattern, RegexOptions.Compiled) == false)
{
throw new InvalidOperationException($"String doesn't match '{pattern}'");
}
}
private static void ValdiateISBNCheckSum(string value)
{
char[] allNumbers = value.ToCharArray();
var result = 0;
for (var i = 0; i < allNumbers.Length - 1; i++)
{
int number = int.Parse(allNumbers[i].ToString());
bool isEven = i % 2 != 0;
result += isEven ? number * 3 : number;
}
result = result % 10;
result = result != 0 ? 10 - result : result;
int checkSum = allNumbers[12];
if (checkSum != result)
{
throw new InvalidOperationException($"Checksum number '{checkSum}' doesn't match '{result}'");
}
}
}
view raw ISBN13.cs hosted with ❤ by GitHub

If we extract each rule in a method, I think we get that same simplicity. But we should send some arguments with the rules not just for reusability in other models but for readability as well.

Take for example the Regular Expression rule. It’s much simpler to just send the pattern with the rule than to come up with some name for the method (or Active Pattern) that would satisfy what you’re trying to verify.

Note that the C# version isn’t done yet and must be refactored since there’s a lot going on which can’t be comprehend as quickly as the F# version (but that’s just my opinion).

Before you say anything about LINQ, I explicitly used and imperative approach because otherwise we would use functional programming again and when I compare functional with imperative I always try to be functional and imperative in the extreme so I can quickly see what’s the actual difference is.

 Testing

Of course, to be complete let’s write some properties for our newly created types. I found not every type to be that obvious to write properties for so it might be a good exercise for you as well.

PositiveInt Properties

First, let us look at the positive integer type. This was the simplest type to model and is also the simplest type to test. I came up with these two properties for the two branches:

String50 Properties

The next type must have a length of 50 chars to be a valid type. Following properties came to mind:

ISBN13 Properties

Now, the last type is probably the most interesting. We must generate valid ISBN numbers to check if the checksum acts properly. I came up with a Test Oracle as another way to express the checksum so I’ll could filter with this expression to generate valid ISBN13 numbers:

I love the way FsCheck allows me to write such properties with such little effort. Now I have a way to generate random, valid ISBN13 numbers. Notice that I didn’t check the other Active Pattern branch, perhaps this is a good exercise for you? All other cases should result in None.

Small side note: the assertion is now valid (accessible) because I wrote the types and properties in the same file. When this isn’t the case, we could test for any type (with wildcards) wrapped inside a Some case, instead of actually creating an ISBN13 or any other type. That way, we could change the values for that type without changing our test. For the sake of this exercise, I thought it was clearer to assert the type this way.

Love to hear your opinion!

Conclusion

In this post, we looked at how F#’s feature Active Patterns can help build a clear, declarative solution for the Validation of Domain Models. By using a Partial Pattern for each Business Rule, we can clearly see how the input is restricted to verify each rule.

In an object-oriented approach, you would normally create a class to wrap the domain model and specify the business rules inside the constructor while in functional programming, this can be done by privatizing the Value Constructor and create a new Constructor Function which uses Active Patterns to specify each business rule.

Thanks for reading!

Subscribe to our RSS feed

Hi there,
how can we help?

Got a project in mind?

Connect with us

Let's talk

Let's talk

Thanks, we'll be in touch soon!

Call us

Thanks, we've sent the link to your inbox

Invalid email address

Submit

Your download should start shortly!

Stay in Touch - Subscribe to Our Newsletter

Keep up to date with industry trends, events and the latest customer stories

Invalid email address

Submit

Great you’re on the list!