关于您的报价:
但是,JPA规范要求实体bean应该有公共的getter和setter方法,这本质上是一种无血缘关系的数据容器,与DDD领域模型相反。
我不是这个主题的专家,但据我所知,JPA并不强制要求公共的getter和setter方法,尽管有时文档可能含糊不清。
例如,Hibernate 5.6文档中说:
声明持久属性的getter和setter
JPA规范要求这样做,否则,模型将防止从实体本身外部直接访问实体持久状态字段。
通过“上述要求”,他们可能指的是这个:
实体的持久状态由实例变量表示,这些变量可能对应于JavaBean风格的属性。实例变量必须仅由实体实例自身的方法直接访问。
实体的状态仅通过实体的访问器方法(getter/setter方法)或其他业务方法向客户端提供。
这些文本并不是非常清楚,是否始终需要使用setter和setter,即使使用了“
requires”这个词。我认为它基本上是说(或应该说),如果您的客户端想要读取和写入属性,则应分别使用公共getter和setter。(使用“
您的客户端类”,我指的是您编写的类,而不是Hibernate本身作为实体的客户端。)但是,如果您的客户端不需要读取或写入它,则根本不需要访问器方法,只要使用JPA字段访问属性注释(而不是JPA方法/属性访问属性注释)。除了setter之外,您还可以使用构造函数参数来设置非空属性。(我在我的Hibernate代码中有很多这样的情况。)尽管JPA / Hibernate文档最初可能是针对更贫血的实体类编写的,但您不需要具有这种贫血的访问器。
关于您的报价:
那么我应该将领域逻辑捆绑在JPA实体内吗?还是在使用ORM和DDD时保持两个不同的模型和映射逻辑?这些模型和映射逻辑应该在项目级别的哪里?
制作两个不同的模型会创建很多样板代码。
JPA规范说:
除了返回和设置实例的持久状态外,属性访问器方法还可以包含其他业务逻辑,例如执行验证。当使用基于属性的访问时,持久性提供程序运行时执行此逻辑。
因此,在实体类中具有业务逻辑不应该是一个问题。在理想情况下,实体类是纯DDD领域类,其中应用于它的ORM仅限于元数据(例如注释),而代码本身是ORM无关的。
但是,将许多ORM元数据与大量业务逻辑混合在一起可能会影响可读性。解决这个问题的方法可以是使用Kotlin(一种基于Java的语言)中的扩展方法。 Kotlin扩展方法基本上是Java中的静态方法,可以作为Kotlin中的实例方法使用,并且从逻辑上添加到其所属的类之外。
例如,您可以拥有以下实体类:com.stackoverflow.domain.Question
。
出于某种原因,您想向其中添加persist()
方法,但是由于关注点分离,您不希望将其放在Question
类中,则可以使用扩展方法来完成此操作:
在文件com.stackoverflow.persistence.QuestionExt.kt
中:
package com.stackoverflow.persistence
import com.stackoverflow.domain.Question
fun Question.persist() {
val id = this.id
}
然后客户端代码可以这样做:
package com.stackoverflow.persistence.repositories
class QuestionRepository {
fun save(question: Question) {
question.persist()
}
}