java - 调用其他有界上下文的策略

标签 java events asynchronous domain-driven-design eai

我目前正在研究一个涉及域驱动设计(DDD)和多个域集成方案的项目。

我在一个受限环境中有一个用例,需要联系另一位BC来验证聚合。实际上,将来可能会有多个BC要求验证数据(但现在没有)。

现在,我患有DDD强迫症神经衰弱,无法找到正确应用模式的方法(笑)。我非常感谢人们对此的一些反馈。


关于2个有界上下文。
-发生用例的第一个(BC_A)将包含与用户相关的元素列表。
-外部(BC_B)对这些元素有一些了解

*因此,从BC_A到BC_B的验证请求将要求审查BC_A中汇总的所有元素,并将返回包含有关如何处理这些元素的规范的报告(如果我们保留或不保留它,以及为什么) 。
*聚合的状态将通过(例如)“草稿”传递,然后在请求后进行“验证”,然后根据发回的报告,如果有一个,则为“有效”或“has_error”。如果用户以后选择不遵循规范,则可能会将聚合的状态传递给“受控”,这意味着存在一些错误,但我们不会对此进行处理。

该命令为 ValidateMyAggregateCommand

用例是:

  • 通过id获得目标聚集
  • 将其状态更改为“正在验证”
  • 坚持合计
  • 进行验证调用(到另一个BC)
  • 保留验证报告
  • 确认带有目标汇总的验证报告(它将根据结果再次更改其状态,应为“OK”或“HAS_ERROR”)
  • 再次坚持聚合
  • 根据验证结果
  • 生成域事件

    它包含8个步骤,可能从1到3个事务或更多。

    我需要持久保存验证报告的本地设置(以便在用户界面中访问它),我认为我可以做到:

    验证调用之后的
  • 独立(报告是其自身的汇总)
  • 当我持久保留目标聚合时(它会在其中)
    我更喜欢第一个选项(第5步),因为它更加解耦-即使我们可以认为这里有一个不变式(???)-因此报表的持久性与用户的认可之间存在一致性延迟。骨料。

    我实际上在为呼叫本身做苦事(第4步)。

    我想我可以通过几种方式做到这一点:
  • A.使用REST实现的同步RPC调用
  • B.没有响应的调用(无效)(即发即忘),让表上有多个实现选项(同步/异步)
  • C.域事件已转换为技术事件,以达到其他BC

  • A.同步RPC呼叫
    // code_fragment_a
    // = ValidateMyAggregateCommandHandler
    // ---
    myAggregate = myAggregateRepository.find(command.myAggregateId());  // #1
    myAggregate.changeStateTo(VALIDATING);                              // #2
    myAggregateRepository.save(myAggregate);                            // #3
    
    ValidationReport report = validationService.validate(myAggregate);  // #4
    validationReportRepository.save(report);                            // #5
    
    myAggregate.acknowledge(report);                                    // #6
    myAggregateRepository.save(myAggregate);                            // #7
    // ---
    
    validationService是在基础结构层中使用REST服务bean实现的域服务(也可以是本地验证,但在我的方案中不是)。

    调用立即需要响应,并且调用者(命令处理程序)被阻止,直到返回响应为止。因此,它引入了高时间耦合

    万一验证调用由于技术原因而失败,我们将采取例外处理,而则必须回滚所有。该命令将不得不稍后重播。

    B.呼叫无响应(同步或异步)

    在此版本中,命令处理程序将保留聚合的“验证”状态,并将触发(并忘记)验证请求。
    // code_fragment_b0
    // = ValidateMyAggregateCommandHandler
    // ---
    myAggregate = myAggregateRepository.find(command.myAggregateId());  // #1
    myAggregate.changeStateTo(VALIDATING);                              // #2
    myAggregateRepository.save(myAggregate);                            // #3
    
    validationRequestService.requestValidation(myAggregate);            // #4
    // ---
    

    在这里,报告的确认可以以同步或异步的方式发生,即在初始事务内部或外部

    在专用事务中具有上面的代码可以使验证调用中的失败变得无害(如果我们在impl中具有重试机制)。

    该解决方案将允许快速,轻松地从同步通信开始,然后在以后切换到异步。因此它很灵活。

    B.1。同步展示

    在这种情况下,validationRequestService的实现(在基础结构层中)执行直接的请求/响应。
    // code_fragment_b1_a
    // = SynchronousValidationRequestService
    // ---
    private ValidationCaller validationCaller;
    
    public void requestValidation(MyAggregate myAggregate) {
            ValidationReport report = validationCaller.validate(myAggregate);
    
            validationReportRepository.save(report);
            DomainEventPublisher.publish(new ValidationReportReceived(report))
    }
    // ---
    

    该报告将保留在专用事务中,事件的发布将激活第三个代码片段(在应用程序层中),该代码片段将对聚合进行实际的确认工作。
    // code_fragment_b1_b
    // = ValidationReportReceivedEventHandler
    // ---
    public void when(ValidationReportReceived event) {
            MyAggregate myAggregate = myAggregateRepository.find(event.targetAggregateId());
            ValidationReport report = ValidationReportRepository.find(event.reportId());
    
            myAggregate.acknowledge(report);                                        
            myAggregateRepository.save(myAggregate);
    }
    // ---
    

    因此,在这里,我们有一个从基础层到应用程序层的事件。

    B.2。异步

    异步版本将更改ValidationRequestService impl(code_fragment_b1_a)中的先前解决方案。 JMS / AMQP bean的使用将允许首次发送消息,并在以后独立接收响应。

    我猜消息传递侦听器将触发相同的ValidationReportReceived事件,而其余的代码对于code_fragment_b1_b而言将是相同的。

    在撰写本文时,我意识到该解决方案(B2)在交换中表现出更好的对称性,并在技术上有更好的表现,因为它在网络通信方面更加分离和可靠。在这一点上,它并没有引入太多的复杂性。

    C.域事件和BC之间的总线

    最后一个实现,而不是使用域服务来请求其他BC的验证,我将引发一个域事件,例如MyAggregateValidationRequested。我意识到这是一个“强制”域事件,好吧用户请求了它,但它从未真正出现在对话中,但仍然是域事件。

    问题是,我还不知道如何以及在何处放置事件处理程序。基础设施处理程序应该直接使用吗?

    在将事件发送到目的地之前,我应该将其转换为技术事件吗?

    技术事件,例如某种DTO,如果它是数据结构


    我猜所有与消息传递有关的代码都属于基础结构层(端口/适配器插槽),因为它们仅用于系统之间的通信。

    并且在这些管道内部以引发/处理代码传递的技术事件应属于应用程序层,因为像命令一样,它们最终会导致系统状态发生变化。它们协调域,并由基础设施触发(例如控制器触发应用程序服务)。

    我阅读了一些有关在命令中转换事件的解决方案,但我认为这会使系统变得更加复杂,没有任何好处。

    因此,我的应用程序外观将公开三种交互方式:
    -命令
    -查询
    -事件

    通过这种分离,我认为我们可以更清楚地将UI和其他BC中的事件隔离开。

    好的,我知道帖子很长,可能有点混乱,但这就是我遇到的问题,因此如果您能说些什么对我有所帮助,我先谢谢您。

    所以我的问题是,我在努力与2 BC的整合。
    不同的解决方案:-服务RPC(#A)很简单,但限制了规模,-带有消息传递的服务(#B)看起来不错,但是我仍然需要反馈,以及-我确实不知道的域事件(#C)如何跨界。

    再次感谢你!

    最佳答案

    我在一个受限环境中有一个用例,需要联系另一位BC来验证聚合。

    这是一个非常奇怪的问题。通常,聚合有效或无效,完全取决于它们自己的内部状态-这就是为什么它们是聚合的原因,而不仅仅是某些较大网络中的实体。

    换句话说,您可能无法应用DDD模式,因为您对要解决的实际问题的理解不完整。

    顺便说一句:在中寻求帮助时,您应该尽可能地坚持自己的实际问题,而不是尝试使其抽象。

    也就是说,有些模式可以帮助您。乌迪·达汉(Udi Dahan)在关于reliable messaging的演讲中详细介绍了它们,但我将在此处介绍要点。

    对聚合运行命令时,需要考虑两个不同方面

  • 持续改变状态
  • 计划副作用

  • “副作用”可以包括要针对其他聚合运行的命令。

    在您的示例中,我们将在幸福的道路上看到三个不同的交易。

    第一个事务会将聚合的状态更新为“正在验证”,并计划任务以获取验证报告。

    该任务异步运行,查询远程域上下文,然后在该BC中启动事务#2,该事务将持久保存验证报告并计划第二个任务。

    第二项任务-根据复制到验证报告中的数据构建-启动事务#3,对聚合运行命令以更新其状态。该命令完成后,不再需要安排任何命令,一切变得安静。

    这行得通,但是可能会使您的聚集体与您的过程紧密结合。此外,您的过程是不相交的-分散在您的汇总代码中,并未真正被认为是头等公民。

    因此,您更有可能看到通过另外两个想法实现的方法。首先,引入域事件。域事件是对状态变化的描述,具有特殊意义。因此,聚合描述了更改(ValidationExpired?)以及实现该更改所需的本地状态,从而异步发布了该事件。 (换句话说,不是异步运行任意任务,而是异步调度以任意域事件作为有效负载的PublishEvent Task)。

    第二,引入“流程管理器”。流程管理器订阅事件,更新其内部状态机,并安排(异步)任务运行。 (这些任务与聚合之前计划的任务相同)。注意,流程管理器没有任何业务规则。这些属于集合。但是他们知道如何将命令与其生成的域事件进行匹配(请参阅Gregor Hohpe的Enterprise Integration Patterns中的消息传递章节),以安排超时任务,以帮助检测在其SLA中尚未完成的计划任务,等等。

    从根本上说,过程管理器类似于聚合。它们本身是域模型的一部分,但是应用程序组件向它们提供了对其的访问。对于聚合,命令处理程序是应用程序的一部分;当命令由聚合处理后,调度异步任务的是应用程序。域事件将发布到事件总线(基础结构),应用程序的事件处理程序订阅该总线,通过持久性加载流程管理器,传递要处理的域事件,再次使用持久性组件保存更新的流程管理器,然后,应用程序安排待处理的任务。

    我意识到这是一个“强制”域事件,好吧用户请求了它,但它从未真正出现在对话中,但仍然是域事件。

    我不会形容为强迫性的。如果此验证过程的要求确实来自业务,则域事件属于普遍使用的语言。

    我应该先将域名事件转换为技术事件,然后再将其发送到目的地

    我不知道你是什么意思。事件是描述发生的事情的消息。 “域事件”表示域内发生了某些事情。它仍然是要发布的消息。

    关于java - 调用其他有界上下文的策略,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38234410/

    相关文章:

    c# - 异步方法中的奇怪调试器行为

    java - 如何在Java中查找两个日期之间的所有有效日期

    Java - 如何删除谷歌云中的非空桶?

    java - 清除 Java 缓存

    jquery - 在 POST 之前从按钮提交的 SelectList 中选择所有选项

    c# - 在 C# 中捕获 javascript 事件

    c# - 具有 2 个处理程序的事件 Func<bool> : which return to expect?

    c++ - 异步CPU读取和GPU+CPU计算

    java - 如何使用 MD5 哈希在 JSP 和 Javascript 中保护登录密码?

    c# - IDbCommand 缺少 ExecuteReaderAsync