我正在尝试一个六边形架构项目,并将域模型与持久性模型分开。但是当我必须引用持久性模型中另一个实体的外键时,我会遇到困难。
我有一个看起来像这样的客户实体及其对应的持久化模型
class Customer(val customerId: String, val name: String) {
}
@Entity(name = "customer")
data class CustomerEntity(
var customerId: String,
var name: String,
@Id @GeneratedValue(strategy = GenerationType.AUTO) var id: Long = -1
)
customerId
应该是业务 ID,而对应的实体还有自己的数据库 ID。
现在我有另一个引用客户的实体Report
class Report(val reportId: String, val date: LocalDate, val customerId: String) {
}
@Entity
data class ReportEntity(val reportId: String,
val date: LocalDate,
val customerId: String, // what should be saved here? the persistence Id (Long) of the customer or its business ID (String)?
@Id @GeneratedValue(strategy = GenerationType.AUTO)
var id: Long = -1)
如果我要保存客户的持久性 ID,那么我必须首先从数据库中获取它,这意味着我需要从报表组件中查询客户表。但这两个是两个独立的有界上下文,我不确定这是否是正确的解决方案或者这里存在建模问题。
如果我要保存客户的持久性 ID,那么我必须首先从数据库中获取它,这意味着我需要从报表组件中查询客户表。但这两个是两个独立的有界上下文,我不确定这是否是正确的解决方案或者这里存在建模问题。如果您的两个有界上下文共享相同的持久层,那么您的持久层可以查询 Customer 表以从自然键解析主键。
如果您的两个有界上下文具有单独的持久层,那么您需要在报表上下文的持久层中维护客户信息的副本。我们称它们为
Customer.Customer
(客户上下文中的事实来源)和
Report.Customer
(报告上下文中的复制)。这些模型可以具有不同的结构,这称为多义域模型。 客户希望在两个持久层中拥有不同的主键。持久化报表时,持久层可以查询
Report.Customer
表以从自然键解析主键。您还需要某种机制将数据从真实来源同步到副本。不要在持久性级别(数据库到数据库)进行复制,否则您将在基础结构级别创建一些依赖关系。更喜欢从客户服务的界面读取数据,因为它更稳定并且通常是版本化的。
但是如果你坚持有一个单独的ID,你会希望使用生成的ID作为参考键,因为它再次对性能更好。另一方面,您应该考虑查询报告的用例。他们是否按 CustomerId 过滤报告?
如果答案是“否”,您不会遇到任何问题,因为用例不需要 CustomerId。
但是,如果答案是“是”,那么您有三个选择:
第三种选择更容易应用,但以耦合为代价。