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:在存储库上删除与在实体上删除?的主要内容,如果未能解决你的问题,请参考以下文章