当仍然存在无用事件或撤消事件的可能性时,您是否立即将事件应用于域模型?
Do you apply events to the domain model immediately when there is still the possibility of useless events or undo?
我看到的每个事件源示例都是针对网络的。它似乎特别适合 MVC 架构,其中客户端的视图不是 运行 域代码并且 interactivity 是有限的。我不完全确定如何推断出丰富的桌面应用程序,用户可能正在其中编辑列表或执行其他一些较长的 运行 任务。
领域模型是 persistence-agnostic 和 presentation-agnostic,并且只能通过将领域事件应用于聚合根来改变。我的具体问题是 当用户进行未提交的更改时,表示代码是否应该改变域模型?
如果表示代码不改变域模型,您如何执行域逻辑?当用户编辑时,即时域验证和域计算冒泡到表示模型会很好。否则,您必须在视图模型中复制 non-trivial 逻辑。
如果表示代码确实改变了域模型,您如何实现撤消?没有域取消删除事件,因为撤消的概念只存在于未提交的编辑 session 中,我不愿意为每个事件添加撤消版本。更糟糕的是,我需要撤消事件的能力 out-of-order。您是否只是删除事件并在每次撤消时重播所有内容? (例如,如果文本字段 returns 恢复到其先前状态,也会发生撤消。)
如果表示代码确实改变了域模型,是坚持用户执行的每个事件更好还是只将用户的 activity 压缩为尽可能简单的事件集?举个简单的例子,想象一遍又一遍地改变评论字段
在保存之前结束。你真的会坚持每一个中间
CommentChangedEvent 在同一编辑期间同一字段
session?或者对于更复杂的示例,用户将是
更改参数,运行 优化计算,调整
参数,重新运行计算等,直到这个用户
对最近的结果感到满意并提交更改。我
认为没有人会考虑所有中间事件的价值
存储。你如何保持这个浓缩?
存在复杂的协作域逻辑,这让我认为 DDD/ES 是可行的方法。我需要一张富客户端视图模型和域模型如何交互的图片,我希望简单和优雅。
虽然让存储库管理事务,但我最终还是做了这样的事情。
基本上我的仓库都实现了
public interface IEntityRepository<TEntityType, TEventType>{
TEntityType ApplyEvents(IEnumerable<TEventType> events);
Task Commit();
Task Cancel();
}
因此,虽然 ApplyEvents 将更新并 return 实体,但我还在内部保留了起始版本,直到调用 Commit 为止。如果调用取消,我只是将它们交换回来,丢弃事件。
一个非常好的功能是我只在交易完成后将事件推送到网络、数据库等。存储库的实现将依赖于数据库或 Web 服务服务 - 但所有调用代码需要知道的是 Commit 或 Cancel。
编辑
要执行取消操作,您需要将旧实体、更新后的实体和事件存储在内存结构中。像
public class EntityTransaction<TEntityType, TEventType>
{
public TEntityType oldVersion{get;set;}
public TEntityType newVersion{get;set;}
public List<TEventType> events{get;set;}
}
然后你的 ApplyEvents 看起来像,对于用户
private Dictionary<Guid, EntityTransaction<IUser, IUserEvents>> transactions;
public IUser ApplyEvents(IEnumerable<IUserEvent> events)
{
//get the id somehow
var id = GetUserID(events);
if(transactions.ContainsKey(id) == false){
var user = GetByID(id);
transactions.Add(id, new EntityTransaction{
oldVersion = user;
newVersion = user;
events = new List<IUserEvent>()
});
}
var transaction = transactions[id];
foreach(var ev in events){
transaction.newVersion.When(ev);
transaction.events.Add(ev);
}
}
然后在您的取消中,如果您要取消交易,您只需用旧版本替换新版本。
有道理吗?
我认为桌面 DDD 应用程序与 MVC 应用程序没有太大区别,您基本上可以拥有相同的层,只是它们大多不是网络分离的。
CQRS/ES 应用程序与 task-based UI 配合使用效果最佳,您可以在其中发出反映用户意图的命令。但我们所说的任务并不是指用户可以在屏幕上执行的每个操作,它必须在域中具有意义和目的。正如您在 3. 中正确指出的那样,无需将每个微修改建模为完整的 DDD 命令和相关事件。它可能会污染您的事件流。
所以你基本上有两个级别:
UI级动作
这些完全可以在表现层进行管理。它们堆叠起来最终映射到单个命令,但您可以很容易地单独撤消它们。没有什么能阻止您将它们建模为封装 closures for do and undo 的微事件。我从未在任何 UI 中看到 "cherrypickable" 撤消,我也没有真正明白这一点,但这应该是可行的并且用户可以理解,只要操作是可交换的(它们的效果不依赖于执行顺序)。
领域级任务
粗粒度activity 由命令和相应的事件表示。如果你需要撤消这些,我宁愿将一个新的回滚事件附加到事件流中,也不愿尝试删除现有的回滚事件("don't change the past").
反映UI
中的域不变量和计算
这是您真正需要正确区分两种类型任务的地方,因为 UI 操作通常不会更新屏幕上的任何内容,除了一些基本验证(必填字段、字符串和数字格式等)另一方面,发出命令会刷新模型视图,但您可能必须通过某些确认按钮来具体化操作。
如果您的 UI 主要是向用户显示计算的数字和预测,这可能是个问题。您可以将计算放在由 UI 调用的单独服务中,然后在用户保存时使用所有更新的计算值发出修改命令。或者,您可以只提交一个带有您更改的参数的命令,并让域调用相同的计算服务。两者实际上更接近 CRUD,并且可能会通过 IMO 导致贫血域模型。
我看到的每个事件源示例都是针对网络的。它似乎特别适合 MVC 架构,其中客户端的视图不是 运行 域代码并且 interactivity 是有限的。我不完全确定如何推断出丰富的桌面应用程序,用户可能正在其中编辑列表或执行其他一些较长的 运行 任务。
领域模型是 persistence-agnostic 和 presentation-agnostic,并且只能通过将领域事件应用于聚合根来改变。我的具体问题是 当用户进行未提交的更改时,表示代码是否应该改变域模型?
如果表示代码不改变域模型,您如何执行域逻辑?当用户编辑时,即时域验证和域计算冒泡到表示模型会很好。否则,您必须在视图模型中复制 non-trivial 逻辑。
如果表示代码确实改变了域模型,您如何实现撤消?没有域取消删除事件,因为撤消的概念只存在于未提交的编辑 session 中,我不愿意为每个事件添加撤消版本。更糟糕的是,我需要撤消事件的能力 out-of-order。您是否只是删除事件并在每次撤消时重播所有内容? (例如,如果文本字段 returns 恢复到其先前状态,也会发生撤消。)
如果表示代码确实改变了域模型,是坚持用户执行的每个事件更好还是只将用户的 activity 压缩为尽可能简单的事件集?举个简单的例子,想象一遍又一遍地改变评论字段 在保存之前结束。你真的会坚持每一个中间 CommentChangedEvent 在同一编辑期间同一字段 session?或者对于更复杂的示例,用户将是 更改参数,运行 优化计算,调整 参数,重新运行计算等,直到这个用户 对最近的结果感到满意并提交更改。我 认为没有人会考虑所有中间事件的价值 存储。你如何保持这个浓缩?
存在复杂的协作域逻辑,这让我认为 DDD/ES 是可行的方法。我需要一张富客户端视图模型和域模型如何交互的图片,我希望简单和优雅。
虽然让存储库管理事务,但我最终还是做了这样的事情。
基本上我的仓库都实现了
public interface IEntityRepository<TEntityType, TEventType>{
TEntityType ApplyEvents(IEnumerable<TEventType> events);
Task Commit();
Task Cancel();
}
因此,虽然 ApplyEvents 将更新并 return 实体,但我还在内部保留了起始版本,直到调用 Commit 为止。如果调用取消,我只是将它们交换回来,丢弃事件。
一个非常好的功能是我只在交易完成后将事件推送到网络、数据库等。存储库的实现将依赖于数据库或 Web 服务服务 - 但所有调用代码需要知道的是 Commit 或 Cancel。
编辑 要执行取消操作,您需要将旧实体、更新后的实体和事件存储在内存结构中。像
public class EntityTransaction<TEntityType, TEventType>
{
public TEntityType oldVersion{get;set;}
public TEntityType newVersion{get;set;}
public List<TEventType> events{get;set;}
}
然后你的 ApplyEvents 看起来像,对于用户
private Dictionary<Guid, EntityTransaction<IUser, IUserEvents>> transactions;
public IUser ApplyEvents(IEnumerable<IUserEvent> events)
{
//get the id somehow
var id = GetUserID(events);
if(transactions.ContainsKey(id) == false){
var user = GetByID(id);
transactions.Add(id, new EntityTransaction{
oldVersion = user;
newVersion = user;
events = new List<IUserEvent>()
});
}
var transaction = transactions[id];
foreach(var ev in events){
transaction.newVersion.When(ev);
transaction.events.Add(ev);
}
}
然后在您的取消中,如果您要取消交易,您只需用旧版本替换新版本。
有道理吗?
我认为桌面 DDD 应用程序与 MVC 应用程序没有太大区别,您基本上可以拥有相同的层,只是它们大多不是网络分离的。
CQRS/ES 应用程序与 task-based UI 配合使用效果最佳,您可以在其中发出反映用户意图的命令。但我们所说的任务并不是指用户可以在屏幕上执行的每个操作,它必须在域中具有意义和目的。正如您在 3. 中正确指出的那样,无需将每个微修改建模为完整的 DDD 命令和相关事件。它可能会污染您的事件流。
所以你基本上有两个级别:
UI级动作
这些完全可以在表现层进行管理。它们堆叠起来最终映射到单个命令,但您可以很容易地单独撤消它们。没有什么能阻止您将它们建模为封装 closures for do and undo 的微事件。我从未在任何 UI 中看到 "cherrypickable" 撤消,我也没有真正明白这一点,但这应该是可行的并且用户可以理解,只要操作是可交换的(它们的效果不依赖于执行顺序)。
领域级任务
粗粒度activity 由命令和相应的事件表示。如果你需要撤消这些,我宁愿将一个新的回滚事件附加到事件流中,也不愿尝试删除现有的回滚事件("don't change the past").
反映UI
中的域不变量和计算这是您真正需要正确区分两种类型任务的地方,因为 UI 操作通常不会更新屏幕上的任何内容,除了一些基本验证(必填字段、字符串和数字格式等)另一方面,发出命令会刷新模型视图,但您可能必须通过某些确认按钮来具体化操作。
如果您的 UI 主要是向用户显示计算的数字和预测,这可能是个问题。您可以将计算放在由 UI 调用的单独服务中,然后在用户保存时使用所有更新的计算值发出修改命令。或者,您可以只提交一个带有您更改的参数的命令,并让域调用相同的计算服务。两者实际上更接近 CRUD,并且可能会通过 IMO 导致贫血域模型。