2014-08-11
Designing Aggregates, Algebra
This evening, I start to feel that things won't move much as long as I have only one Aggregate.
With several Aggregates we'll have to add Process and Process Managers to orchestrate the reactions to events, and ensure the global consistency of the system.
We'll also have to manage projections over several aggregates. And make the infrastructure code a bit more generic to not wire it directly to the only aggregate.
But before this happens: Design !
The Game Aggregate
This one is already partly implemented and represents the enforcement of rules on the discard pile.
A discussion on Jabbr with @rbartelink interrupted me in the process for something awesome !
Algebra
The first question was whether to change the empty
member and make it a static member
, ditto the apply
function.
The second was whether to try to hide empty
and apply
completly in a replay
function.
empty
as a static member feels quite natural, but I had a problem with apply
. Its signature
seems to validate this change, but putting it before handle
seems wrong.
The problem is that when reading an Aggregate, the first important thing is the decision
in the handle
method, and changes in apply
are always implemented later. Changing
this would break the flow.
But it's possible to implement it further in F# with a type extension:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
|
The second question started with this sample code:
1: 2: |
|
It would be possible to write this but it's not part of the Aggregate, let's see why.
Monoids
Do you remember how a Monoid is defined ?
A monoid is a triple (S,+,n) where
S
is a set+
is closed on the set (the signature would be S -> S -> S)+
is associative ( x + y + z = (x + y) + z = x + (y + z) )n
is the neutral(aka identity) element ( x + n = n + x = x)
Most of mathematical structures are defined using sets (here S
), functions (here +
) and specific set items
(here n
).
Aggregates
Aggregates are not monoids, but you can see that it could be defined using the same kind of primary element:
A quadruple (State, handle, apply, empty) with the rules and signatures you know. handle
and apply
are not closed on the set but the composition is not bad.
If we alias apply
as +
we can write:
1: 2: 3: |
|
Makes sense, right ?
The only change that I would propose is to change empty
to initial
because it's the initial state:
1: 2: 3: 4: |
|
Hiding apply
and initial
would seriously reduce the composability of all this.
Higher order functions
replay
can actually be seen as a high order function built using initial
and apply
.
To build it directly from the State
type we can use
statically resolved type parameters,
you can hover over the replay
definition below to see the type constraints:
1: 2: 3: 4: |
|
Ok, it's a bit more complicated but you can check the signature by hovering the function, and
it can be used on any type that has initial
and apply
members that adhere to the expected signature.
And the inference still works from the return type, so any hint to the compiler indicating
the specific Aggregate State
type in use means there is no need for further type annotations.
1: 2: 3: |
|
So where are we now ?
Better vocabulary with initial
, better understanding about the Aggregate algebra, and
a high order function to use it.
Now we can see how we'll implement more generic command handlers.
Good evening !
module Event
from Microsoft.FSharp.Control
--------------------
type Event = | SomethingHappened
Full name: Designing Aggregates.Event
--------------------
type Event<'T> =
new : unit -> Event<'T>
member Trigger : arg:'T -> unit
member Publish : IEvent<'T>
Full name: Microsoft.FSharp.Control.Event<_>
--------------------
type Event<'Delegate,'Args (requires delegate and 'Delegate :> Delegate)> =
new : unit -> Event<'Delegate,'Args>
member Trigger : sender:obj * args:'Args -> unit
member Publish : IEvent<'Delegate,'Args>
Full name: Microsoft.FSharp.Control.Event<_,_>
--------------------
new : unit -> Event<'T>
--------------------
new : unit -> Event<'Delegate,'Args>
from Designing Aggregates
{CurrentState: string;}
static member apply : state:State -> (Event -> State)
static member build : (State -> Event list -> State)
static member empty : State
static member initial : State
static member replay : (Event list -> State)
Full name: Designing Aggregates.FirstAttempt.State
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
Full name: Designing Aggregates.FirstAttempt.State.empty
Full name: Designing Aggregates.FirstAttempt.handle
Full name: Designing Aggregates.FirstAttempt.State.apply
Full name: Designing Aggregates.FirstAttempt.State.build
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<_>
Full name: Microsoft.FSharp.Collections.List.fold
Full name: Designing Aggregates.FirstAttempt.State.replay
Full name: Designing Aggregates.FirstAttempt.finalState
Full name: Designing Aggregates.FirstAttempt.State.initial
Full name: Designing Aggregates.FirstAttempt.finalState'
Full name: Designing Aggregates.FirstAttempt.replay
Full name: Designing Aggregates.FirstAttempt.state