有时我无法确定事件应该代表什么。例如,以下是银行帐户的简化Ledger
:
Ledger {
date : Date;
amount : Int;
cleared : String;
}
用户通过输入参考文本来清除分类帐。或者用户可以通过将文本设置为空字符串来删除清除。
我的问题是,在跟踪事件源的变化时,我应该为用户打算做什么创建事件,例如:
Event clearLedger(clearText : String)
Event removeClearing()
或者我应该为幕后发生的事情制作一个更通用的事件,这在两种情况下都有效:
Event updateLedger(clearText : String)
这可以一直到达一个非常基本的CRUD级别,最终会在数据库的事务日志级别结束,所以这里有指导吗?
答案 0 :(得分:2)
马匹课程。
我的问题是,在跟踪事件来源的变化时,我应该为用户打算做什么创建事件
可能。在实体边界内,它并不重要;但是从外部看历史,能够识别事件变化的背景可能非常有用。想想吧/子;一旦你有一个实体写作事件,你可能想要开始订阅这些事件。
例如,考虑更改客户资料中的地址。这可能是对早期数据输入中的拼写错误的纠正(业务跟踪错误率,寻找可纠正的系统性问题以改善客户体验),或者是客户的重新安置(在这种情况下,我们希望向他们的新客户发送欢迎套件)地址;或启动审核以根据现有政策审核新地址。
如果所有内容都存在于单个AddressChanged事件的保护之下,则会失去灵活性。最好的情况是,您发现自己试图仅从数据中猜测变化的背景。
另一方面,如果这种灵活性没有带来任何价值,那么你就不需要了。
也就是说,事件采购CRUD很奇怪 - 如果你不把这些变化看作是头等公民,为什么事件来源实体呢?写出聚合状态要简单得多。
写出事件和写出聚合状态有什么区别?
不是很多;阅读它们会有点不同。
不那么隐秘:写出聚合状态类似于现在写出聚合的样子。通常,这由持久性组件完成,该组件将当前状态序列化为DTO。例如,我们可以使用JSON文档表示Ledger
的当前状态
{ "date" : "2016-07-06"
, "amount" : 40
, "cleared" : null
}
要稍后重新创建分类帐,我们只需从永久存储中获取json文档,然后让对象映射器继续工作。
写出历史,即事件,看起来更像是
[ { "event_type" : "LedgerCreated"
, "data"
: { "date" : "2016-07-06"
, "amount" : 40
}
}
, { "event_type" : "LedgerCleared"
, "data"
: { "reason" : "Because I said so"
}
}
, { "event_type" : "ClearingRemoved"
, "data"
: {}
}
]
要稍后重新创建分类帐,我们需要将json文档从永久存储中取出,然后使用对象映射器创建有序的事件序列,在其初始“种子”状态下创建Ledger,然后< em>将这些事件重新应用到我们的新Ledger实体,通过重播其历史记录中的每个更改,有效地发现Ledger现在的样子。
答案 1 :(得分:1)
无论商业专家使用什么语言,我都会。如果他们使用UpdateLedger,那么使用它。如果他们使用LedgerCleared那就使用那个。
答案 2 :(得分:1)
事件代表了一些业务运营,它们本身具有重要价值,可供查看和分析。例如,您可能希望查看清除分类帐清除的次数。如果这是你想要的,这就是你的领域专家所说的 - 做到这一点。如果你想做CRUD,就像@VoiceOnUnreason写的那样,请不要使用事件源CRUD。从本质上讲,您可能不仅仅是在谈论域事件。例如,您的汇总方法clearLedger
和removeClearance
将生成哪种域事件。如果两个不同的业务操作产生相同的事件时间,那将是奇怪的。
您的候选人UpdateLedger
会生成通用LedgerUpdated
。然后几周之后,你将另一个操作添加到UpdateLedger
中,它将变成一个庞大的膨胀方法,其中包含许多参数,这些参数正在检查nulls / empties和许多if语句。这可能是每个基本DDD讲座/书籍/演示文稿的第一个例子,作为糟糕设计的一个例子,首先做DDD的原因......
关于这个问题有两个简单的规则:
Create
,Update
和Delete
,则会产生CRUD气味。如果您正在做的是DDD if
语句,它检查参数并决定如何更改聚合状态或调用哪种聚合方法,这是一种没有足够的粒度,交叉关注和违规的气味单一责任原则