5
\$\begingroup\$

Is this a satisfactory implementation of event sourcing using functional programming?

module Account

(*Types*)
type EventAggregate<'Aggregate> = { Event:Event; Aggregate:'Aggregate }

and Command = 
    | Login      of EventAggregate<Account>
    | Deposit    of EventAggregate<Account>
    | Withdrawal of EventAggregate<Account>

and Event = 
    | LoggedIn  of Login
    | Deposited of decimal
    | Withdrew  of decimal

and AccountId = AccountId of string
and FirstName = FirstName of string
and LastName =  LastName  of string

and Login =   { UserId:string; Password:string }
and Account = {
    AccountId: AccountId
    FirstName: FirstName
    LastName:  LastName }

(*Functions*)
let appendTo (repo:Map<Event,_>) (plan:EventAggregate<'a>) =
    repo.Add (plan.Event , plan.Aggregate)

let getAccount login = {
    AccountId= AccountId "myAccountId"
    FirstName= FirstName "Scott"
    LastName= LastName   "Nimrod" }

let interpret cmd repo = cmd |> function
    | Login   v
    | Deposit v -> appendTo repo v      
    | _         -> repo

(*Client*)
let login = { UserId= "MyUserId"; Password= "MyPassword" }
let account = getAccount login

let loginPlan =    { Event=     LoggedIn login
                     Aggregate= account        }

let depositPlan =  { Event=     Deposited 100m
                     Aggregate= account        }

let store =  (Login loginPlan , Map.empty) 
             ||> interpret
              |> interpret (Deposit depositPlan)
\$\endgroup\$
1
  • \$\begingroup\$ Welcome to Code Review! Your question could use a little more context. What does it do? Do you have specific concerns about your code? To get the best out of asking questions on Code Review, please read this post on meta. \$\endgroup\$
    – Mast
    Feb 11, 2017 at 14:17

2 Answers 2

5
\$\begingroup\$

The key concept in event sourcing is having an 'updater' function that takes an event and an old state, and returns a new state. Let's capture that as a type. The type can be very generic:

module Update =
  type t<'s, 'e> = 's -> 'e -> 's

  (**
  A function that represents the operation of taking an event and an old
  state to return a new state.
  *)

The next thing to capture is your specific domain and its specific implementation of the 'updater' function:

module Account =
  type t = { logged_in : bool; balance : decimal }
  type event =
    | Login of userid:string * passwd:string
    | Deposit of decimal
    | Withdraw of decimal

  let empty = { logged_in = false; balance = 0m }
  let login_valid userid passwd = true // <- TBD
  let update t event =
    match event, t with
      | Login (userid, passwd), _ ->
        { t with logged_in = login_valid userid passwd }

      | Deposit amt, { logged_in = true; balance = balance } ->
        { t with balance = balance + amt }

      | Withdraw amt, { logged_in = true; balance = balance } ->
        { t with balance = balance - amt }

      | _, t -> t

  (* val update : Update.t<t, event>

Finally, you need some stream of events. It can be in the form of some stream data type or an observable sequence. We will use an IObservable<Account.event> because it's idiomatic in the .Net world. With this stream of events, you can 'fold' over the events and regenerate your final state (your account) by applying the update function at each step.

module Main =
  open System.Reactive.Linq

  (* For example only; real stream will be built differently. *)
  let acct_events =
    Observable.Concat(
      [| Observable.Return(Account.Login ("bob", "pass"))
         Observable.Return(Account.Deposit 1000m)
         Observable.Return(Account.Withdraw 500m) |])

  let acct =
    Observable.Aggregate(acct_events, Account.empty, Account.update)

  (** val acct : IObservable<Account.t> *)
\$\endgroup\$
6
  • \$\begingroup\$ Thanks. However, the update function has build errors. Can you update the code so that it builds? \$\endgroup\$ Feb 11, 2017 at 10:30
  • \$\begingroup\$ Great answer, but doesn't it just capture the building of state/views. If it is the logic that should handle the commands you also need something that generates new events based on a command and state (or list of events) \$\endgroup\$ Feb 11, 2017 at 11:41
  • \$\begingroup\$ @ScottNimrod hi, sorry about that. Updated with fixes and also to use IObservable instead of stream. \$\endgroup\$
    – Yawar
    Feb 11, 2017 at 20:41
  • \$\begingroup\$ @TomasJansson personally I don't differentiate between 'commands' and 'events'; ultimately your updater function has to take some message type as one of its inputs and handle it. I think it comes to the same thing whether we call that a 'command' or an 'event'. Also MSDN doesn't seem to make a distinction either msdn.microsoft.com/en-us/library/dn589792.aspx . As to the question of 'How do the events/commands get generated?', they are probably coming from some user-facing app, or from a sensor. \$\endgroup\$
    – Yawar
    Feb 11, 2017 at 20:45
  • \$\begingroup\$ I wouldn't use msdn as a reference in this topic, there are many more better sources out there. With that said, you should definitely differentiate between command and event. The have totally different semantic meanings. A command is an action, that is, you want to do something. An event is a fact, that is, it has already happened. \$\endgroup\$ Feb 12, 2017 at 21:36
2
\$\begingroup\$

I updated my code based on code review feedback.

My solution has been updated to the following:

module EventSourcing =

    type StateResponse<'state, 'event> = ('state * 'event) -> 'state

module Account =

    open EventSourcing

    type AccountId = AccountId of string
    type FirstName = FirstName of string
    type LastName =  LastName  of string

    type Credentials =   { UserId:string; Password:string }
    type Account = {
         AccountId: AccountId
         FirstName: FirstName
         LastName:  LastName
         Balance:   decimal }

    and Event = 
        | LoggedIn  of Credentials
        | Deposited of balance: decimal
        | Withdrew  of balance: decimal

    (*Functions*)
    let emptyAccount = {
         AccountId= AccountId ""
         FirstName= FirstName ""
         LastName=  LastName  ""
         Balance=   0.00m }

    let getAccount credentials = {
        AccountId= AccountId "myAccountId"
        FirstName= FirstName "Scott"
        LastName= LastName   "Nimrod"
        Balance=  0.00m                    }

    let update : StateResponse<Account,Event> = function
        | account , LoggedIn  credentials ->   getAccount credentials
        | account , Deposited balance     -> { account with Balance = account.Balance + balance }
        | account , Withdrew  balance     -> { account with Balance = account.Balance - balance }

    (*Client*)
    let credentials = { UserId="Bizmonger"; Password="MyPassword" }
    let account =       credentials |> getAccount 
    let events =
        [
            LoggedIn  credentials
            Deposited 1000m
            Deposited 5000m
            Withdrew  500m
        ]

    let nextUpdate account event = update (account , event)
    let hydratedAccount          = (emptyAccount , events) 
                                   ||> List.fold nextUpdate
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.