将业务键建模为属性与嵌入式对象

Modelling a business key as an attribute vs an embedded object

我正在关注这本关于 Spring 使用域驱动设计引导的书,作者在其中对具有大量属性的 Cargo 实体进行建模。有问题的两个属性是数字键和业务键 (bookingId)。 Cargo实体如下

**Cargo.java**


package whatever;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Getter @Setter
public class Cargo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Embedded
    private BookingId bookingId; // Business Identifier
}

**BookingId.java**

package whatever;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.Column;
import javax.persistence.Embeddable;

@Embeddable
@Getter @Setter
public class BookingId {
    @Column(name="booking_id")
    private String bookingId;
}

如您所见,作者将业务密钥建模为一个嵌入式对象。将属性嵌入另一个对象有什么好处?为什么我们不在 Cargo class 中有一个名为 bookingId 的字符串变量并对其强制执行唯一性约束?

Why don't we just have a String variable called bookingId in Cargo class and enforce a uniqueness constraint on it?

两个主要原因:

使用特定类型,而不是像 String 这样的 general-purpose 原语,允许编译器将预订标识符与其他类型的字符串区分开来,减少您无意中传递预订 ID 的更改需要位置标识符。

此外,定义 class(如 BookingId)可以让您将“需要了解底层数据结构的代码”与“不需要了解底层数据结构的代码”进行逻辑分离。换句话说,您受益于将您的预订标识符数据结构查询集中在一个地方。

(与模型中的其他值类型相比,第二个对于“标识符”而言往往不那么重要。在许多情况下,标识符在语义上是不透明的 - 您可以对它们做的唯一真实的事情就是比较它们彼此。)

使用显式类型建模信息还允许您减少 操作space,消除没有任何意义的方法。例如,获取预订 ID 的子字符串可能没有任何用处,因此您通过将其简化为在您的域模型中有意义的语义来简化 api。

您会看到与 collections 类似的问题;列表和地图往往具有广泛的界面,因此它们支持许多可能的应用程序,但运送域与列表和地图无关,它与 行程 有关。标准库恰好有一个方便使用的数据结构这一事实是实现的意外,而不是您试图建模的业务的一部分。

重要吗?最终,机器真的不关心代码设计——但下一个做出改变的人会受到设计的很大影响。常见的 DDD 模式的部分动机是代码的设计让你,程序员,可以专注于手头的任务(另请参阅“域代码”与“持久性代码”的分离,以及引入Evans 2003 第 6 章中的生命周期模式。