Domain-Driven Design with
F# and EventStore

Lev Gorodinski

gorodinski.com

@eulerfx

Goals

  • Learn F# / Functional Programming
  • Learn EventStore / Event Sourcing
  • Contrast with OOP
Domain-Driven Design Implementation Path

Functional Themes

  • Immutability
  • Purity
  • Composability

The Domain

Inventory tracking system

InventoryItem Aggregate

Behaviors

  • Create
  • Rename
  • Activate
  • Deactivate
  • Check items in
  • Check items out

C#


        public class InventoryItem : AggregateRoot 
        {
          bool _activated;

          public void Deactivate()
          {
              if(!_activated) throw new InvalidOperationException("already deactivated");
              ApplyChange(new InventoryItemDeactivated(_id));
          }

          void Apply(InventoryItemDeactivated e)
          {
            _activated = false;
          }
        }
        
source

      public abstract class AggregateRoot
      {
        public abstract Guid Id { get; }
        public int Version { get; internal set; }

        readonly List<Event> _changes = new List<Event>();        

        protected void ApplyChange(Event e)
        {
            ApplyChange(e, true);
        }

        void ApplyChange(Event e, bool isNew)
        {
            (this as dynamic).Apply((dynamic)e);
            if (isNew) _changes.Add(e);
        }
      }
      

Issues

  • Verbose
  • Not POCO
  • Double dispatch DLR voodoo
  • Conflation of state and identity
  • Broken composition

F#

    
      type State = { 
        isActive : bool; 
      }
      

Record Type

  • Immutable by default
  • No inheritance
  • Structural equality, comparison

Events & Commands

    
      type Command = 
          | Create of name:string
          | Deactivate 

      type Event = 
          | Created of name:string
          | Deactivated
      

Union Type

  • Mutually exclusive, named cases
  • Similar: closed class hiearachy, C union, enum on steroids
  • Pattern matching

      let apply state event = 
        match event with
        | Deactivated -> { state with State.isActive = false; }
      

      let apply state = function
        | Deactivated -> { state with State.isActive = false; }
      

Pattern Matching

  • Switch statement on steroids
  • ...lots of steroids
  • Customizable

      module Assert =
          let inactive item = if item.isActive = true then failwith "The item is already deactivated."

      let exec state = 
        let apply event = 
            let newItem = apply item event
            event

        function
        | Deactivate -> item |> Assert.inactive; Deactivated |> apply
      
source
  • Pipe operator
  • Inner functions
  • Modules

Improvements

  • Brevity
  • Explicitness
  • Uniformity
  • Single responsibility
  • Static verification
  • Cohesion

Benefits for DDD

  • Focus on behavior
  • Declarative
  • Assertive
  • Side-effect free

Challenges

  • New paradigm
  • New syntax
  • Different paradigms collide for attention
  • Less tooling support
  • C# interop can be tricky
  • Missing features: type classes, higher kinds, etc.

Event Sourcing


AHA!


        let apply state = function
          | Deactivated -> { state with State.isActive = false; }
        

Event Sourcing

Capture all changes to an application state as a sequence of events.
Martin Fowler

Capture all changes to an application state behaviors as a sequence of events.

Reconstitution

Replay all past events to bring aggregate into current state.

Aggregate Abstraction


      type Aggregate<'TState, 'TCommand, 'TEvent> = {          
          zero : 'TState;
          apply : 'TState -> 'TEvent -> 'TState;
          exec : 'TState -> 'TCommand -> 'TEvent;
      }
      

Replay


      let events = load (typeof<'TEvent>,id)
      let state = events |> Seq.fold aggregate.apply aggregate.zero        
      
source

EventStore Integration


  let load (typ,id) =
      let slice = conn.ReadStreamEventsForward(id,1,Int32.Max,false)
      slice.Events 
      |> Seq.map (fun e -> decode(typ,e.Event.EventType,e.Event.Data))
      
source

Event Persistance


  let commit (id,expectedVersion) e =
        let eventType,bytes = serialize e
        let meta = [||] : byte array
        let data = EventData(Guid.NewGuid(),eventType,true,bytes,meta)
        if version = 0 then 
          conn.CreateStream(id, Guid.NewGuid(), true, metaData)
        conn.AppendToStream(id,expectedVersion,data)
      
source

Projections

  • Project stream onto new stream
  • State
  • Declared with JavaScript
  • Simple read-models
  • CEP
  • Indexing

Read Model Projection


fromCategory('InventoryItem').foreachStream().when({
  $init: function() {
    return { name: null, active: false };
  },
  "Created": function (s, e) {
    s.name = e.body.value;
    s.active = true;
    var streamId = "ReadModel-" + 
          e.streamId.replace("InventoryItem-", "");
    var eventType = e.eventType + "_ReadModel";
    emit(streamId, eventType, s);
  },
  "Deactivated": function (s, e) {
    s.active = false;
    var streamId = "ReadModel-" + 
          e.streamId.replace("InventoryItem-", "");
    var eventType = e.eventType + "_ReadModel";
    emit(streamId, eventType, s);
  }
});
        
source

Challenges

  • Less tooling support
  • Non-trivial shift in data modelling
  • Queries must be implemented separately
  • Eventual consistency
  • Lack of referential integrity
  • Schema maintenance, refactoring

Why F# / Functional Programming?

  • .NET, VisualStudio
  • Multi-paradigm
  • Powerful type inference
  • Language oriented
  • Better coupling characteristics
  • Smaller, more numerous types
  • Function as unit of coupling
  • More explicit, declarative
  • Pattern matching
  • Avoid nulls
  • Isolate, minimize state
  • OOP patterns taken care of
  • Draw on math research

Why EventStore / event sourcing?

  • Uniform / Simple
  • Immutable
  • Append only
  • Scalable
  • Natural model of reality

Lev Gorodinski

gorodinski.com

@eulerfx

github.com/eulerfx

eulerfx@gmail.com