【问题标题】:Domain Driven Design Layout Question领域驱动设计布局问题
【发布时间】:2010-11-29 10:04:50
【问题描述】:

我是 DDD 的新手。我有一个配置文件类和一个配置文件存储库类。 PROFILE 类包含以下字段 -> Id、Description、ImageFilePath

所以当我添加一个新的配置文件时,我会将图像上传到服务器并将其路径存储在我的数据库中。

当我删除配置文件时,图像也应该从我的文件系统中删除。

我的问题:

我在哪里为此添加逻辑。我的配置文件存储库有一个 Delete 方法。我应该在这里添加这个逻辑。或者我应该添加一个服务来封装这两个操作。

任何意见将不胜感激......

谢谢

【问题讨论】:

    标签: c# architecture domain-driven-design separation-of-concerns ddd-repositories


    【解决方案1】:

    您有两个与图像相关的不同“操作”。你有一个“物理”过程和一个“逻辑”过程。逻辑过程是将有关图像的信息保存到域存储库中,因为它是域的一部分。添加(和删除)的物理过程是逻辑过程的先决条件。

    退一步说,物理过程完全独立于逻辑过程,但反之则不然。如果未保存图像,您显然不想保留有关图像的元信息(在域中)。此外,如果您无法删除物理文件,您也不想从域中删除信息。

    域应包含从数据源中删除图像的逻辑实例所需的信息。将域视为物理上独立的应用程序。在这种情况下,域实际上并不知道它所保存的数据与物理文件有任何关系。确保保持这种方式。

    通常,我将实体放在一个程序集中,然后将我的存储库和域服务放在另一个程序集中。应用程序服务位于域模型之外,但利用它来完成工作。因此,应用服务使用一个或域服务或其他应用服务,而域服务可以使用一个或多个存储库。

    记住这一点,实际删除逻辑有两个位置,第三个位置用于协调它们。如果我这样做,这就是它的工作方式。域服务将利用存储库从底层数据源进行逻辑删除(以及您将需要的检索)。除了使用域对象实例之外,它不知道其他任何事情。我还将有一个应用程序服务(域外),专门处理删除物理实例。为了论证起见,我假设您有一个“ImageRepository”类和一个“ImageServices”类,它们分别包含您的域存储库和域服务。您的 ImageServices 需要一个 Delete() 方法,以及您正在使用的任何 Find() 方法。我通常将查找方法明确称为 FindBy...()(即 FindByKey()、FindByName() 等)。

    如果您无法删除物理实例,您不想删除逻辑实例,因此请确保您有一种方法可以衡量物理映像的删除操作是否成功。在这种情况下,我可能会使用某种自定义异常(因为我认为删除文件是通常不会失败的标准操作)。这通常属于“管理”领域。所以通常我有一个名为“ImageManagementService”的应用程序服务。为简单起见,此服务(因为它是应用程序的一部分而不是域的一部分)可以有一个私有方法来执行物理删除。我们称之为“DeleteImageFile()”。

    第三个地方是这两个操作的协调,也作为一个应用服务。我只是将其设为“ImageManagementService”中的公共方法。我们可以称它为“RemoveImage”。此应用程序服务将执行以下操作:

    1. 从域服务中检索实例信息(对存储库的直通调用)。
    2. 使用实例信息定位物理文件并将其删除(再次提到第一个应用程序服务)。
    3. 如果物理删除成功,则删除实例(返回域服务,再次门面存储库)。

    所以,应用程序本身会从“ImageManagementService”实例调用“RemoveImage()”方法。在内部,“RemoveImage()”首先从域的“ImageServices”中调用“FindBy..()”来从域中获取一个实例。文件路径用于从那里调用“ImageManagementService”实例中的私有“DeleteImageFile() 方法。成功后,它将调用域的“ImageService”中的“Delete()”方法,该方法充当门面到您的存储库。

    我认为在这种情况下关注关注点的分离非常重要,因为如果你有一个明确的分离(你可以用不同的程序集来做),你会很容易知道哪种逻辑可以进入哪个地方。我强烈推荐埃文的书。此外,为了快速了解与 DDD 相关的 SOC 概念,我建议查看 Jeffrey Palermo 在“Onion Architecture”上的三部分系列。

    关于您为什么要使用域服务而不是直接从应用程序服务调用存储库的几点说明。首先,存储库具有比域服务更复杂的实例化。请记住,它主要是一个外观,但可能具有不适合域中其他任何地方的附加逻辑。如果您想强制使用唯一的文件名,这可能是一个很好的例子。域对象本身不直接了解其他聚合中的其他域对象,因此域服务可能会在保存操作之前检查具有相同名称的现有实例。非常方便,确实!此外,域服务不限于单个存储库。您可以让域服务协调多个存储库之间的工作。如果您有重叠的聚合,您可能需要同时使用两个相关的聚合根调用 work。您可以在域服务中执行此操作,将这种逻辑保留在域中,而不是渗入应用程序。

    希望这会有所帮助。我确信还有其他方法可以做到这一点,但这是我在自己的具有类似场景的应用程序中找到成功的方法。

    【讨论】:

      【解决方案2】:

      @joseph.ferris:“通常,我将实体放在一个程序集中,然后将我的存储库和域服务放在另一个程序集中。”

      就个人而言,我更喜欢将程序集视为一个部署单元,而不是关注点分离设计工具。为此,我宁愿使用命名空间。

      以这种方式确保没有循环依赖(在这些命名空间之间)比较困难,但 NDepend 之类的工具可以提供帮助。

      【讨论】:

      • 我实际上两者都使用它。它们以这种方式分离的事实允许我们将包含实体的程序集分发给使用我们的 Web 服务的第三方,例如,而无需访问其他关注点。它还与变化的频率和“涟漪效应”有关。存储库和服务的变化率高于实体本身。显然,您不希望过于分散,但您也不希望最终将所有东西都放在一个程序集中。
      • 此外,我们在域中使用“每层组装”方法。但是,如前面的评论中所述,在这种情况下只有两个真正的程序集。通过代码审查,我们强制您只能向下调用堆栈而不能向上调用。因此,域服务使用存储库和实体。存储库可以使用实体。确定是否违反了实体与存储库/服务的关系相当简单,因为它必须在项目中被引用。删除引用(如果存在)会立即显示所有违反此规则的地方。
      【解决方案3】:

      在第一种方法中,我想我会选择最简单的方法,并从 ImageRepository 内的磁盘中删除物理映像。 它可能不是最“正确”或“纯粹”的解决方案,但它是最简单的解决方案,这符合“选择最简单可行的解决方案”的慢板。

      如果在项目的后期阶段,您觉得这个解决方案不好,并且您觉得需要一个更复杂(也许更纯粹)的解决方案,比如 joseph.ferris 提出的解决方案,那么您总是可以重构它。

      重构一个简单的解决方案比重构一个复杂的解决方案更容易。 :)

      【讨论】:

      • 你必须小心,因为这是一条非常好的线路。在这种情况下,您基本上编写了相同数量的代码,除了如果您将它放在存储库中,您在一个方法中拥有的代码,需要大约九行,以方法声明和大括号的形式。每个团队都不同,但我的团队会采用简单的解决方案,只要该解决方案不涉及另一层的问题。对于一种简单的方法,我仍然可以看到将其滚动到一个方法中,但我会在应用程序层执行此操作,然后调用存储库的删除方法。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-10-08
      • 1970-01-01
      • 2018-07-03
      • 2010-12-01
      • 2011-04-26
      相关资源
      最近更新 更多