Domain-Driven Design (DDD) With F# - Validation
In this post I dig deeper into validation of a Domain-Driven Design with F# and the functional paradigm. The implementation builds upon the design introduced earlier. The central theme is that of explicitness - the possibility of a failure is made explicit with type signatures. In turn, this calls for explicit handling of failure conditions paving the way for a design-by-contract style of programming and equational reasoning. Under the hood, the implementation draws on basic principles of category theory which provide for uniformity and composability. As a result, from the perspective of DDD, the confluence of declarative design, supple design, invariants and assertions is tremendously enhanced. The functional implementation of validation is to a great extent based on the wonderful work of Mauricio Scheffer.
Pitfalls of Exceptions
Validation can be a tough topic. In modern object-oriented languages such as C# and Java, a notable reason for the difficulty is the impedance mismatch between exceptions, which are used to handle various types of error conditions, and methods which return normally. The aftermath is a nonuniform interface coupled with reduced autogenesis. In addition to a labyrinthine implementation, usually requiring explicit support from the hosting runtime, exceptions seem to almost invariably lead to bad programming practices. Contrary to their ambition, exceptions compel programmers to defer and ultimately avoid explicit handling of errors. This is because exceptions are highly optimized for the non-degenerate case and despite guidelines and best practices, their fundamental flaws cannot be escaped.
The following is a typical example used to showcase exception deficiencies.
The method interface is not uniform because the result can either be a integer value, which can be captured by assigning the result to a variable, or an exception which can only be captured by a catch block. Furthermore, static verification extends only to the non-exceptional case. In other words, the compiler will issue a warning if one tries to assign the result of this method to a string, but it won’t issue a warning if the result is assigned to an integer variable. This is problematic because the integer type is a proper subset of the method’s effective range - which is a union of the set of integers and an error case.
Unfortunately, the flaws of exceptions extend beyond interface irregularities to friction in designing exception hierarchies as evidenced by the likes of ApplicationException in .NET. Moreover, it isn’t immediately clear whether a given exception should ever be caught. In .NET, it is just as easy to catch an ArgumentException as it is an OutOfMemoryException though it never makes sense to catch the latter since there is not way to handle it. If all of this wasn’t enough, catching exceptions also incurs a performance penalty.
For more about the pitfalls of exceptions take a look at Exceptions are Bad.
An Explicit Alternative
The functional programming paradigm addresses the afore-mentioned scenario with explicit typing. Since the range of the Divide function is extended by the possibility of error, the range type is explicitly amplified. One such amplified type in F# is the option type use of which results in the following.
In this example, an erroneous result is explicitly encoded as an empty option. Therefore, the return type of the divide function is Option<int> - an amplified integer. Note that in this case, the interface of the Divide method is uniform because it is captured entirely in the return type. Pattern matching with the match clause allows the compiler to ensure that both result sub-types are handled explicitly. It follows that, unlike in the C# example, it unnatural to write functional F# code which defers handling of exceptional cases. One drawback of this technique however, is that it can become cumbersome to compose functions which return amplified types. We shall address this composition challenge in the following sections.
For a formal look at the origins of functional approaches to addressing exceptions and beyond take a look at Tackling the awkward squad: monadic input/output, concurrency, exceptions, and foreign-language calls in Haskell.
Functional Validation in Domain-Driven Design
Let us return to the original inventory domain. In the original implementation, validation was performed in the function which executed an inventory item command:
In this example, a failed assertion will raise an exception which will bubble to an outer layer. While it certainly attains the desired result of preventing execution of the behavior, we will refine this implementation with explicit types.
The first order of business is finding an amplified type to encode both successful and erroneous results. While the Option type, used in the integer division example above, captures both cases properly it doesn’t provide any insight into why the result was erroneous. Fortunately, F# provides the Choice type. Much like Option, Choice is a union type, the difference being that it also allows association of an arbitrary value with the second case. We shall use a two-case Choice type to encode success with the first case and an error expressed as a list of string messages with the second case. Thus, the result type of the exec function will be Choice<’TEvent, string list> instead of just ‘TEvent. A basic implementation follows.
In this example, like the successful result, the erroneous result is returned explicitly. While this implementation avoids many of the pitfalls of exceptions, the syntax could certainly use some work. To address the syntax, we shall put the compositional facilities of F# to work.
A Functional Validation Framework
The validation framework presented here is based largely on the work of Mauricio Scheffer in Validating with applicative functors in F#. This work has been refined and incorporated into fsharpx drawing on powerful, category theory based composition mechanisms contained therein.
Initially, we define a primitive validator builder function.
This is a function which when given a predicate and an error value creates a single parameter function which passes on the parameter if the predicate returns true otherwise returning the error value. This function shall be used to compose more complex validators.
One of the things we would like to do with these validating functions is write code which applies multiple validations on a parameter. If one of the validators fails, we would like to capture the error and continue collecting errors from subsequent validators finally returning a composite error result. If all validators succeed we pass on the parameter. Ultimately, the validation code should compose with code which executes behavior in the non-erroneous case.
Applicative functors will be used to attain the desired degree of composability. A functor is a commonly used structure in Haskell and functional programming in general. It is sort of like a monad but weaker. For the C# programmer, a functor can be thought of as a set of extension method associated with an amplified type. For example, the IEnumerable<T> type, which amplifies T, together with the Select extension method can be regarded as an instance of the functor type class. Intuitively, a functor provides a way to execute a function which operates upon the constituent type instead of the amplified type.
An applicative functor extends the functor type class with additional functions called pure and apply. Pure takes a non-amplified value and creates an instance of the amplified type corresponding to the functor. In the C# IEnumerable case this would entail a function which when given a value returns an IEnumerable containing that value - yield return value;. Apply for the Choice amplified type is defined as follows.
The apply function takes a function wrapped in a Choice type and arbitrary value also wrapped in a choice type. It applies the function, if available, otherwise passing on the contained errors, possibly composing with errors from the arbitrary value. In effect, it handles each of the four cases that a set of two Choice values can be in. In turn, we use these functions to compose the following functions.
The functions lift2, <?>, and |?> in this example form a sort of interface between instances of the Choice type and non-amplified values. The other two functions compose Choice values together passing on the type of either the left or the right value. We can use these functions to refine the syntax in the inventory item example as follows.
In this example from the InventoryItem module, the validName assertion, with the <* operator, composes two validators from the Validator module which ensure that the inventory item name is neither null or empty. In the exec function, the assertion is composed with the returned event using the <?> operator. This operator takes a Choice value on the left and a non-amplified value on the right and composes them into a single Choice value the first case of which is a value of the type of the second operand. The order of the operands can be reversed with the |?> operator which can be read as if and only if. Note the return type of the exec function is Choice<Event, string list>.
Refactoring Outer Layers
In the original example, the Aggregate module defined an aggregate as follows.
In order to incorporate the validation work herein, we change the signature of exec to address erroneous results. The handler must also be changed to handle errors explicitly. The following snippet from the Aggregate module provides an example.
In this example, only a successful executed command results in a commit to the event store. Otherwise, errors are propagated to the caller. The caller can in turn raise an exception or do anything else it may fancy.
In this post we discussed the pitfalls of traditional validation techniques involving exceptions. Next, we implemented a validation mechanism which avoids exceptions and which as a result is in better alignment with functional programming. To this end, we were able to draw on basic concepts in category theory which studies composition among mathematical structures. Throughout our approach, the themes of uniformity, explicitness and declarative design prevail. The resulting code remains succinct, draws on static verification and provides better composition facilities. In particular, it provides for equational reasoning which will be a topic of future posts. Additionally, the explicit implementation is simpler both in terms of readability, intuition and the requirements upon the runtime. Finally, the rich compositional facilities of F# allowed a solution that does not short-circuit like exceptions do, allowing clients to obtain all detected errors immediately.
The source for this post is on GitHub.