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>