实体
当我们需要考虑一个对象的个性特性时,或者需要区分不同的对象时,我们引入实体这个领域概念,一个实体是一个唯一的东西。并且可以在相当长的一段时间内持续的变化,我们可以对实体进行多次的修改。但是尽管经历了多次的修改,实体始终拥有唯一一个身份标识。
实体生成唯一标识的几种方案:
- 用于提供唯一标识
让用户自己输入,这是一种非常简单的方案,但是也可能会变得很复杂,因为我们需要高质量的标识。大多数情况下标识是不允许修改。 - 应用程序生成
比如UUID,GUID
String id = java.util.UUID.randomUUID().toString();
或采用生成随机数加密的方式
SecureRandom randomGenerator = new SecureRandom();
int randomNumber = randomGenerator.nextInt();
String randomDigits = new Integer(randomNumber).toString();
try {
MessageDigest encryptor = MessageDigest.getInstance("SHA-1");
byte[] idBytes = encryptor.digest(randomDigits.getBytes());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
缺点:
1)没有排序,无法保证趋势递增。
2)UUID往往是使用字符串存储,查询的效率比较低。
3)存储空间比较大,如果是海量数据库,就需要考虑存储量的问题。
4)传输数据量大
5)不可读。
这里建议采用Twitter的snowflake算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake。
另外我们可以通过Commons项目的Commons Id组件实现
- 持久化机制生成唯一标识
有些数据库不支持标识的提前生成,比如MYSQL,但是对于Oracle是支持采用序列值的方式提前生成一个可用的标识。
值对象
值对象通常是用来度量和描述事物。我们可以非常容易的对其进行创建,测试,使用,优化和维护,所以在建模时,我们尽量采用值对象来建模。
当你只关注某个对象属性时,该对象便可以是一个值对象,为其添加有意义的属性,并赋予它相应的行为。我们需要把值对象看做一个不可变的对象,所以不要给它任何身份标识。
当你考虑一个对象是否能够作为值对象时,你需要考量它是否有一下特征:
- 它度量或者描述了领域中的一件东西
- 它可以作为不变量
- 它将不同的相关的属性组合成了一个概念整体
- 当度量和描述改变时,可以用另外一个值对象予以替换
- 它可以与其他值对象进行相等性比较
- 它不会对协作对象造成副作用
有的时候值对象要以实体的身份进行持久化。换句话说,某个值对象实例会单独占据一张表中的一条记录,而该表也是为值对象设计的,换句话讲它甚至拥有主键列。
这个时候你需要从领域模型的角度考虑问题,而不是从持久化的角度考虑。考虑它是否满足值对象的特征。
在写代码时,值对象采用final修饰。我们最容易忽视的是值对象的无副作用属性,值对象的方法只是用于产生属性,并不会改变其属性。例如String类,它的所有改变自身属性的方法都是返回一个新的String对象实现。
区别
实体的唯一性和可变性的特性将其与值对象区别开来。
如果你无法区分你只需要回答下列问题:
1.当前所建模的概念是描述领域中的一个东西还是只是用于描述和度量其他东西呢?
2.如果该概念起描述作用,那么它是否满足先前所提到的值对象的几大特征?
3.将该概念建模成实体是不是因为它拥有唯一标识,我们关注的是对象实例的个体性,并且需要在其整个生命周期跟踪其变化。
如果你的回答是“描述,是的,不是”,那么你应该建模成值对象。
上一篇:《DDD之领域,子域,限界上下文》
下一篇:《DDD之如何合理设计一个聚合》