4
votes

As an exercise to wrap my brain around Haskell types and typeclasses, I'm attempting to implement a simple DDD/CQRS style solution. I'm modelling it directly after Lev Gorodinski's F# Simple CQRS implementation.

I've implemented a very simple Vehicle aggregate, which is more or less a direct port of Lev's InventoryItem aggregate

module Vehicle where

data State = State { isActive :: Bool } deriving (Show)
zero = State { isActive = True }

type Mileage = Int 

data Command = 
    Create String Mileage
    | UpdateMileage Mileage
    | Deactivate
    deriving (Show)    

data Event =
    Created String
    | MileageUpdated Mileage 
    | Deactivated
    deriving (Show)

-- Define transitions from a command to one or more events
execute :: State -> Command -> [Event] 
execute state (Create name mileage)   = [Created name, MileageUpdated mileage]
execute state (UpdateMileage mileage) = [MileageUpdated mileage]
execute state Deactivate              = [Deactivated]    

-- Apply an event against the current state to get the new state
apply :: State -> Event -> State
apply state (Created _)        = state
apply state (MileageUpdated _) = state
apply state Deactivated        = state { isActive = False }

The part that I'm trying to figure out is how to create a higher level abstraction for the domain aggregate, since all Aggregates would be made up of the same component parts. In Lev's example, he defined a type Aggregate<'TState, 'TCommand, 'TEvent> which allowed him to define a generic command handler which would work against any instance of a domain Aggregate. In Haskell this feels like something I should be using Typeclasses for. Of course, since I have no idea what I'm doing this may be a complete misconception of what they are used for.

My thought is that an Aggregate Typeclass would define the interface for the execute and apply commands, as well as somehow that the type requires associated types to represent its State, Commands, and Events. From there I could define some generic command handler that would be able to execute a command against any instance of an Aggregate. For example

class Aggregate a where =
    execute :: state -> command -> [event]
    apply   :: state -> event   -> state

Where state, command, and event are type variables representing State, Command, and Event for a given instance of Aggregate. This doesn't work, of course.

Is what I'm trying to do an appropriate use of Typeclasses? If so, how should I define the class such that an instance of Aggregate must have corresponding State, Command, and Event types?

If this the wrong approach, how should I be defining Aggregate in order to create higher level abstractions?

Update

Following @jberryman's advice I used an MPTC to define my Aggregate, which allowed me to create the generic command handler I was looking for:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}

module Aggregate where

class Aggregate state command event
    | state -> command event, command -> state event, event -> state command where
        execute :: state -> command -> [event]
        apply   :: state -> event   -> state
        zero    :: state

makeHandler (load, commit) = 
    (\ (id, expectedVersion) command -> 
        do events <- load id
           let state = foldl apply zero events
           let newEvents = execute state command
           commit (id, expectedVersion) newEvents)

Which in turn leads to the following instance declaration in my Vehicle module

instance Aggregate State Command Event where
    execute = Vehicle.execute
    apply   = Vehicle.apply
    zero    = Vehicle.zero

And then a sample script tying it all together:

import Aggregate
import Vehicle

-- Mock out IO to a domain repository (EventStore)
load :: Int -> IO [Event]
load id = do return [Created "Honda", MileageUpdated 15000]
commit (id, expectedVersion) events = putStrLn (show events)

-- Create the handler provide a command to it
handler = makeHandler (load, commit)
handler (1,1) Deactivate
2
data Aggregate = Aggregate { state :: State, command :: Command, event :: Event } or use a tuple type Aggregate = (State,Command,Event) - 4castle
Why does execute take a State argument? It doesn't use it... - Mark Seemann
@MarkSeemann True, I was just keeping the example super simple. In practice execute would have to validate the command against the current state before events could be generated (I should probably also be returning Maybe [Event]). - mclark1129

2 Answers

4
votes

If state, command and event are a triple that are all unique to a particular instance you can use MultiParameterTypeClasses along with FunctionalDependencies like

class Aggregate state command event | state -> command event, command -> state event, event -> state command  where
    execute :: state -> command -> [event]
    apply   :: state -> event   -> state

The fundeps after the | read: "where command and event are uniquely determined by state, and state and event are uniquely determined by command, and..." etc. This allows instances to be resolved if we can infer any one of the types in the instance head. You could do the same with type families.

But three questions you should ask yourself before defining a type class:

  • do I intend to write useful code that is polymorphic in this class?
  • do I expect users to define their own instances I haven't thought of?
  • does it have any laws?

If the answer is no to all of these then you probably shouldn't be defining a type class. It's hard to give an answer as to whether that's the case here.

But it might be e.g. that what you really want is something like:

data Aggregate state command where 
   Aggregate :: (state -> command -> [event]) -> (state -> event   -> state) -> Aggregate state command

this is a GADT where event is hidden.

4
votes

I'm not familiar with Lev Gorodinski's F# example, so this isn't guaranteed to point in the right direction, but I took a look at it, and it looks like one use of its Aggregate type is to create handlers. If we ignore all the impure operations that those functions imply, the core operation seems to be:

  • fold events to get the current state
  • execute the command against that state

So, inspired by @jberryman's answer, you can defined a typeclass like this:

class Aggregate state command event | event -> state command where
  eventsToState :: [event] -> state
  execute :: state -> command -> [event]

In order for this to compile, you'll need the FunctionalDependencies language extension.

You can now write the core of the handler function as a generic function, based on that typeclass:

handle :: Aggregate state command event => command -> [event] -> [event]
handle command = flip execute command . eventsToState

You can now define an instance of Aggregate for the types defined in the OP:

instance Aggregate State Command Event where
  eventsToState = foldl folder zero
    where
      folder state (Created _)        = state
      folder state (MileageUpdated _) = state
      folder state Deactivated        = state { isActive = False }
  execute _ (Create name mileage)   = [Created name, MileageUpdated mileage]
  execute _ (UpdateMileage mileage) = [MileageUpdated mileage]
  execute _ Deactivate              = [Deactivated]

Try it out in GHCI:

*Vehicle> handle (Create "BMW 520i" 250000) [] :: [Event]
[Created "BMW 520i",MileageUpdated 250000]
*Vehicle> handle (UpdateMileage 256000) it
[MileageUpdated 256000]
*Vehicle> handle Deactivate it
[Deactivated]

I haven't looked further around in Lev Gorodinski's F# example, so I'm not sure if this is sufficient, but I hope it's a start.