域对象中带有 Autofac 的 IMediatr DDD

如何解决域对象中带有 Autofac 的 IMediatr DDD

我已将域模型对象设置为独立于任何服务和基础架构逻辑。 我也在使用领域事件来应对领域模型中的一些变化。

现在我的问题是如何从域模型对象本身引发这些事件。 目前,我正在为此使用 Udi Dahan 的 DomainEvents 静态类(我需要在它们发生时准确处理事件,而不是在以后的时间)。 这些事件用于许多事情,例如记录日志、更新相关服务和其他领域模型对象和数据库中的数据、将消息发布到 MassTransit 总线等。

DomainEvents 静态类使用我在某个点注入的 Autofac 作用域来查找 IMediatr 实例并发布事件,如下所示:

public static class DomainEvents
{
    private static ILifetimeScope Scope;

    public async static Task RaiseAsync<TDomainEvent>(TDomainEvent @event) where TDomainEvent : IDomainEvent
    {
        var mediator = Scope?.Resolve<IMediatorBus>();
        if (mediator != null)
        {
            await mediator!.Publish(@event).ConfigureAwait(false);
        }
        else
        {
            Debug.WriteLine("Mediator not set for DomainEvents!");
        }
    }

    public static void SetScope(ILifetimeScope scope)
    {
        Scope = scope;
    }
}

这一切在单线程环境中都可以正常工作,但是 DomainEvents.SetScope() 方法在多线程环境中可能存在竞争问题。 IE。当我引入 MassTransit 并创建消息消费者时,每个消息消费者都会通过该方法将当前 LifetimeScope 设置为 DomainEvents,而问题是,每个消费者都会用新的生命周期范围覆盖。

为什么我使用 DomainEvents 静态类?因为我不想用基础设施的东西污染我的领域模型对象。 我想过使 DomainEvents 非静态(定义一个接口),但后来我需要将它们注入到每个域模型对象中,我仍在考虑这一点,但也许有更好的方法。

我想知道是否有更好的方法来处理这个问题? 也许 DomainEvents 类有一些变化?或者可能删除 DomainEvents 静态类,最终使用接口或 DomainService 来执行此操作。 问题是我不喜欢静态类,但我也不喜欢将非特定于域的依赖项推送到域模型对象中。

请帮忙。

更新

为了更好地阐明流程以及我使用 DomainEvents 的目的... 我有一个长时间运行的过程,可能需要几分钟到几小时/几天才能完成。 所以这个过程是这样的:

  1. 我收到来自 MassTransit 的消息,即 ProcessStartMessage(processId)
  2. 从数据库中获取 (processId) 的 ProcessData。
  3. 构建内存域模型ProcessTracker(单例)并将我从DB加载的所有数据放入其中。 (内存缓存)
  4. 我收到了来自 Masstransit 的另一条消息,即。 ProcessStatusChanged(processId,data)。
  5. 将此消息数据转发到内存中的单例 ProcessTracker 进行处理。
  6. ProcessTracker 处理数据。

为了 ProcessTracker 能够处理这些数据,它实例化了许多领域模型对象,每个对象负责处理数据的某些部分。 (注意没有更多的 db 调用和来自 db 的实体水化,这一切都发生在内存中,域模型也没有映射到任何实体,它没有连接到任何 db 对象)。 在某些时候,我需要记录链中的域模型对象已完成的操作、它的工作是否完成或开始、已达到某个里程碑等。这是通过引发 DomainEvents 来完成的。我还需要将这些事件通知 GUI,因此它们也用于发送 Masstransit 消息。

即(伪代码):

public class ProcessTracker 
{
    private Step _currentStep;
    
    public void ProcessData(data)
    {
        _currentStep.ProcessData(data);
        DomainEvents.Raise(new ProcesTrackerDataProcessed());
        ...
    }
 }

 public class Step 
 {
    public Phase _currentPhase;
  
    public void ProcessData(data)
    {
        if (data.IsManual && _someOtherCondition())
        {
            DomainEvents.Raise(new StepDataEvent1());
            ...
        }

        if(data.CanTransition)
        {
            DomainEvents.Raise(new TransitionToNewPhase(this,data));
        }

        _currentPhase.DoSomeWork(data);
        DomainEvents.Raise(new StepDataProcessed(this,data));
        ...
    }
 }

关于数据库更新,那些不是事务性的,对进程不重要,域模型对象状态只保存在内存中,如果进程崩溃,进程必须从头开始(没有恢复)。

结束进程:

  1. 我从大众运输系统收到 ProcessEnd
  2. 消息数据被转发到ProcessTracker
  3. ProcessTracker 处理数据并将其作为过程的结果
  4. 处理结果保存到db
  5. 向流程中的其他方发送一条消息,通知他们流程已完成。

解决方法

首先问问自己,当您从域模型中引发事件时,您打算做什么?

通常它是这样工作的:

  • 获取命令
  • 从存储库加载域对象
  • 执行行为
  • (这里可能)发起一个活动
  • 保持新的域对象状态

那么,您的额外域事件处理程序适合放在哪里?您是否要执行其他一些数据库调用,发送电子邮件?请记住,这一切都现在发生,当您甚至还没有持久化域对象的更改状态时。如果你的坚持失败了怎么办?它会在 执行完所有域处理程序之后发生。

当您处理单个命令时,您不应执行多个事务。 Aggregate 模式清楚地告诉您聚合是事务边界。您应该在完成事务之后引发域事件,或者在同一技术事务中,但它应该只保留聚合状态事件.域事件反应可能会触发其他域对象的事务,并且应该在处理当前命令的范围之外完成。

这个问题根本不是技术问题,而是设计问题。

如果您使用 MassTransit,则只有在消息使用者中处理命令时,才能使其(相对)可靠。然后,您可以使用 in-memory outbox,除非消费者成功,否则它不会发送事件。仍然不能保证在代理失败的情况下会发布事件。

除非您使用 Event Sourcing,否则您有两个 100% 可靠的选择:

  • 使用事务发件箱模式(NServiceBus 有一个,而且非常复杂)。它对您使用的数据库类型有限制。
  • 将事件存储到与域对象相同的数据库中,在不同的表中,在同一个事务中。使用 DELETE INTO 轮询表并从那里向代理发送事件。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-