Domain driven design 当仍然存在无用事件或撤消的可能性时,是否立即将事件应用于域模型?

Domain driven design 当仍然存在无用事件或撤消的可能性时,是否立即将事件应用于域模型?,domain-driven-design,desktop-application,event-sourcing,Domain Driven Design,Desktop Application,Event Sourcing,我看到的每一个事件来源的例子都是针对网络的。在MVC体系结构中,客户端的视图没有运行域代码,交互性受到限制。我不完全确定如何推断富桌面应用程序,用户可能正在编辑列表或执行其他长时间运行的任务 域模型与持久性无关,与表示无关,只能通过将域事件应用于聚合根来进行变异。我的具体问题是当用户进行未提交的更改时,表示代码是否应该改变域模型? 如果表示代码没有改变域模型,您如何实施域逻辑?当用户进行编辑时,立即进行域验证和域计算,并将其冒泡到表示模型中,这将是一件好事。否则,必须在视图模型中复制非平凡逻辑

我看到的每一个事件来源的例子都是针对网络的。在MVC体系结构中,客户端的视图没有运行域代码,交互性受到限制。我不完全确定如何推断富桌面应用程序,用户可能正在编辑列表或执行其他长时间运行的任务

域模型与持久性无关,与表示无关,只能通过将域事件应用于聚合根来进行变异。我的具体问题是当用户进行未提交的更改时,表示代码是否应该改变域模型?

  • 如果表示代码没有改变域模型,您如何实施域逻辑?当用户进行编辑时,立即进行域验证和域计算,并将其冒泡到表示模型中,这将是一件好事。否则,必须在视图模型中复制非平凡逻辑

  • 如果表示代码确实改变了域模型,那么如何实现撤消?没有域撤消删除事件,因为撤消的概念只存在于未受限制的编辑会话中,我不愿意为每个事件添加撤消版本。更糟糕的是,我需要撤销无序事件的能力。您是否只是删除事件并在每次撤消时重播所有内容?(例如,如果文本字段返回到其以前的状态,也会发生撤消。)

  • 如果表示代码确实改变了域模型,那么最好是持久化用户执行的每个事件,还是将用户的活动浓缩为可能的最简单的事件集?举个简单的例子,想象一下反复更改注释字段 在保存之前请先保存。你真的会坚持每一次吗 在同一编辑过程中,同一字段上的CommentChangedEvent 一场或者对于更复杂的示例,用户将 更改参数、运行优化计算、调整 参数,重新运行计算等,直到该用户 对最近的结果感到满意并提交更改。我 不要认为任何人都会考虑所有的中间事件 储存。你如何保持这种浓缩


  • 有一个复杂的协作域逻辑,这让我认为DDD/ES是一条出路。我需要一张富客户端视图模型和域模型如何交互的图片,我希望简单和优雅

    虽然让存储库管理事务,但我最终还是做了类似的事情

    基本上我的存储库都实现了

    public interface IEntityRepository<TEntityType, TEventType>{
        TEntityType ApplyEvents(IEnumerable<TEventType> events);
        Task Commit();
        Task Cancel();
    }
    
    公共接口客户端存储库{
    TEntityType ApplyEvents(IEnumerable事件);
    任务提交();
    任务取消();
    }
    
    因此,虽然ApplyEvents将更新并返回实体,但我也在内部保留起始版本,直到调用Commit。如果调用了Cancel,我只是将它们调回,丢弃事件

    其中一个非常好的特性是,一旦事务完成,我只将事件推送到web、DB等。存储库的实现将依赖于数据库或web服务,但调用代码需要知道的只是提交或取消

    编辑 要执行取消操作,请将旧实体、更新实体和事件存储在内存结构中。差不多

     public class EntityTransaction<TEntityType, TEventType>
    { 
        public TEntityType oldVersion{get;set;}
        public TEntityType newVersion{get;set;}
        public List<TEventType> events{get;set;}
    }
    
    公共类EntityTransaction
    { 
    公共TEntityType oldVersion{get;set;}
    公共TEntityType新版本{get;set;}
    公共列表事件{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);
        }
    }
    
    私有字典事务;
    公共IUser应用程序事件(IEnumerable事件)
    {
    //不知怎么弄到身份证了
    var id=GetUserID(事件);
    if(transactions.ContainsKey(id)==false){
    var user=GetByID(id);
    事务。添加(id,新实体事务{
    oldVersion=用户;
    新版本=用户;
    事件=新列表()
    });
    }
    var事务=事务[id];
    foreach(事件中的var ev){
    交易。新版本。何时(ev);
    事务.events.Add(ev);
    }
    }
    
    然后在“取消”中,如果要取消事务,只需用旧版本替换新版本


    有意义吗?

    我看不出桌面DDD应用程序与MVC应用程序有多大的不同,基本上可以有相同的层,只是它们大部分不是网络分离的

    CQRS/ES应用程序在您发出反映用户意图的命令的情况下工作得最好。但我们所说的任务并不是指用户可以在屏幕上采取的每一个行动,它必须在领域中具有意义和目的。正如您在3中正确指出的,无需将每个微修改建模为一个完整的DDD命令和相关事件。它可能会污染您的事件流

    所以你基本上有两个层次:

    UI级操作

    这些可以完全在表示层中进行管理。它们叠加起来最终映射到单个命令,但您可以很容易地逐个撤消它们。没有什么可以阻止您将它们建模为封装的微事件。我从来没有在任何UI中看到过“cherrypickable”撤销,也没有真正看到这一点,但只要操作是可交换的(它们的效果不依赖于执行顺序),这应该是可行的,用户可以理解的

    域级任务

    由命令和相应事件表示的粗粒度活动。如果需要撤消这些操作,我宁愿向事件流附加一个新的回滚事件,也不愿尝试删除一个现有事件(“不要更改过去”)

    在UI中反映域不变量和计算

    这是您真正需要正确区分这两种任务的地方,因为UI操作通常不会更新屏幕上的任何内容,只会更新一些任务