DDD:在存储库上删除与在实体上删除?

Posted

技术标签:

【中文标题】DDD:在存储库上删除与在实体上删除?【英文标题】:DDD: Delete on a Repository vs delete on an Entity? 【发布时间】:2015-07-16 10:23:41 【问题描述】:

一个使用 DDD 和 java 实现的非常简单的用例。

我有一个 FooEntity 和一个 FooRepository。 Entity 有一个 delete 方法,该方法验证某些状态以检查是否可以安全删除,如果评估结果为 true,则调用存储库中的 delete,该存储库被注入实体。

到目前为止一切都很好,但是,如果有人直接在存储库中调用 delete 方法会发生什么?则不会执行验证。

将验证放在存储库中可以解决问题,但这显然是错误的,因为必须公开实体的内部状态。

我错过了什么?

public class FooEntity 

  @inject
  FooRepository fooRepository;

  private Boolean canBeDeleted;

  public void delete()
    if (canBeDeleted)
      fooRepository.delete(this);
    
    throw new CannotBeDeletedException();
  


public class FooRepository 

  @inject
  FooDAO fooDAO;

  public void delete(FooEntity fooEntity)
    fooDAO.delete(fooEntity.getId());
  

【问题讨论】:

【参考方案1】:

不要暴露内部状态,在实体上暴露一个类似 isDeletable() 的方法。存储库的删除可以在删除之前调用 entity.isDeletable(),如果您尝试删除不可删除的实体,则会引发异常。这样你就可以分离关注点。实体具有“可删除性”的领域知识,而 repo 知道如何删除实体。

【讨论】:

这实际上是我找到的最佳解决方案。验证逻辑被封装在实体中,无法避免。我不喜欢 isDeletable() 成为实体接口的一部分,因此客户可能认为有必要在删除之前调用它,但事实并非如此。这是一个非常基本的场景,我希望有一个标准的解决方案。【参考方案2】:

示例代码照原样很好(除了在存储库类内部有一个DAO 很奇怪,因为“存储库”只是与DAO 相同概念的更抽象的名称)。

您无法真正阻止其他开发人员调用错误的方法,除非在可用的情况下使用静态分析代码检查。

存储库应该只关心从一组持久实体中删除给定的实体实例。即使isDeletable()方法在实体类中,它也不具备检查实体是否被允许删除的逻辑。

【讨论】:

在我看来,DAO 属于基础设施/持久层,它的实现非常接近使用的数据库,并且对实体一无所知,只知道 DTO。 Repository 属于领域层并返回/处理真实实体。我可以理解 DAO 在删除某些内容之前不需要检查是否需要执行任何类型的验证,因为它是纯粹的基础设施,它就像直接向数据库发出删除一样。但是存储库属于域,应该知道这种东西。 在实际项目中,我通常使用 JPA/Hibernate,因此同时拥有存储库和 DAO 毫无意义。但请注意,在 DDD 的上下文中没有 DAO,只有存储库。虽然我同意存储库可以并且应该包含“业务逻辑”,但它应该只出现在查询 DSL 表达式(如 JPA-QL)中,而不出现在 Java 代码中,因为这种业务逻辑代码属于实体和域服务;否则,如果存储库可以包含任意业务逻辑,它最终会看起来像一个域服务。 The repository speaks the domain language, the DAO does not(它只适用于 DTO,靠近数据库)。为了构建聚合,存储库可能已经注入了多个 DAO。 DDD 中没有提到 DAO,因为它不是它的一部分,它只是实现存储库的一种方式。如果使用 jpa,则不需要 DAO,是的。【参考方案3】:

我会将删除功能放在域服务中。

public class FooService 

  @inject
  FooRepository fooRepository;

  public void delete(Foo foo) 

    if( /* insert validation stuff here to check if foo can be deleted */ ) 
      fooRepository.delete(foo);
    
 

我这样做的方式是我通常使用 ValueObject 来表示实体的身份。例如

public class FooId() 

    String foodId;

    public String FooId(String fooId) 
       this.foodId = foodId;
    


public class Foo() 

    FooId id;

    /* other properties */

然后我会将 FooService 修改为:

public class FooService 

  @inject
  FooRepository fooRepository;

  public void delete(FooId fooId) 

    foo = fooRepository.retrieve(fooId);

    if( /* insert validation stuff here to check if foo can be deleted */ ) 
      fooRepository.delete(foo);
    
 

删除一个 foo(假设 fooId 是由 UI 中的命令传递的:

 fooService.delete(fooId);

我不会在代表实体的类中注入 FooRepository。我不认为这是正确的地方。对我来说,实体不应该能够创建或删除自己。这些函数应该在该实体的域服务中。

【讨论】:

领域服务的目标是封装不属于实体的业务逻辑,这通常是涉及多个实体/值对象的操作。为什么你认为这个删除验证不属于实体?在这种情况下,由于此验证是基于实体的状态完成的,这意味着暴露实体的内部。 你是对的。在这种情况下,在 FooService 中,您应该执行 if(foo.canBeDeleted()) fooRepository.delete(foo); ;然后它使用域来验证它是否可以被删除,如果它说是(真),那么我们调用存储库来删除它。我假设您很难删除记录。如果你是软删除它(即只是将记录的状态标记为已删除而不从表中删除它),你可以调用 foo.delete() 然后调用 FooRepository 来持久化更改。

以上是关于DDD:在存储库上删除与在实体上删除?的主要内容,如果未能解决你的问题,请参考以下文章

数据存储区 - 如果您删除一个实体,将来是不是可以在新创建的实体上重复使用它的实体 ID?

ABP官方文档翻译 3.1 实体

Dapper 的 DDD 原则和存储库

使用事件和 CQRS 重写 CRUD 系统

从核心数据中删除实体

使用 EF Core 保存附加实体时如何删除子实体