用 F#和EventStore实现领域驱动设计:Domain-Driven Design with F# and EventStore - Lev Gorodinski
废话少说,直接上代码,其库存品种领域模型代码InventoryItem如下,注意是F函数语言:
/// An inventory item. [<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
|
从上面代码看出,应该是Command激活一个Event,Event改变状态。因为使用了函数语言特点,将Command Event和State比C更好地封装。
EventStore.fs代码如下:
/// Integration with EventStore. [<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
|
应该是将InventoryItem.Event进行保存持久化,以便下次回放追溯,实现Event Sourcing。
Aggregate.fs是实现聚合状态的持久化统一接口。非常类似状态模式,包括切换状态。
/// Aggregate framework. [<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)
|
整个项目源码下载
[该贴被banq于2013-02-19 18:32修改过]