如何使用 Spring Crud/Jpa 存储库实现 DDD

How to implement DDD using Spring Crud/Jpa Repository

我想通过使用 Spring 实现 DDD 来创建一个应用程序。假设我有一个业务实体 Customer 和一个接口 CustomerRepository。

由于spring默认提供CrudRepositoryJpaRepository来执行基本的CRUD操作和其他操作,如finder方法,我想使用它们。所以我的界面变成了

 @Repository
public interface CustomerRepository extends JpaRepository<Customer, Long>{

}

但是根据 DDD,接口应该在域层,实现应该在基础设施层。

现在我的问题是,CustomerRepository 属于哪一层?

简答:虽然它应该是对领域层基础设施的任何依赖,但为了KISS,你可能 这样做。如果你想成为 DDD 的纯粹主义者,你可以定义一个 CustomerRepository 接口,并在实现这两个接口的基础设施中定义一个实现。

又长又无聊的答案:一般来说,域不应该关心或了解基础设施,因为它不应该依赖于其他层(基础设施、应用程序、表示或您正在使用的任何架构)。遵循这条规则会导致更简洁的架构。

特别是,域不应该关心持久性,它应该像内存中的 运行s 一样表现。从域的角度来看,实体会发生变化,仅此而已,不需要持久性。

域代码的写入端实际上不需要持久性。当聚合执行命令时,它们已经完全加载。在执行命令后,聚合只是 return 更改或新状态。聚合本身不会保留更改。它们是纯净的,没有明显的副作用。

作为架构师,我们需要持久性,因为我们需要确保数据在重启之间持续存在,并且我们可以 运行 同时在多台机器上使用相同的代码。

但是,还有另一个需要域代码,特别是域的读取和响应端(Sagas/Process 管理器)。域的这些组件需要查询和过滤域实体。 Readmodels 需要 return 调用方的实体,Sagas/Process 管理器需要正确识别向谁发送命令的正确聚合。

解决方案是只在域层定义接口并在基础结构中实现。通过这种方式,域拥有接口,因此,根据 Dependency Inversion Principle,它不依赖于基础设施。

在你的例子中,虽然领域层依赖于Spring框架基础设施部分的某物,但某物 只是一个接口。它仍然依赖于 JPA,因为您的域将使用它不拥有的方法,但 KISS 在这种情况下可能更重要。

另一种方法是定义一个不扩展 JpaRepository 的接口,并在基础设施中实现该接口和 JpaRepository 接口。

哪种解决方案取决于您:更多的代码重复但更少的依赖性或更少的代码重复和更多的对 JPA 的依赖性。