用 F#和EventStore實現DDD領域驅動設計

banq發表於2013-02-19
用 F#和EventStore實現領域驅動設計:Domain-Driven Design with F# and EventStore - Lev Gorodinski

廢話少說,直接上程式碼,其庫存品種領域模型程式碼InventoryItem如下,注意是F函式語言


/// An inventory item.
<p class="indent">[<RequireQualifiedAccess>]
module InventoryItem
 
/// Represents the state of an inventory item.
type State = {
    isActive : bool;
}
with static member Zero = { isActive = false }
 
/// An inventory item command.
type Command = 
    | Create of System.Guid * string
    | Deactivate 
    | Rename of string
    | CheckInItems of int
    | RemoveItems of int
 
/// An inventory item event.
type Event = 
    | Created of string
    | Deactivated
    | Renamed of string
    | ItemsCheckedIn of int
    | ItemsRemoved of int
 
/// Applies a inventory item event to a state.
let apply item = function
    | Created _ -> { item with State.isActive = true; }
    | Deactivated _ -> { item with State.isActive = false; }
    | Renamed _ -> item
    | ItemsCheckedIn _ -> item
    | ItemsRemoved _ -> item
 
/// Assertions used to maintain invariants upon command execution.
module private Assert =
    let validName name = if System.String.IsNullOrEmpty(name) then invalidArg "name" "The name must not be null."
    let validCount count = if count <= 0 then invalidArg "count" "Inventory count must be positive."    
    let inactive item = if item.isActive = true then failwith "The item is already deactivated."
 
/// Executes an inventory item command.
let exec item = 
 
    let apply event = 
        let newItem = apply item event
        event
 
    function
 
    | Create(id, name) -> 
        Created(name) |> apply
        
    | Deactivate -> 
        item |> Assert.inactive
        Deactivated |> apply
        
    | Rename(name) -> 
        name |> Assert.validName
        Renamed(name) |> apply
        
    | CheckInItems(count) -> 
        count |> Assert.validCount
        ItemsCheckedIn(count) |> apply
        
    | RemoveItems(count) ->
        count |> Assert.validCount
        ItemsRemoved(count) |> apply
<p class="indent">


從上面程式碼看出,應該是Command啟用一個Event,Event改變狀態。因為使用了函式語言特點,將Command Event和State比C更好地封裝

EventStore.fs程式碼如下:


/// Integration with EventStore.
<p class="indent">[<RequireQualifiedAccess>]
module EventStore

open System
open System.Net
open EventStore.ClientAPI

/// Creates and opens an EventStore connection.
let conn () = 
    let conn = EventStoreConnection.Create() 
    conn.Connect(IPEndPoint(IPAddress.Parse("127.0.0.1"), 1113))
    conn

/// Creates an event store functions with an InventoryItem-specific serializer.
let make (conn:EventStoreConnection) (serialize:InventoryItem.Event -> string * byte array, deserialize: string * byte array -> InventoryItem.Event) =

    let streamId id = "InventoryItem-" + id.ToString().ToLower()

    let load id =
        let streamId = streamId id
        let eventsSlice = conn.ReadStreamEventsForward(streamId, 1, Int32.MaxValue, false)
        eventsSlice.Events 
        |> Seq.map (fun e -> deserialize(e.Event.EventType, e.Event.Data))

    let commit (id,expectedVersion) (e:InventoryItem.Event) =
        let streamId = streamId id
        let eventType,data = serialize(e)
        let metaData = [||] : byte array
        let eventData = new EventData(Guid.NewGuid(), eventType, true, data, metaData)
        if expectedVersion = 0 
            then conn.CreateStream(streamId, Guid.NewGuid(), true, metaData)
        conn.AppendToStream(streamId, expectedVersion, eventData)

    load,commit
<p class="indent">


應該是將InventoryItem.Event進行儲存持久化,以便下次回放追溯,實現Event Sourcing。

Aggregate.fs是實現聚合狀態的持久化統一介面。非常類似狀態模式,包括切換狀態。

/// Aggregate framework.
<p class="indent">[<RequireQualifiedAccess>]
module Aggregate
 
/// Represents an aggregate.
type Aggregate<'TState, 'TCommand, 'TEvent> = {
    
    /// An initial state value.
    zero : 'TState;
 
    /// Applies an event to a state returning a new state.
    apply : 'TState -> 'TEvent -> 'TState;
 
    /// Executes a command on a state yielding an event.
    exec : 'TState -> 'TCommand -> 'TEvent;
}
 
type Id = System.Guid
 
/// Creates a persistent command handler for an aggregate.
let makeHandler (aggregate:Aggregate<'TState, 'TCommand, 'TEvent>) (load:Id -> 'TEvent seq, commit:Id * int -> 'TEvent -> unit) =
    fun (id,version) command ->
        let state = load id |> Seq.fold aggregate.apply aggregate.zero
        let event = aggregate.exec state command
        event |> commit (id,version)
<p class="indent">


整個專案原始碼下載

[該貼被banq於2013-02-19 18:32修改過]

相關文章