用 F#和EventStore实现DDD领域驱动设计

13-02-19 banq

用 F#和EventStore实现领域驱动设计:Domain-Driven Design with F# and EventStore - Lev Gorodinski

废话少说,直接上代码,其库存品种领域模型代码InventoryItem如下,注意是F#函数语言:

/// An inventory item.
<p>[<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>

从上面代码看出,应该是Command激活一个Event,Event改变状态。因为使用了函数语言特点,将Command Event和State比C#更好地封装。

EventStore.fs代码如下:

/// Integration with EventStore.
<p>[<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>

应该是将InventoryItem.Event进行保存持久化,以便下次回放追溯,实现Event Sourcing。

Aggregate.fs是实现聚合状态的持久化统一接口。非常类似状态模式,包括切换状态。

/// Aggregate framework.
<p>[<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>

整个项目源码下载

[该贴被banq于2013-02-19 18:32修改过]

              

2
banq
2013-02-20 10:16

之前以为:这个DDD实践项目的一个亮点是其聚合框架,aggregate framework,类似传统ORM/JPA等框架,如Hibernate,也去除了传统DDD的仓储概念,类似聚合的生产工厂+仓储组合。

仔细再看了一下代码特别是测试代码:

let handleCommand = 
    Aggregate.makeHandler 
        { zero = InventoryItem.State.Zero; apply = InventoryItem.apply; exec = InventoryItem.exec }
        (EventStore.make conn Serialization.serializer)
<p>

显然是从聚合框架获得一个操作句柄,这个聚合框架应该是一个命令句柄工厂,更像一个Context。

[该贴被banq于2013-02-20 20:47修改过]