Last active
December 20, 2016 15:36
-
-
Save DamianReeves/9e785e1e6c9f5023ea568630c8454182 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace FSharp.EventSourcing | |
open System | |
open System.Threading.Tasks | |
open Orleankka | |
open Orleankka.FSharp | |
open Strategies.Contracts | |
open System.Collections | |
[<AbstractClass>] | |
type CqsActor<'Command,'Query> = | |
inherit Actor | |
interface IActor | |
new () = { inherit Orleankka.Actor(); } | |
new (id:string, runtime:IActorRuntime) = { | |
inherit Orleankka.Actor(id, runtime, null); | |
} | |
override this.OnReceive message = task { | |
match message with | |
| :? 'Command as cmd -> return! this.OnCommand(cmd) | |
| :? 'Query as query -> return! this.OnQuery(query) | |
| _ -> sprintf "Received unexpected message of type %s" (message.GetType().ToString()) |> failwith | |
return nothing | |
} | |
abstract member OnCommand: command:'Command -> Task<obj> | |
abstract member OnQuery: query: 'Query -> Task<obj> | |
abstract member Activate: unit -> Task<unit> | |
default this.Activate() = Task.completedTask | |
override this.OnActivate() = this.Activate() :> Task | |
[<AbstractClass>] | |
type EventSourcedActor<'Command,'Query,'Event>() = | |
inherit CqsActor<'Command,'Query>() | |
override this.OnCommand command = task { | |
let! events = this.Handle(command) | |
this.ApplyEvents(events) | |
return nothing | |
} | |
/// Handle the command | |
abstract member Handle: command:'Command -> Task<'Event seq> | |
/// Handle/apply the event | |
abstract member On: event:'Event -> unit | |
abstract member ApplyEvents: events:'Event seq -> unit | |
default this.ApplyEvents events = | |
for event in events do | |
this.On(event) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module StreamStoneSample | |
open System | |
open System.Threading.Tasks | |
open Orleankka | |
open Orleankka.FSharp | |
open FSharp.EventSourcing | |
type InventoryItemDetails = { | |
Name:string | |
Total:int | |
Active:bool | |
} | |
type Command = | |
| Create of Name:string | |
| CheckIn of Quantity:int | |
| CheckOut of Quantity:int | |
| Rename of NewName:string | |
| Deactivate | |
type Query = | |
| GetDetails | |
type Event = | |
| InventoryItemCreated of Name:string | |
| InventoryItemCheckedIn of Quantity:int | |
| InventoryItemCheckedOut of Quantity:int | |
| InventoryItemRenamed of OldName:string * NewName:string | |
| InventoryItemDeactivated | |
[<Reentrant(typeof<Query>)>] | |
type InventoryItem() as this = | |
inherit EventSourcedActor<Command,Query,Event>() | |
let mutable name = String.Empty; | |
let mutable total = 0; | |
let mutable active = false; | |
let checkIfActive() = | |
if not active then | |
sprintf "%s item is deactivated" this.Id | |
|> invalidOp | |
|> raise | |
let onCreate name = seq { | |
if String.IsNullOrWhiteSpace(name) then | |
"Inventory item name cannot be null or empty" |> invalidArg "name" |> raise | |
match name with | |
| null -> | |
sprintf "Inventory item with id %s has been already created" this.Id | |
|> invalidOp | |
|> raise | |
| _ -> () | |
yield InventoryItemCreated name | |
} | |
let onRename newName = seq { | |
checkIfActive() | |
if String.IsNullOrEmpty(newName) then | |
"Inventory item name cannot be null or empty" | |
|> invalidArg "newName" | |
|> raise | |
yield InventoryItemRenamed (name, newName) | |
} | |
let onCheckIn quantity = seq { | |
checkIfActive() | |
match quantity with | |
| qty when qty <= 0 -> | |
"must have a qty greater than 0 to add to inventory" | |
|> invalidOp | |
|> raise | |
| qty -> | |
yield InventoryItemCheckedIn(qty) | |
} | |
let onCheckOut quantity = seq { | |
checkIfActive() | |
match quantity with | |
| qty when qty <= 0 -> | |
"can't remove negative qty from inventory" | |
|> invalidOp | |
|> raise | |
| qty -> | |
yield InventoryItemCheckedOut(qty) | |
} | |
let onDeactivate () = seq { | |
checkIfActive() | |
yield InventoryItemDeactivated | |
} | |
/// <summary> | |
/// Handles events. | |
/// </summary> | |
/// <remarks> | |
/// The event handler is where the actually changes to state occur. | |
/// </remarks> | |
/// <param name="event"></param> | |
override x.On event = | |
match event with | |
| InventoryItemCreated itemName -> | |
name <- itemName | |
active <- true | |
| InventoryItemRenamed (_, newName) -> name <- newName | |
| InventoryItemCheckedIn newTotal -> total <- newTotal | |
| InventoryItemCheckedOut newTotal -> total <- newTotal | |
| InventoryItemDeactivated -> active <- false | |
override x.OnQuery query = task { | |
match query with | |
| GetDetails -> return {Name=name;Total=total;Active=active} :> obj | |
} | |
/// Handles commands | |
override x.Handle command = task{ | |
match command with | |
| Create name -> return onCreate name | |
| Rename newName -> return onRename newName | |
| CheckIn qty -> return onCheckIn qty | |
| CheckOut qty -> return onCheckOut qty | |
| Deactivate -> return onDeactivate() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment