CQRS in Haskell: Command validation with Applicative Functors

I recently started a toy Haskell project implementing some concepts
related to Event Sourcing and CQRS. I find strong the abstractions and
type system from Haskell usually help in solidifing my understanding
of most concepts.

This post will start things off only tangentially related to CQRS and will
describe a small DSL I’ve put together for modelling validation of
commands.

We’ll end up being able to compose commands from smaller parts, while
still being able to run validations against them in a “transactional” way,
meaning the validation will run before any side-effects occur, and if there
is a failure, all side-effects will be prevented from happening. Essentially,
a unit of work type behavior that arises through correctness of construction.

This will be a win, as composability is always a critical feature of any
well designed software system. It will also lead us to a system that will
be more expressive than something like attributes/annotations in C# or Java,
while keeping our code simple such that it will interact well with our
other abstractions.

We will for now skip modelling things like using a read/write model
and use commands that are simple IO actions, such as

addUserCommand :: String -> IO ()
addUserCommand user =
  printf "Added user %sn" user

setPasswordCommand :: String -> String -> IO ()
setPasswordCommand user pwd =
  printf "Set password for user %s to '%s'n" user pwd

I’ll mostly be interested in the side-effects themselves, which are
exemplified as simple print statements.

Now for modelling validations themselves, there are two things I’d
like a validation type to satisfy.

  • There should be an “empty” validation, that never fails.
  • There should be a way to compose validations into one validation
    object, such that it encompasses it’s parts and collects errors from
    both.

Naturally, this suggests using a Monoid. We’ll just use a list in
this example, but I will abstract over monoids, as it will lead to
more flexible code, and will keep me honest about how the API is used.

Now there needs to be a way to combine commands with their validations,
such that we can compose commands while still ensuring that we can run
all validation logic before any side-effects occur.

Of course, Haskell’s standard set of abstractions contains the answer
we need in order to maximize the expressiveness for this API. We want
to combine effectful objects such that the resulting “control flow”
can be predicted ahead of running effects. The interface we need is
Applicative, for applicative functors, a generalization of the
Monad class.

Applicative functors as a generalization of monads, are thus also
somewhat less powerful. This loss of power is however exactly what is
need to achieve our goals.

We define a data type Attributed that contains an effectful object
(m) running side-by-side with a monoid (w).

data Attributed m w a = Attributed (m a) w

This datatype cannot be made into a monad in a meaningful way, but can
be made into an applicative in the obvious way.

instance (Monoid w, Applicative m) => Applicative (Attributed m w)
  where
    pure a = Attributed (pure a) mempty
    Attributed f v <*> Attributed a w =
      Attributed (f <*> a) (v <> w)

This code might be hard to understand if you’re not versed in Haskell,
but don’t worry if it looks like opaque, it will not affect the
resulting API, and is really pretty uninteresting. If you want to
understand it, I’d recommend reading up on monoids and applicative
functors.

Before we get to the meat of things we need some helper
functions. We’ll define an operator (#) to apply validations to an
effect.

infixr 0 #
vs # effect = Attributed effect (mconcat vs)

This will apply a list of validations (monoids) to an effect
(command). Using a list will also have the cute effect of making the
syntax look somewhat like attributes in C#, my paying-the-bills
language of choice.

We also define a simple helper function on lists

assert p err = if p then [] else [err]

and use it to write a function for validating the min-length of
strings.

minLength name n str =
  assert (length str >= n) $
    printf "%s %s: Min-length is %d" name str n

Note that printf is polymorphic, and above becomes a pure function
to strings, while in the commands it’s side-effecting and prints to
console. (Type system win).

We now can define some commands with validation.

addUserCommand :: String -> Command ()
addUserCommand user =
  [ minLength "Username" 5 user ]
  # printf "Added user %sn" user

setPasswordCommand :: String -> String -> Command ()
setPasswordCommand user pwd =
  [ minLength "Password" 7 pwd ]
  # printf "Set password for user %s to '%s'n" user pwd

Above I’ve defined the type Command as an attributed effect, where
our monoid is a [String], or list of strings representing error
messages.

type Command a = Attributed IO [String] a

Now, let’s say we want to define a new command that both adds a user
and sets the initial password. We can do this with applicative
composition.

newUserCommand user pwd =
  addUserCommand user *> setPasswordCommand user pwd

We use a run function to execute commands with their validations.

run :: Command a -> IO ()
run (Attributed command w) = case w of
  []     -> void command
  errors -> for_ errors $ e -> printf "error: %sn" e

Let’s try it out

*Main> run $ newUserCommand "foo" "password"
  error: Username foo: Min-length is 5

*Main> run $ newUserCommand "username" "foo"
  error: Password foo: Min-length is 7

*Main> run $ newUserCommand "username" "password"
  Added user username
  Set password for user username to 'password'

Cool. Even if the validation fails for the password, the create user
action is not executed. This could not be the case had we used monadic
composition in something like the similarly constructed writer monad.

To see the flexibility of this abstraction, we’ll do something stupid
like taking a list of usernames and adding all users that start with
the letter ‘s’.

addUsers :: [String] -> Command ()
addUsers users =
  for_ users $ userName ->
    case userName of
     's':_ -> addUser userName
     _     -> pure () -- do nothing

Let’s test it

*Main> run $ addUsers ["simon_marlow", "spj", "pni", "conal", "simon_thompson"]
  error: Username spj: Min-length is 5

*Main> run $ addUsers ["simon_marlow", "pni", "conal", "simon_thompson"]
  Added user simon_marlow
  Added user simon_thompson

Sweet. This example is contrived, but illustrates we can combine
commands using arbitrary “control-flow” such as “for-loops” and
“if-statemens” (or the haskell versions thereof), as long as we don’t have any dependencies on the side-effects themselves, which makes a lot of sense.

The occurence of invalid username “pni” does not fail the computation
as it is never inserted, and even if we use the for control function
we are still “transactional” over the side effects, so this acheives
what we want.

From here we can extend this abstraction with an explicit read model,
that will be available to validate against, while ensuring no invalid
actions are taking against the write model.

Running without validation

We can of course also execute commands without their validations, simply
by ignoring any attached validation. Since Haskell is lazy by default,
this will come at essentially no runtime cost either.

run_unsafe (Attributed m w) = m

Conclusion

It’s amazing what can be achieved with only a few lines of Haskell, when
we chose abstractions wisely. Check out the full source here.

I find the ability to design programs such as these a big win for
languages like Haskell, and for strong type checking and in particular
the separation of pure and effectful computation via the type system,
as well as design patterns such as monoids and applicatives. These go
a long way of ensuring our programs are correctly constructed, and
lead us towards simple yet expressive code. Perhaps now we can put to rest
ideas such as type systems being the root of all evil.

3 Comments

  1. Gerolf Seitz

    Great post, thanks.
    I noticed 2 things: the type of addUsers is missing the () for the Command type, and addUser should probably be addUserCommand.

    • Well spotted. I was worried it’d be hard to read the difference between addUserCommand/addUsersCommand so I just called it addUsers, but you’re of course correct we should really be consistent the convention.

  2. Thought-provoking post. Thank you Philip :)

    In the declaration of addUsers, there is a reference to addUser which should be addUserCommand.

    The Functor instance declaration for Attributed is missing from the article. Not necessarily

    The link “type systems being the root of all evil” appears not to be linked correctly. It should be linked to http://www.jayway.com/2010/04/14/static-typing-is-the-root-of-all-evil/.

    I have a very slight twist on your implementation posted at https://gist.github.com/AlainODea/273217c724f745184761.

Leave a Reply