Define an aggregate
An aggregate is a couple of types and two pure functions, bound to an actor with one call through the
FCQRS.FSharp facade.
open FCQRS.Common
open FCQRS.FSharp
type State = { Document: Root option; Version: int64 }
let initial = { Document = None; Version = 0L }
type Command =
| CreateOrUpdate of Root
| Approve
type Event =
| Updated of Root
| ApprovedEvt
| Errored
// decide (handleCommand): command + state -> action
let decide (cmd: Command<Command>) state =
match cmd.CommandDetails, state.Document with
| CreateOrUpdate doc, _ -> Updated doc |> PersistEvent
| Approve, Some _ -> ApprovedEvt |> PersistEvent
| Approve, None -> Errored |> DeferEvent
// fold (applyEvent): event -> new state
let fold (event: Event<Event>) state =
match event.EventDetails with
| Updated doc -> { state with Document = Some doc; Version = state.Version + 1L }
| _ -> state
// Registering the aggregate returns its typed handle.
let register (api: IActor) =
Fcqrs.aggregate api
{ Name = "Document"; Initial = initial; Decide = decide; Fold = fold }
|
Fcqrs.aggregate is the registration — calling it initializes the sharding region and hands back
an AggregateHandle with two members:
-
*
.Send cid id command filter* — send a command and await the first matching event (read-your-writes). ReturnsAsync<Event<'Event>>. -
*
.Factory* — an entity-ref factory you hand to a saga so it can target this aggregate.
Return the right action. PersistEvent stores, applies, and publishes the event (and bumps the
version). DeferEvent publishes a rejection without storing it — use it for "no" answers (like
Errored) that shouldn't enter history. IgnoreEvent does nothing; UnhandledEvent rejects the
message.
Keep both functions pure. No I/O, no clock, no side effects — they run on the happy path and during recovery replay, and must produce identical results. Side effects belong in a saga.
See Aggregates and the write side for the reasoning, and Test your domain to test these two functions directly.
val int64: value: 'T -> int64 (requires member op_Explicit)
--------------------
type int64 = System.Int64
--------------------
type int64<'Measure> = int64
module Event from Microsoft.FSharp.Control
--------------------
type Event = | Updated of obj | ApprovedEvt | Errored
--------------------
type Event<'T> = new: unit -> Event<'T> member Trigger: arg: 'T -> unit member Publish: IEvent<'T>
--------------------
type Event<'Delegate,'Args (requires delegate and 'Delegate :> Delegate and reference type)> = new: unit -> Event<'Delegate,'Args> member Trigger: sender: obj * args: 'Args -> unit member Publish: IEvent<'Delegate,'Args>
--------------------
new: unit -> Event<'T>
--------------------
new: unit -> Event<'Delegate,'Args>
FCQRS