跳至主要内容

ImplDDD 阅读笔记 - Entity Identity

DDD 中实体类型的设计往往被错误的导向了与数据库有关的 Scheme Design。 > Because of the prevailing approaches to software development that place > importance on the database, instead of designing domain concepts with rich > behaviors, developers might think primarily about the attributes (columns) > and associations (foreign keys) of the data. 这种以数据库范式为导向的设计思路导致 domain models 中几乎每一个概念都以 entity 的方式被编码为仅有 `getter` 和 `setter` 的类型,可是显然 Domain driven design 所追求的 entities 不应该仅仅具有 property accessors。 > An entity is a unique thing and is capable of being changed continuously over > a long period of time. Changes may be so extensive that the object might seem > much different from what it once was. And yes, it is the same object by identity. 常见的 *Anemia model (贫血的模型)* 很多时候就是来自于这样的设计。例如,书中第一章所给出的 Customer 的例子就是很【贫血】的: private void saveCustomer( String customerId, String customerFirstName, String customerLastName, String streetAddress1, String streetAddress2, String city, String stateOrProvince, String postalCode, String country, String homePhone, String mobilePhone, String emailAddress) { Customer customer = customerDao.readCustomer(customerId); if (customer == null) { customer == new Customer(); customer.setCustomerId(customerId); } customer.setFirstName(customerFirstName); customer.setLastName(customerLastName); customer.setStreetAddress1(streetAddress1); customer.setStreetAddress2(streetAddress2); customer.setCity(city); customer.setStateOrProvince(stateOrProvince); customer.setPostalCode(postalCode); customer.setCountry(country); customer.setHomePhone(homePhone); customer.setMobilePhone(mobilePhone); customer.setEmailAddress(emailAddress); } saveCustomer 方法为了保存对用户信息的修改,调用了 Customer model 上的 setters,这是一个典型的贫血对象。如果针对业务逻辑将 Customer 修改如下,就会好很多: public interface Customer { void changePersonalName(String firstName, String lastName); void postAddress(PostalAddress postAddress); void relocateTo(PostalAddress changedPostalAddress); void changeHomeTelephone(Telephone telephone); void disconnectHomeTelephone(); void changeMobilePhone(Telephone telephone); void disconnectMobilePhone(); void emailAddress(EmailAddress emailAddress); } ### Unique Identity of Entities 与 Value Objects 相比,Entities 最大的特点在于它的 unique identity 和 mutability。Value objects 作为传递值的对象,具有 immutability,而为了满足 Entities 的 unique identity,Value objects 很适合被用作为 unique identifier 的载体。在设计 unique identity 的时候,有几种不同的策略。 首先,unique identity 的生成时机是大有讲究的,有两种不同的顺序: 1. Early identity generation and assignment happen before the Entity is persisted. 这种提前生成 unique identity 的方式常见于用 Oracle sequence。 2. Late identity generation and assignment happen when the entity is persisted. 这种当持久化 Entity 对象时才生成并赋值 unique identity 的方式常见于 MySQL auto-increament。 最为简单的生成 unique identity 的方式显然是交给 data store 来处理,在第一次持久化保存 entity 的时候生成并赋值。然而这种做法在某些情况下并不管用。例如,在创建若干个 entities 对象之后,如果需要将其添加到 java.util.Set 对象内就无法正常工作。这是因为 hashCode 方法在 entity 被持久化之前无法得到有效的 id,它很可能返回 null 或是 0 这样的值。 要解决这一问题有两个方法,要么修改为提前生成并赋值 unique identity,要么修改 Entity 的 hashCode 和 equals 方法,改为使用 properties 而非 identity 来比较对象和生成哈希值。实际上,第一种解决方法更好一些,因为 Entities 的 equals 和 hashCode 方法应当根据其自身的 unique identity 来工作。 此外,在某些复杂的用例下,可能需要由另一个 Bounded Context 来给当前的 Bounded Context 中的 Entity 来赋值,这一类的情景往往涉及到 synchronization (同步),如果外部 Bounded Context 中的 reference objects 发生了变化导致对当前的 entity 产生影响,可以采用 Event-Driven Architecture 和 Domain Events 来解决同步的问题。 ### Surrogate Identity Hibernate 作为最为流行的 ORM framework 被广泛使用在项目中。Hibernate 倾向于使用数据库的 native type 作为 primary identity,像是 numeric sequence。应用程序开发者受限于 Hibernate 这样的 de-facto ORM tools,难以使用其它类型作为 Entity 的 primary identiy。为了解决这一问题,可以引入两个 identities,一个是针对 domain model 设计的符合 requirements 的 unique identity,另一个则是为了 Hibernate 而专门提供的 surrogate identity。 定义 surrgote identity 很简单,在 Entity 上定义 int 或是 long 类型的 property,并在数据库中为对应的 table 定义与之对应的 column。在使用 surrogate identity 的时候应当尽可能的将它对外隐藏起来,因为这个 identity 并非 domain model 的自然部分。以下类型是根据 Layer Supertype 策略来定义的一个抽象层: public abstract class IdentifiedDomainObject implements Serializable { private long id = -1; public IdentifiedDomainObject() { super(); } protected long id() { return this.id; } protected void setId(long anId) { this.id = anId; } } 没有必要让 domain identity 在数据库中扮演 primary key 的角色,surrogate identity 可以很出色的扮演这一角色。Surrogate database primary key 可以被用于其它 tables 的 foreign keys,提供 referential integrity 等。这些功能往往可以满足 data management 的 requirement,尤其是 audits 所需要 referential integrity。

评论