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

用 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修改过]

之前以为:这个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)

显然是从聚合框架获得一个操作句柄,这个聚合框架应该是一个命令句柄工厂,更像一个Context。
[该贴被banq于2013-02-20 20:47修改过]