notxcain / aecor

Pure functional event sourcing runtime

Home Page:aecor.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Finalize aggregate behavior API

notxcain opened this issue · comments

After all I have a couple of thoughts on how it could be done. There are several decision to be made.

  1. Decide If there should be a support for traditional snapshot based behavior.
  2. Should behavior be implemented as a set of functions or using type class instance (is type class appropriate if it is used only in one place)?

There are several options for command handler signature:

Using type class with this signature

/**
 * Command[R] - type of command (or entity DSL) that produces result of type R
 * State - an entity state
 * F[R] - effect on state (not sure about naming) of command handler (see below)
 **/
trait CommandHandlerTC[A, Command[_], State, F[_]] {
  def handleCommand[R](a: A)(state: State, command: Command[R]): F[R]
}

or this

trait CommandHandlerTC[A, Command[_]] {
  type F[X]
  type State
  def handleCommand[R](a: A)(state: State, command: Command[R]): F[R]
}

without typeclass

type CommandHandlerT[Command[_], State, F[_]] = Command ~> λ[α => State => F[α]]

or

type CommandHandlerT[Command[_], State, F[_]] = λ[α => (Command[α], State)] ~> F

or

trait CommandHandlerT[Command[_], State, F[_]] {
  def handleCommand[R](command: Command[R], state: State): F[R]
}

we could go even further, and express effect as some change

type CommandHandler[Command[_], State, Change] = CommandHandlerT[Command, State, (?, Change)]

Here is the very difference between ES and snapshot based entities expressed in types:

type ES[Command[_], State, Event] = CommandHandler[Command, State, Seq[Event]]
type S[Command[_], State] = CommandHandler[Command, State, State]

Event sourcing adds ability to see why state changed compared to just the fact that it happened.

In order to apply change to current state we need one more function of type (State, Change) => State. The function signature though will depend on actual runtime requirements. e.g. for akka peristence based runtime user would need to provide (State, Event) => State function, which can be converted to (State, Seq[Event]) => State so they are kind of identical. For snapshot based runtime it's just a (State, State) => State with only one obvious implementation.

As for now, in order to drop behavior in Aecor, user should provide:

  1. Command handler
  2. Initial state
  3. Event projection
  4. Aggregate name (also used to tag all events of same entity).
  5. Correlation - used to extract entity id from a command.

to be continued...