ReST vs CQRS: The Trigger Pattern

Matt Hawkins @mattchawkins


Introduction

I've found that most online examples/blogs of Command Query Responsibility Segregation (CQRS) with Event Sourcing (ES) and Domain Driven Design (DDD) are fairly trivial. After trudging the road to create one of these behemoths on a production scale, in a complex financial domain, I’ve discovered that CQRS and ReST can be quite orthogonal.

The problem arises when you consider the limited number of HTTP methods that you have to execute non-query requests on the ReSTful API. In this blog post we’re going to focus on two verbs: POST and PUT. How do you perform multiple complex state changes with one or two verbs while still being ReSTful and not providing explicit intent to the API? I’ve found a few compelling solutions to this problem, with the winner being the trigger pattern.


Part 1: Domain Model

Let’s consider the domain of a Car. We’ll assume this domain requires a granular log of all state changes. Obviously we wouldn’t be changing CarID, but it’s likely the Color, Mileage, Condition, and Owner may change during a car’s lifecycle.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
[<CustomEquality;NoComparison>]
type Car =
  { CarID     : Guid 
    Year      : int
    CarType   : CarType
    Color     : Color
    Mileage   : int
    VIN       : string
    Condition : Condition
    Owner     : Guid }
  // DDD Entities have identity equality
  override this.Equals (that :obj) =
    match that with                                     
    | :? Car as that  -> this.CarID = that.CarID  
    | _               -> false  
  override this.GetHashCode () =
    hash this
and CarType =
  | Ford of Ford
  | BMW  of BMW
and Ford =
  | Escape | F150  
and BMW =
  | M3 | M5 
and Color =
  | Red | White | Blue
and Condition =
  | Excellent | Good | Average | Poor

Above is the Domain Model of a Car. All of these associated domain types represent an Aggregate in Domain Driven Design.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
type CarEvents =
  | Created          of  carID     :Guid 
                       * year      :int
                       * CarType   
                       * Color     
                       * mileage   :int
                       * vin       :string
                       * owner     :Guid
                       * condition :Condition
  | ColorChanged     of Color      
  | MileageChanged   of mileage    :int
  | ConditionChanged of Condition  
  | OwnerChanged     of owner      :Guid

Events model all possible state changes in a granular delta representation; the event only contains what has changed. There are a few different approaches to modeling events (granular delta's, snapshots, and before-and-after’s); depending on the requirement, you can choose accordingly.


Part 2: The ReSTful API

Providing an API to your domain model is how you affect change on it. Below we have two API endpoints, a POST and a PUT. For this example, POST will be used to create an instance of Car and PUT will be used to update an instance of Car. CQRS API's are typically light weight and have two job's, to fire-and-forget commands and to provide a data via query (GET).

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
// POST: {address}/cars
let postEndpoint httpRequest =
  let carID = Guid.NewGuid()
  { httpRequest.Body with CarID = carID}
  |> Create 
  |> sendToQueue
  // Return a 200 with the CarID
  OK (carID)

The POST endpoint parses the JSON body of the request:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
{
    "year": 2009,
    "make": "BMW",
    "model": "M3",
    "color": "black",
    "mileage": 14320,
    "vin": "2G4GM5ER4E9225618",
    "condition": "good",
    "owner": "ad4181f8-c33f-4e81-9d9e-0896188d73f0"
}

Then pipes the deserialized Car representation into an instance of a Create command and places it on the command queue.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
// PUT: {address}/cars/{carID}
let putEndpoint uri httpRequest =
  let carID = extractID uri
  { httpRequest.Body with CarID = carID }
  |> Update
  |> sendToQueue
  Accepted ()

The PUT endpoint is very similar to the POST endpoint except it includes the identifier of the Car in the URI as shown above. We simply use the F# with keyword to effectivly update the Car representation with the CarID, pipe it into an instance of Update command, and place the command on the command queue.


Part 3: The Command & Trigger Patterns

As shown above, the API publishes commands onto the command queue, which is then consumed by a command handler. Let's take a closer look at the commands:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
type CarCommands =
  | Create of CarRepresentation
  | Update of CarRepresentation
  | Delete of carID:Guid
and CarRepresentation = 
  { CarID     : Guid
    Year      : int
    Make      : string
    Model     : string
    Color     : string
    Mileage   : int
    VIN       : string
    Condition : string
    Owner     : Guid }

Notice the only responsiblity of the command is to encapsulate the Car's API representation, CarRepresentation.

Now let's examine how the command is handled. The commandHandler is responsible for consuming the command, validating it, and working with the Aggregate Root. The root may return an event which is then persisted and broadcasted.

1: 
2: 
3: 
4: 
5: 
6: 
let commandHandler command = choice {
  let! currentState,triggers = TriggerBuilder.map command
  let! finalState  ,events   = triggers |> execute root currentState
  do! events |> EventStore.saveEvents finalState.CarID
  do! broadcast events
  return ()}

The TriggerBuilder.map function takes an API command, data-type validates it, and maps it into domain trigger(s). Data-type validation is simply confirming that Guid's are not empty and strings map to their proper enum's, etc.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
type CarTriggers =
  | Create          of  carID     :Guid
                      * year      :int
                      * CarType   
                      * Color     
                      * mileage   :int
                      * vin       :string
                      * owner     :Guid
                      * condition :Condition
  | ChangeColor     of Color      
  | UpdateMileage   of mileage    :int
  | UpdateCondition of Condition  
  | ChangeOwner     of owner      :Guid

CarTriggers Trigger domain type provides explicit actions you can take against the Aggregate. CarCommands from the API have implicit intent, as you're just PUT'ing the future state of the Car to the API, whereas CarTriggers have explicit intent, and provide a precise modeling mechanism for complex state changes.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
module TriggerBuilder =
  // ...
  // Code commented out for readability, see examples below
  // ...
  let map command = 
    match command with
    | CarCommands.Create car   -> mapCreate car
    | CarCommands.Update car   -> mapUpdate car
    | CarCommands.Delete carID -> mapDelete carID

The TriggerBuilder matches on the CarCommands type and provides the CarRepresentation from the command to the mapping function (mapCreate, mapUpdate, or mapDelete).

The responsibility of the trigger mapping functions (mapCreate, mapUpdate, or mapDelete), are to 1) data-type validate the representation 2) determine which domain triggers to return. The data-type validation is quite trivial:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
let validateRepresentation (payload:CarRepresentation) = choice {
  // Simple type validation & mapping -> No domain validation here!
  let! carID     = validateID payload.CarID
  let! carType   = mapCarType payload
  let! color     = mapEnum<Color> payload.Color
  let! vin       = isValidString payload.VIN
  let! condition = mapEnum<Condition> payload.Condition
  let! ownerID   = validateID payload.Owner
  // Return all the mapped & validated types
  return carID,payload.Year,carType,color,payload.Mileage,vin,ownerID,condition }

Simple type validation and mapping of primitives to value types. ("Value Types" in the context of Domain Driven Design)

1: 
2: 
3: 
4: 
// Validate & map representation into domain types, and create the trigger
let mapCreate = validateRepresentation 
                >> Choice.map (Create >> List.singleton) 
                >> Choice.map (fun e -> root.Zero (),e)

mapCreate validates & maps the API representation CarRepresentation, then makes a Create trigger, finally mapping into a Tuple with the zero state of the Aggregate Root (see Part 4). The trigger builder will return this ('State * 'Triggers list) tuple back to the Command Handler, where it is then fired against the Aggregate Root; thus producing domain events.

Finally the part you've been waiting for. How does the system magically figure out what state been changed from the PUT API representation? Truth is, it's not that magical.

1: 
2: 
3: 
4: 
let mapUpdate representation = choice {
  let! carID,_,_,color,mileage,_,owner,condition = validateRepresentation representation
  let! currentState = EventStore.rehydrate<Car> carID
  return! compare currentState (color,mileage,owner,condition) }

mapUpdate data-type validates the CarRepresentation the same way the mapCreate function does. The compare function is where the analysis happens.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
let compare current proposed = choice {
  // Figure out what has changed!
  let proposedColor,proposedMileage,proposedOwner,proposedCondition = proposed
  let changeColorTrigger     = proposedColor     |> evalColor     current.Color
  let updateMileageTrigger   = proposedMileage   |> evalMileage   current.Mileage
  let changeOwnerTrigger     = proposedOwner     |> evalOwner     current.Owner
  let updateConditionTrigger = proposedCondition |> evalCondition current.Condition
  // Controls the order in which the triggers fire to the aggregate
  return
    current,
    [changeColorTrigger
     updateMileageTrigger
     changeOwnerTrigger
     updateConditionTrigger]
    |> List.choose some }
     

Each eval function evaluates the current and proposed state, returning a Trigger option.

1: 
2: 
3: 
4: 
let evalColor current proposed =
  if current = proposed
    then None
    else Some <| ChangeColor proposed

evalColor evaluates current and proposed color, returning a ChangeColor option (Trigger).

To summarize the Command Handler, it consists of the following components:
1. Trigger Builder
-Maps "implicit intent" API Commands to "explicit intent" Domain Triggers.
2. Execution
-Involves rehydration of the Aggregate from the event store and firing of the Triggers against the Aggregate Root, which result in Domain Events.
3. Persistence & Broadcasting
-Saving the Domain Events to durable storage (Event Store) and broadcasting said events to interested parties.


Part 4: CQRS Aggregate Root

The Domain Driven Design Aggregate Root is the "chosen" entity that is responsible for consistency within the aggregates type's and acts as a liason to the outside world. In this example the Aggregate Root was a Car. Consider the abstract notion of the DDD Aggregate Root with the following concrete type definition of a CQRS Aggregate Root:

1: 
2: 
3: 
4: 
type AggregateRoot<'state,'event,'trigger> =
  { Zero  :  unit  -> 'state
    Apply : 'state -> 'event   -> 'state
    Fire  : 'state -> 'trigger ->  Choice<'event,DomainError> }

AggregateRoot type implemented for the Car aggregate root:

1: 
2: 
3: 
4: 
let root = 
  { Zero  = (fun _ -> Unchecked.defaultof<Car>)
    Apply =  applyFn
    Fire  =  fireFn }
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
let fireFn = 
  (fun state trigger -> 
    match trigger with
    | Create (carID,year,carType,color,mileage,vin,owner,condition) -> choice {
        // Domain validation
        let! carID   = validateID carID
        let! year    = carType |> validateYear year
        let! mileage = isNonNegative mileage
        // Return the domain event
        return Created (carID,year,carType,color,mileage,vin,owner,condition) }
    | ChangeColor (color) -> 
        // No validation necessary
        ColorChanged color |> Choice.result
    | UpdateMileage (mileage) -> choice {
        // Domain validation
        let! mileage = mileage |> isGreaterThan state.Mileage
        return MileageChanged mileage }
    | UpdateCondition (condition) ->
        ConditionChanged condition |> Choice.result
    | ChangeOwner (ownerID) ->
        OwnerChanged ownerID |> Choice.result)
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
let applyFn =
  (fun state event ->
    match event with 
    | Created (carID,year,carType,color,mileage,vin,owner,condition) ->
        { state with CarID     = carID
                     Year      = year
                     CarType   = carType
                     Color     = color
                     Mileage   = mileage
                     VIN       = vin
                     Owner     = owner
                     Condition = condition }
    | ColorChanged (color) ->
        { state with Color = color }
    | MileageChanged (mileage) ->
        { state with Mileage = mileage }
    | ConditionChanged (condition) ->
        { state with Condition = condition }
    | OwnerChanged (ownerID) ->
        { state with Owner = ownerID })

Notice the AggregateRoot type has 3 functions. Zero provides a starting state for the Aggregate Root, frequently used during rehydration/hydration of Domain Events. Apply provides model rehydration functionality. Fire is a function that takes the current state of the Aggregate and a Domain Trigger, and returns a Domain Event.

Closing Thoughts

There are many ways to expose domain actions through an API. The Trigger Pattern approach is one that strives to allow you to maintain somewhat of a ReSTful API. Another approach I've encountered (but haven't tested), is moving the "trigger" logic into the client, effectively placing a command-type in the request headers. I feel this approach is may be moving you torwards RPC rather than ReST, but it is another option. A similar alternative is just using the PATCH verb, where the client supplies the delta change to the API, effectively eliminating the need for Triggers in the most trivial of cases.


Written by:
Matt Hawkins |
December 18th 2015 |
Twitter: @mattchawkins |

namespace System
namespace ExtCore
namespace ExtCore.Control
type CarCommands =
  | Create of CarRepresentation
  | Update of CarRepresentation
  | Delete of carID: Guid

Full name: Post.CarCommands
union case CarCommands.Create: CarRepresentation -> CarCommands
type CarRepresentation =
  {CarID: Guid;
   Year: int;
   Make: string;
   Model: string;
   Color: string;
   Mileage: int;
   VIN: string;
   Condition: string;
   Owner: Guid;}

Full name: Post.CarRepresentation
union case CarCommands.Update: CarRepresentation -> CarCommands
union case CarCommands.Delete: carID: Guid -> CarCommands
Multiple items
type Guid =
  struct
    new : b:byte[] -> Guid + 4 overloads
    member CompareTo : value:obj -> int + 1 overload
    member Equals : o:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member ToByteArray : unit -> byte[]
    member ToString : unit -> string + 2 overloads
    static val Empty : Guid
    static member NewGuid : unit -> Guid
    static member Parse : input:string -> Guid
    static member ParseExact : input:string * format:string -> Guid
    ...
  end

Full name: System.Guid

--------------------
Guid()
Guid(b: byte []) : unit
Guid(g: string) : unit
Guid(a: int, b: int16, c: int16, d: byte []) : unit
Guid(a: uint32, b: uint16, c: uint16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : unit
Guid(a: int, b: int16, c: int16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : unit
CarRepresentation.CarID: Guid
CarRepresentation.Year: int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
CarRepresentation.Make: string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string

--------------------
type string<'Measure> = string

Full name: ExtCore.string<_>
CarRepresentation.Model: string
CarRepresentation.Color: string
CarRepresentation.Mileage: int
CarRepresentation.VIN: string
CarRepresentation.Condition: string
CarRepresentation.Owner: Guid
type HTTPRequest =
  {Body: CarRepresentation;}

Full name: Post.HTTPRequest
HTTPRequest.Body: CarRepresentation
val sendToQueue : message:'a -> unit

Full name: Post.sendToQueue
val message : 'a
val extractID : uri:'a -> Guid

Full name: Post.extractID
val uri : 'a
Guid.NewGuid() : Guid
val OK : locationHeaders:'a -> unit

Full name: Post.OK
val locationHeaders : 'a
val Accepted : body:'a -> unit

Full name: Post.Accepted


 Returns a 202 status code
val body : 'a
Multiple items
module Choice

from ExtCore

--------------------
type Choice<'T1,'T2> =
  | Choice1Of2 of 'T1
  | Choice2Of2 of 'T2

Full name: Microsoft.FSharp.Core.Choice<_,_>

--------------------
type Choice<'T1,'T2,'T3> =
  | Choice1Of3 of 'T1
  | Choice2Of3 of 'T2
  | Choice3Of3 of 'T3

Full name: Microsoft.FSharp.Core.Choice<_,_,_>

--------------------
type Choice<'T1,'T2,'T3,'T4> =
  | Choice1Of4 of 'T1
  | Choice2Of4 of 'T2
  | Choice3Of4 of 'T3
  | Choice4Of4 of 'T4

Full name: Microsoft.FSharp.Core.Choice<_,_,_,_>

--------------------
type Choice<'T1,'T2,'T3,'T4,'T5> =
  | Choice1Of5 of 'T1
  | Choice2Of5 of 'T2
  | Choice3Of5 of 'T3
  | Choice4Of5 of 'T4
  | Choice5Of5 of 'T5

Full name: Microsoft.FSharp.Core.Choice<_,_,_,_,_>

--------------------
type Choice<'T1,'T2,'T3,'T4,'T5,'T6> =
  | Choice1Of6 of 'T1
  | Choice2Of6 of 'T2
  | Choice3Of6 of 'T3
  | Choice4Of6 of 'T4
  | Choice5Of6 of 'T5
  | Choice6Of6 of 'T6

Full name: Microsoft.FSharp.Core.Choice<_,_,_,_,_,_>

--------------------
type Choice<'T1,'T2,'T3,'T4,'T5,'T6,'T7> =
  | Choice1Of7 of 'T1
  | Choice2Of7 of 'T2
  | Choice3Of7 of 'T3
  | Choice4Of7 of 'T4
  | Choice5Of7 of 'T5
  | Choice6Of7 of 'T6
  | Choice7Of7 of 'T7

Full name: Microsoft.FSharp.Core.Choice<_,_,_,_,_,_,_>
val packSequence : col:seq<Choice<'a,'b>> -> Choice<seq<'a>,'b []>

Full name: Post.Choice.packSequence
val col : seq<Choice<'a,'b>>
Multiple items
module Seq

from ExtCore.Collections

--------------------
module Seq

from Microsoft.FSharp.Collections
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map
val s : Choice<'a,'b>
union case Choice.Choice1Of2: 'T1 -> Choice<'T1,'T2>
union case Choice.Choice2Of2: 'T2 -> Choice<'T1,'T2>
type Array =
  member Clone : unit -> obj
  member CopyTo : array:Array * index:int -> unit + 1 overload
  member GetEnumerator : unit -> IEnumerator
  member GetLength : dimension:int -> int
  member GetLongLength : dimension:int -> int64
  member GetLowerBound : dimension:int -> int
  member GetUpperBound : dimension:int -> int
  member GetValue : [<ParamArray>] indices:int[] -> obj + 7 overloads
  member Initialize : unit -> unit
  member IsFixedSize : bool
  ...

Full name: System.Array
val ofSeq : source:seq<'T> -> 'T []

Full name: Microsoft.FSharp.Collections.Array.ofSeq
val partition : predicate:('T -> bool) -> array:'T [] -> 'T [] * 'T []

Full name: Microsoft.FSharp.Collections.Array.partition
Multiple items
val obj : bool * Choice<'a,'b>

--------------------
type obj = Object

Full name: Microsoft.FSharp.Core.obj
val fst : tuple:('T1 * 'T2) -> 'T1

Full name: Microsoft.FSharp.Core.Operators.fst
val passed : (bool * Choice<'a,'b>) []
val failed : (bool * Choice<'a,'b>) []
property Array.Length: int
val result : value:'T -> Choice<'T,'Error>

Full name: ExtCore.Choice.result
val map : mapping:('T -> 'U) -> array:'T [] -> 'U []

Full name: Microsoft.FSharp.Collections.Array.map
val p : bool * Choice<'a,'b>
val snd : tuple:('T1 * 'T2) -> 'T2

Full name: Microsoft.FSharp.Core.Operators.snd
val get : value:Choice<'T,'Error> -> 'T

Full name: ExtCore.Choice.get
val ofArray : source:'T [] -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.ofArray
val error : value:'Error -> Choice<'T,'Error>

Full name: ExtCore.Choice.error
val err : Choice<'a,'b>
val getError : value:Choice<'T,'Error> -> 'Error

Full name: ExtCore.Choice.getError
val rehydrate : streamID:Guid -> Choice<'aggregate,string>

Full name: Post.EventStore.rehydrate
val streamID : Guid
Multiple items
module Choice

from Post

--------------------
module Choice

from ExtCore

--------------------
type Choice<'T1,'T2> =
  | Choice1Of2 of 'T1
  | Choice2Of2 of 'T2

Full name: Microsoft.FSharp.Core.Choice<_,_>

--------------------
type Choice<'T1,'T2,'T3> =
  | Choice1Of3 of 'T1
  | Choice2Of3 of 'T2
  | Choice3Of3 of 'T3

Full name: Microsoft.FSharp.Core.Choice<_,_,_>

--------------------
type Choice<'T1,'T2,'T3,'T4> =
  | Choice1Of4 of 'T1
  | Choice2Of4 of 'T2
  | Choice3Of4 of 'T3
  | Choice4Of4 of 'T4

Full name: Microsoft.FSharp.Core.Choice<_,_,_,_>

--------------------
type Choice<'T1,'T2,'T3,'T4,'T5> =
  | Choice1Of5 of 'T1
  | Choice2Of5 of 'T2
  | Choice3Of5 of 'T3
  | Choice4Of5 of 'T4
  | Choice5Of5 of 'T5

Full name: Microsoft.FSharp.Core.Choice<_,_,_,_,_>

--------------------
type Choice<'T1,'T2,'T3,'T4,'T5,'T6> =
  | Choice1Of6 of 'T1
  | Choice2Of6 of 'T2
  | Choice3Of6 of 'T3
  | Choice4Of6 of 'T4
  | Choice5Of6 of 'T5
  | Choice6Of6 of 'T6

Full name: Microsoft.FSharp.Core.Choice<_,_,_,_,_,_>

--------------------
type Choice<'T1,'T2,'T3,'T4,'T5,'T6,'T7> =
  | Choice1Of7 of 'T1
  | Choice2Of7 of 'T2
  | Choice3Of7 of 'T3
  | Choice4Of7 of 'T4
  | Choice5Of7 of 'T5
  | Choice6Of7 of 'T6
  | Choice7Of7 of 'T7

Full name: Microsoft.FSharp.Core.Choice<_,_,_,_,_,_,_>
module Unchecked

from Microsoft.FSharp.Core.Operators
val defaultof<'T> : 'T

Full name: Microsoft.FSharp.Core.Operators.Unchecked.defaultof
val saveEvents : streamID:Guid -> events:'a -> Choice<unit,'b>

Full name: Post.EventStore.saveEvents
val events : 'a
val some : a:'a -> 'a

Full name: Post.some
val a : 'a
val broadcast : events:'a -> Choice<unit,'b>

Full name: Post.broadcast


 Broadcasts events to interested parties
val postEndpoint : httpRequest:HTTPRequest -> unit

Full name: Post.postEndpoint
val httpRequest : HTTPRequest
val carID : Guid
val putEndpoint : uri:'a -> httpRequest:HTTPRequest -> unit

Full name: Post.putEndpoint
Multiple items
type CustomEqualityAttribute =
  inherit Attribute
  new : unit -> CustomEqualityAttribute

Full name: Microsoft.FSharp.Core.CustomEqualityAttribute

--------------------
new : unit -> CustomEqualityAttribute
Multiple items
type NoComparisonAttribute =
  inherit Attribute
  new : unit -> NoComparisonAttribute

Full name: Microsoft.FSharp.Core.NoComparisonAttribute

--------------------
new : unit -> NoComparisonAttribute
type Car =
  {CarID: Guid;
   Year: int;
   CarType: CarType;
   Color: Color;
   Mileage: int;
   VIN: string;
   Condition: Condition;
   Owner: Guid;}
  override Equals : that:obj -> bool
  override GetHashCode : unit -> int

Full name: Post.Car
Car.CarID: Guid
Car.Year: int
Multiple items
Car.CarType: CarType

--------------------
type CarType =
  | Ford of Ford
  | BMW of BMW

Full name: Post.CarType
Multiple items
Car.Color: Color

--------------------
type Color =
  | Red
  | White
  | Blue

Full name: Post.Color
Car.Mileage: int
Car.VIN: string
Multiple items
Car.Condition: Condition

--------------------
type Condition =
  | Excellent
  | Good
  | Average
  | Poor

Full name: Post.Condition
Car.Owner: Guid
val this : Car
override Car.Equals : that:obj -> bool

Full name: Post.Car.Equals
val that : obj
type obj = Object

Full name: Microsoft.FSharp.Core.obj
val that : Car
override Car.GetHashCode : unit -> int

Full name: Post.Car.GetHashCode
val hash : obj:'T -> int (requires equality)

Full name: Microsoft.FSharp.Core.Operators.hash
type CarType =
  | Ford of Ford
  | BMW of BMW

Full name: Post.CarType
Multiple items
union case CarType.Ford: Ford -> CarType

--------------------
type Ford =
  | Escape
  | F150

Full name: Post.Ford
Multiple items
union case CarType.BMW: BMW -> CarType

--------------------
type BMW =
  | M3
  | M5

Full name: Post.BMW
type Ford =
  | Escape
  | F150

Full name: Post.Ford
union case Ford.Escape: Ford
union case Ford.F150: Ford
type BMW =
  | M3
  | M5

Full name: Post.BMW
union case BMW.M3: BMW
union case BMW.M5: BMW
type Color =
  | Red
  | White
  | Blue

Full name: Post.Color
union case Color.Red: Color
union case Color.White: Color
union case Color.Blue: Color
type Condition =
  | Excellent
  | Good
  | Average
  | Poor

Full name: Post.Condition
union case Condition.Excellent: Condition
union case Condition.Good: Condition
union case Condition.Average: Condition
union case Condition.Poor: Condition
type CarEvents =
  | Created of carID: Guid * year: int * CarType * Color * mileage: int * vin: string * owner: Guid * condition: Condition
  | ColorChanged of Color
  | MileageChanged of mileage: int
  | ConditionChanged of Condition
  | OwnerChanged of owner: Guid

Full name: Post.CarEvents
union case CarEvents.Created: carID: Guid * year: int * CarType * Color * mileage: int * vin: string * owner: Guid * condition: Condition -> CarEvents
union case CarEvents.ColorChanged: Color -> CarEvents
union case CarEvents.MileageChanged: mileage: int -> CarEvents
union case CarEvents.ConditionChanged: Condition -> CarEvents
union case CarEvents.OwnerChanged: owner: Guid -> CarEvents
type CarTriggers =
  | Create of carID: Guid * year: int * CarType * Color * mileage: int * vin: string * owner: Guid * condition: Condition
  | ChangeColor of Color
  | UpdateMileage of mileage: int
  | UpdateCondition of Condition
  | ChangeOwner of owner: Guid

Full name: Post.CarTriggers
union case CarTriggers.Create: carID: Guid * year: int * CarType * Color * mileage: int * vin: string * owner: Guid * condition: Condition -> CarTriggers
union case CarTriggers.ChangeColor: Color -> CarTriggers
union case CarTriggers.UpdateMileage: mileage: int -> CarTriggers
union case CarTriggers.UpdateCondition: Condition -> CarTriggers
union case CarTriggers.ChangeOwner: owner: Guid -> CarTriggers
type DomainError = String

Full name: Post.DomainError
Multiple items
type String =
  new : value:char -> string + 7 overloads
  member Chars : int -> char
  member Clone : unit -> obj
  member CompareTo : value:obj -> int + 1 overload
  member Contains : value:string -> bool
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EndsWith : value:string -> bool + 2 overloads
  member Equals : obj:obj -> bool + 2 overloads
  member GetEnumerator : unit -> CharEnumerator
  member GetHashCode : unit -> int
  ...

Full name: System.String

--------------------
String(value: nativeptr<char>) : unit
String(value: nativeptr<sbyte>) : unit
String(value: char []) : unit
String(c: char, count: int) : unit
String(value: nativeptr<char>, startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int) : unit
String(value: char [], startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : unit
type AggregateRoot<'state,'event,'trigger> =
  {Zero: unit -> 'state;
   Apply: 'state -> 'event -> 'state;
   Fire: 'state -> 'trigger -> Choice<'event,DomainError>;}

Full name: Post.AggregateRoot<_,_,_>
val state : StateBuilder

Full name: ExtCore.Control.WorkflowBuilders.state
AggregateRoot.Zero: unit -> 'state
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
AggregateRoot.Apply: 'state -> 'event -> 'state
AggregateRoot.Fire: 'state -> 'trigger -> Choice<'event,DomainError>
val validateID : guidID:Guid -> Choice<Guid,string>

Full name: Post.validateID
val guidID : Guid
field Guid.Empty
val validateYear : year:'a -> carType:'b -> Choice<'a,'c>

Full name: Post.validateYear
val year : 'a
val carType : 'b
val isNonNegative : num:'a -> Choice<'a,'b>

Full name: Post.isNonNegative
val num : 'a
val isGreaterThan : that:'a -> this:'b -> Choice<'b,'c>

Full name: Post.isGreaterThan
val that : 'a
val this : 'b
val fireFn : state:Car -> trigger:CarTriggers -> Choice<CarEvents,string>

Full name: Post.fireFn
val state : Car
val trigger : CarTriggers
val year : int
val carType : CarType
val color : Color
val mileage : int
val vin : string
val owner : Guid
val condition : Condition
val choice : ChoiceBuilder

Full name: ExtCore.Control.WorkflowBuilders.choice
val ownerID : Guid
val applyFn : state:Car -> event:CarEvents -> Car

Full name: Post.applyFn
val event : CarEvents
val root : AggregateRoot<Car,CarEvents,CarTriggers>

Full name: Post.root
val mapEnum : str:string -> Choice<'enum,string>

Full name: Post.mapEnum
val enum : value:int32 -> 'U (requires enum)

Full name: Microsoft.FSharp.Core.Operators.enum
val str : string
type Enum =
  member CompareTo : target:obj -> int
  member Equals : obj:obj -> bool
  member GetHashCode : unit -> int
  member GetTypeCode : unit -> TypeCode
  member HasFlag : flag:Enum -> bool
  member ToString : unit -> string + 3 overloads
  static member Format : enumType:Type * value:obj * format:string -> string
  static member GetName : enumType:Type * value:obj -> string
  static member GetNames : enumType:Type -> string[]
  static member GetUnderlyingType : enumType:Type -> Type
  ...

Full name: System.Enum
Enum.Parse(enumType: Type, value: string) : obj
Enum.Parse(enumType: Type, value: string, ignoreCase: bool) : obj
val typedefof<'T> : Type

Full name: Microsoft.FSharp.Core.Operators.typedefof
val x : exn
val isValidString : str:'a -> Choice<'a,'b>

Full name: Post.isValidString
val str : 'a
val mapCarType : payload:CarRepresentation -> Choice<CarType,string>

Full name: Post.mapCarType
val payload : CarRepresentation
val make : string
String.Trim() : string
String.Trim([<ParamArray>] trimChars: char []) : string
val model : string
val bind : binding:('T -> Choice<'U,'Error>) -> value:Choice<'T,'Error> -> Choice<'U,'Error>

Full name: ExtCore.Choice.bind
val validateRepresentation : payload:CarRepresentation -> Choice<(Guid * int * CarType * Color * int * string * Guid * Condition),string>

Full name: Post.validateRepresentation
val mapCreate : (CarRepresentation -> Choice<(Car * CarTriggers list),string>)

Full name: Post.mapCreate
val map : mapping:('T -> 'U) -> value:Choice<'T,'Error> -> Choice<'U,'Error>

Full name: ExtCore.Choice.map
Multiple items
module List

from ExtCore.Collections

--------------------
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
Multiple items
val singleton : value:'T -> 'T list

Full name: ExtCore.Collections.List.singleton

--------------------
val singleton : value:'T -> 'T list

Full name: Microsoft.FSharp.Collections.List.singleton
val e : CarTriggers list
AggregateRoot.Zero: unit -> Car
val evalColor : current:Color -> proposed:Color -> CarTriggers option

Full name: Post.evalColor
val current : Color
val proposed : Color
union case Option.None: Option<'T>
union case Option.Some: Value: 'T -> Option<'T>
val evalMileage : current:int -> proposed:int -> CarTriggers option

Full name: Post.evalMileage
val current : int
val proposed : int
val evalOwner : current:Guid -> proposed:Guid -> CarTriggers option

Full name: Post.evalOwner
val current : Guid
val proposed : Guid
val evalCondition : current:Condition -> proposed:Condition -> CarTriggers option

Full name: Post.evalCondition
val current : Condition
val proposed : Condition
val compare : current:Car -> Color * int * Guid * Condition -> Choice<(Car * CarTriggers list),'a>

Full name: Post.compare
val current : Car
val proposed : Color * int * Guid * Condition
val proposedColor : Color
val proposedMileage : int
val proposedOwner : Guid
val proposedCondition : Condition
val changeColorTrigger : CarTriggers option
Car.Color: Color
val updateMileageTrigger : CarTriggers option
val changeOwnerTrigger : CarTriggers option
val updateConditionTrigger : CarTriggers option
Car.Condition: Condition
val choose : chooser:('T -> 'U option) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.choose
val mapUpdate : representation:CarRepresentation -> Choice<(Car * CarTriggers list),string>

Full name: Post.mapUpdate
val representation : CarRepresentation
val currentState : Car
module EventStore

from Post
val mapDelete : carID:'a -> 'b

Full name: Post.mapDelete
val carID : 'a
val map : command:CarCommands -> Choice<(Car * CarTriggers list),string>

Full name: Post.TriggerBuilder.map
val command : CarCommands
val car : CarRepresentation
val execute : root:AggregateRoot<'a,'b,'c> -> fromState:'a -> triggers:seq<'c> -> Choice<('a * seq<'b>),string>

Full name: Post.execute
val root : AggregateRoot<'a,'b,'c>
val fromState : 'a
val triggers : seq<'c>
val fold : folder:('State -> 'T -> 'State) -> state:'State -> source:seq<'T> -> 'State

Full name: Microsoft.FSharp.Collections.Seq.fold
val state : Choice<('a * 'b),DomainError> list
val t : 'c
val append : list1:'T list -> list2:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.append
val s : 'a
val last : source:seq<'T> -> 'T

Full name: Microsoft.FSharp.Collections.Seq.last
val e : 'b
AggregateRoot.Fire: 'a -> 'c -> Choice<'b,DomainError>
AggregateRoot.Apply: 'a -> 'b -> 'a
property List.Empty: 'T list
val data : seq<'a * 'b>
val state : 'a
val events : seq<'b>
val mapError : mapping:('Error1 -> 'Error2) -> value:Choice<'T,'Error1> -> Choice<'T,'Error2>

Full name: ExtCore.Choice.mapError
val concatArray : arr:string [] -> string

Full name: ExtCore.String.concatArray
val commandHandler : command:CarCommands -> Choice<unit,string>

Full name: Post.commandHandler
val triggers : CarTriggers list
module TriggerBuilder

from Post
val finalState : Car
val events : seq<CarEvents>