从领域模型通信回应用层

2024-02-05

我有一个带有价格列表的域模型产品。

  public class Product
  {    
    private List<int> _prices; //Note that this is a value object in my actual code

       public void AddPrice(int price)
       {

         var currentPrice = _prices.LastOrDefault();
      
          if(price < currentPrice)
            _prices.add(price)
       }

    }

当价格变化时,我希望发生很多事情。使用贫血域模型这非常容易,因为我可以将这一点保留在我的服务中:

 if(price < currentPrice)
        _prices.Add(price)

然后加上一些我想做的事情:

     if(price < currentPrice)
        {
            product.Prices.Add(price);
            _emailService.Email();
            _discordBot.Broadcast();
            _productUpdater.UpdateRatings();
           //etc etc
        }

如何实现这一点而不使我的域依赖于服务?或者我应该将它们传递到我的域?

不确定最佳方法(或诚实的任何方法),我已经阅读过有关领域事件的内容,但我认为这些有点高于我当前的经验水平,而且我不太理解这些材料


我可以想到不同的选项 - 根据您的具体要求 - 或多或少适合,并且也可以针对不同的用例选择不同的方法并将它们混合到您的解决方案中。

为了说明这一点,我想根据产品应用程序的操作研究不同的选项,我简单地称之为AddPriceToProduct(AddProductPriceCommand 定价命令)。它代表添加产品新价格的用例。这添加产品价格命令是一个简单的 DTO,它保存执行用例所需的所有数据。


选项(A): Inject相应的服务(例如,电子邮件服务)在将域逻辑执行到域对象的方法中时需要调用(此处AddPrice).

如果你总是选择这种方法传入一个接口(在您的域层中定义)而不是实际执行(应在基础设施层中定义)。另外,如果发生一些事情,我不会选择这种方法after您的域操作中发生了一些事情。

public void AddPriceToProduct(AddProductPriceCommand pricingCommand)
{
    var product = _productRepository.findById(pricingCommand.productId);
    product.AddPrice(pricingCommand.price, _emailService);
    _productRepository.Update(product);
}

以及相应的AddPrice方法可能如下所示:

public void AddPrice(int price, IEmailService emailService)
{
    var currentPrice = _prices.LastOrDefault();
  
    if(price < currentPrice)
    {
        _prices.add(price);
        // call email service with whatever parameters required
        emailService.Email(this, price);  
    }
}

选项(B): 让应用服务(编排用例)调用相应的服务在调用应用程序用例需要执行的相应聚合(或域服务)方法之后。

如果这总是在执行特定域模型操作之后发生,那么这可能是一种简单而有效的方法。我的意思是,在您的聚合(或域服务)上调用该方法之后,在您的情况下AddPrice方法,有没有条件逻辑是否应调用其他服务(例如电子邮件)。

public void AddPriceToProduct(AddProductPriceCommand pricingCommand)
{
    var product = _productRepository.findById(pricingCommand.productId);
    product.AddPrice(pricingCommand.price);
    _productRepository.Update(product);
    // always send an email as part of the usual workflow
    _emailService.Email(product, pricingCommand.price);
}

在这种情况下,我们假设正常工作流程将始终包含此附加步骤。我不认为这里务实有什么问题,只需在应用程序服务方法中调用相应的服务即可。


选项(C):类似于选项(B) but 有条件逻辑之后执行AddPrice已被调用。在这种情况下,这个逻辑可以包装成一个单独的域服务这将根据当前状态处理条件部分Product或域运算的结果(如果有的话)(AddPrice).

我们首先简单改变应用服务方法,加入一些领域知识:

public void AddPriceToProduct(AddProductPriceCommand pricingCommand)
{
    var product = _productRepository.findById(pricingCommand.productId);
    product.AddPrice(pricingCommand.price);
    _productRepository.Update(product);

    if (product.HasNewPrice())
    {
        _emailService.Email(product, pricingCommand.price;
    }
    if (product.PriceTargetAchieved())
    {
        _productUpdater.UpdateRatings(product, pricingCommand.price);
    }
}

现在这种方法还有一些改进的空间。由于要执行的逻辑绑定到产品的 AddPrice() 方法,因此可能很容易错过需要调用的附加逻辑(在某些情况下调用电子邮件服务或更新程序服务)。当然你可以将所有服务注入添加价格()Product 实体的方法,但在这种情况下,我们想要研究将逻辑提取到域服务.

首先我们看一下新版本的应用服务方法:

public void AddPriceToProduct(AddProductPriceCommand pricingCommand)
{
    var product = _productRepository.findById(pricingCommand.productId);
    _productPricingService.AddPrice(product, pricingCommand.price);
    _productRepository.Update(product);
}

现在让我们看一下名为的域服务的相应域服务方法,例如产品定价服务:

public void AddPrice(Product product, int price)
{
    if (product.HasNewPrice())
    {
        _emailService.Email(product, pricingCommand.price;
    }
    if (product.PriceTargetAchieved())
    {
        _productUpdater.UpdateRatings(product, pricingCommand.price);
    }
}

现在,处理产品价格更新的逻辑是在域层处理的。另外,域逻辑是更容易进行单元测试因为有更少的依赖(例如,这里不关心存储库)并且需要使用更少的测试替身(模拟)。

这是当然的仍然不是最高程度的业务逻辑封装与最低程度的依赖相结合在领域模型内部,但它至少更接近一些。

为了实现上述组合,领域事件将投入使用,但当然,这些也可能需要更多的实施工作。让我们在下一个选项中看看这个。


选项(D):从域实体引发域事件并实现相应的处理程序,这些处理程序可以是域服务甚至基础设施服务。

域事件发布者(您的域实体或域服务)和订阅者(​​例如电子邮件服务、产品更新程序等)之间的连接。

在这种情况下,我建议不要立即分派引发的事件,而是收集它们,并且只有在一切正常(即没有抛出异常、状态已保留等)之后才分派它们进行处理。

让我们看看添加价格()的方法Product通过使用相应的领域事​​件再次实体。

public void AddPrice(int price, IEmailService emailService)
{
    var currentPrice = _prices.LastOrDefault();
  
    if(price < currentPrice)
    {
        _prices.add(price);
        RaiseEvent(
            new ProductPriceUpdatedEvent(
                this.Id,
                price
            ));
    }
}

The 产品价格更新事件是一个简单的类,它表示过去发生的业务事件以及该事件的订阅者所需的信息。在您的情况下,订阅者将是电子邮件服务、产品更新服务等。

考虑引发事件()方法作为一种简单方法,它将创建的事件对象添加到产品实体的集合中,以便收集从应用程序或域服务调用的一个或多个业务操作期间发生的所有事件。此事件收集功能也可以是实体基类的一部分,但这是一个实现细节。

重要的是,之后添加价格()方法执行后,应用层将确保所有收集到的事件将被分派给相应的订阅者。

这样,域模型就完全独立于基础设施服务依赖性以及事件调度代码。

The “派遣前承诺”本文中描述的方法弗拉基米尔·霍里科夫 (Vladimir Khorikov) 的博客文章 https://enterprisecraftsmanship.com/posts/domain-events-simple-reliable-solution/说明了这个想法,并且也是基于您的技术堆栈。

注意:对您的逻辑进行单元测试Product与其他解决方案相比,域实体现在非常简单,因为您没有任何依赖项,并且根本不需要模拟。测试是否在正确的操作中调用了相应的域事件也很容易,因为您只需从Product调用业务方法后的实体。


To get 回到你的问题:

如何实现这一点而不使我的域依赖于服务?

要实现这一目标,您可以查看选项(B)、(C)和(D)

或者我应该将它们传递到我的域?

这可能是一种有效的方法 - 请参阅选项(A)- 但请注意,如果在域模型类的可维护性和可测试性方面需要注入多个依赖项,那么事情会变得更加复杂。

当我在这些不同的选项之间进行选择时,我总是试图找出所执行操作的哪些部分确实属于相应的业务操作,哪些部分或多或少不相关,并且实际上并不需要使业务交易成为有效交易。

例如,如果需要由服务执行的某些操作需要发生,或者整个操作根本不应该发生(就一致性而言),那么选项 (A) - 将服务注入域模型方法 - 可能很合适。否则,我会尝试将任何后续步骤与域模型逻辑分离,在这种情况下,应考虑其他选项。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从领域模型通信回应用层 的相关文章

随机推荐

  • 如何修复因字节计数长度不正确而损坏的序列化字符串?

    我使用 Hotaru CMS 和图像上传插件 如果我尝试将图像附加到帖子中 则会收到此错误 否则不会出现错误 unserialize function unserialize 偏移处错误 有问题的代码 错误点与 一致 Retrieve su
  • i18next 显示键而不是值

    I have translation json文件输入 locales en app name Example App In html 我有 a href In js document ready function var language
  • Castle:如何在日志拦截器中获取正确的 ILogger?

    如果您在温莎城堡中使用 LoggingFacility 如果您的类中有可选的记录器依赖项 城堡可以将记录器注入其中的 ILogger 属性 容器将自动解析与您的类关联的记录器 但是如果我想使用AOP 拦截器方法 来实现日志记录 我基本上想写
  • SQL 2005 - 多次指定列

    尝试在 SQL 2005 中运行此查询时出现以下错误 SELECT tb FROM SELECT FROM vCodesWithPEs INNER JOIN vDeriveAvailabilityFromPE ON vCodesWithPE
  • 在 ngx-toastr 中找不到模块

    我找不到答案 ERROR in node modules ngx toastr fesm5 ngx toastr js Module not found Error Can t resolve Users vasanthan Mean pr
  • 使用 JPanel 作为 JTable 单元格编辑器时获取焦点问题

    我有一个单元格编辑器 其中包含一个小按钮 可以双击该按钮来弹出编辑对话框 然后是一个可用于内联编辑值的文本字段 需要弹出窗口才能编辑其他值 仅允许编辑第一个显示在 JTable 中 当用户单击字段时 一切正常 但如果他们使用 Tab 键进入
  • 如何在散点图的 x 轴上绘制每分钟的时间

    为 1 秒采样数据设置分钟小刻度 OverflowError int too big to convert 考虑这个数据帧 采样间隔为 1 秒 持续时间约为 30 分钟 import matplotlib pyplot as plt fro
  • .jsp 中的登录系统

    我有一个login jsp包含登录表单的页面 登录后 用户将被带到index jsp和这个index jsp应该知道哪个用户已登录 如果用户刷新页面 他将保持登录状态 而不会返回到login jsp 所以需要有某种会议 UPDATE会话管理
  • SMTP 错误:无法连接到 SMTP 主机

    我有这段代码 并且在我的本地服务器上一切正常 电子邮件已发送 没有任何问题 但现在我将内容传递给网络服务器 我收到此错误 SMTP Error Could not connect to SMTP host SSL is enable in
  • 复式记账分页问题

    带有分页的复式记账会计系统存在一个非常严重的问题 我认为这很常见 但我仍然没有找到解决我的问题的任何解决方案 您可以使用此链接阅读有关简单的复式记账会计系统 https medium com RobertKhou double entry
  • 创建虚拟监视器(显示设备)

    我提出了一个问题here https stackoverflow com questions 11919175 java robot screenshot beyond visible但意识到我走错了方向 我需要创建一个虚拟监视器 实际上只
  • 如何以最便携的方式通过Graphene访问PrimeFaces组件?

    我正在使用 Primefaces 6 1 组件为 Java EE Web 客户端编写 Arquillian Graphene 功能测试 并希望从一开始就以最便携的方式编写它们 我注意到不可能执行类似的操作WebElement click在任
  • watchOS 2 与 CocoaPods 配合使用

    有人让 CocoaPods 与 watchOS 2 一起使用吗 我尝试将 use framework 与 platform watchos 2 0 一起使用 但它说 无效Podfile文件 不支持的平台watchos2 平台必须是 ios
  • 是否可以从谷歌电子表格填充谷歌表单?

    我想创建一个使用电子表格中的数据的表单 以便它是动态的 是否有可能做到这一点 我无法找到任何描述如何操作或任何示例的地方 似乎所有可能的方法都是从表单填充电子表格 我也会使用该表单 但这不是这里的主要问题 是的 使用表单脚本并使用 FORM
  • 如何升级django?

    我的项目在 Django 1 5 4 上运行 我想升级它 我做到了pip install U I django现在pip freeze显示 Django 1 6 5 显然 django 已升级 我在virtualenv 但我的项目仍在使用
  • bootstrap-selectpicker 不改变框宽度

    我正在尝试更改显示所选选项的选择器框的宽度 但无论我尝试什么 我都无法更改它 也无法更改框的颜色 任何想法或建议表示赞赏 我假设应该更改框宽度的代码片段如下 如您所见 我将其一直设置为 25 但宽度仍然很宽 selectpicker wid
  • 如果窗口未激活则获取输入 (Windows)

    简洁版本 当窗口未激活时 如何在使用 C C 的 Windows 中接收输入消息 背景资料 我目前正在开发一个不应该依赖于任何窗口的输入系统 因此它可以例如也可以在控制台中使用 我的想法是创建一个仅接收消息的不可见窗口 这可以使用 HWND
  • 在 Go 1.18 中 strings.Title() 已被弃用。现在该用什么?如何?

    按照建议here https www thesaurus com e grammar when to capitalize words 人名应该大写 例如John William Smith 我正在用 Golang 编写一个小软件 它从用户
  • 在另一个文件中查找一个文件的内容

    我使用以下 shell 脚本将一个文件的内容查找到另一个文件中 bin ksh file home nimish contents txt while read r line do grep line home nimish another
  • 从领域模型通信回应用层

    我有一个带有价格列表的域模型产品 public class Product private List