DDD领域驱动设计实战 - 创建实体身份标识的常用策略(中)

简介: DDD领域驱动设计实战 - 创建实体身份标识的常用策略(中)

3.1.2 应用程序生成唯一标识

很多可靠方法可自动生成唯一标识,但若应用程序处于集群环境或分布在不同计算节点,就要注意了!

有些方法可以生成完全唯一的标识,比如UUID或者GUID。

以下是生成唯一标识的另一种方法,其中每一步生成的结果都将添加到最终的文本标识中:


计算节点的当前时间,以毫秒记

计算节点的IP地址

虚拟机(Java)中工厂对象实例的对象标识

虚拟机(Java)中由同一个随机数生成器生成的随机数

以上可产生一个128位唯一值,可通过一个32字节或36字节的16进制数的字符串表示。在使用36字节时,可用连字符-来连接以上各步骤生成结果,比如f36ab21c- 67dc-5274-c642-lde2f4d5e72ao在不用连字符时,即为32字节。但这都是一个很大的唯一标识,且不具可读性。

在Java里,以上方法被标准的UUID生成器所替代(自从Java 1.5),对应java.util.UUlD类。该类支持4种不同的唯一标识生成算法,这些算法都基于Leach-Saiz变量。使用JavaSE API,可简单生成伪随机的唯一标识: String rawld = java.util.UUID.randomUUID().toString();

以上代码使用了第4类算法,该算法采用高度加密的伪随机数生成器,而该生 成器又基于java.security.SecureRandom生成器。第3类算法采用对名字加密的方 法,它使用了java.security.MessageDigest类。我们可以通过以下方式生成一个基于名字的UUID:

String rawld = java.util.UUID.nameUUIDFromBytes( 
  "Some text'*.getBytes())
  .toString ();

还可加密所生成的伪随机数

SecureRandom randomGenerator = new SecureRandom();
int randomNumber = randomGenerator.nextInt();String randomDigits = 
  new Integer(randomNumber).toString();
MessageDigest encryptor = MessageDigest.getlnstance(nSHA-l"); 
byte[] rawIdBytes = encryptor.digest(randomDigits.getBytes());

接下来将 rawIdBytes 数组转换成16进制数的字符串表示即可。可先将随机数转换成字符串类型,再将该字符串传给UUID的nameUUlDFromBytes。工厂方法。


UUID是一种快速生成唯一标识的方法,它不需要与外界交互,比如持久化机制。即便需要在1秒钟之内多次创建实体,UUID生成器也可应付。对有性 能要求的领域来说,可缓存UUID实例,使其在背后不间断地向缓存中填入新UUID值。如果缓存中的UUID实例由于服务器重启而丢失,在不同唯一标识间不会存在缺口,因为所有标识都是随机,因此重新向缓存中填UUID值并不会对系统造成影响。


对于如此大的唯一标识,从内存使用角度看可能不实际。可采用由持久化机制生成的8字节长标识或甚至4字节长标识就够了。


通常并不会在用户界面上显示UUID: f36ab21c-67dc-5274-c642-lde2f4d5e72a,若UUID可隐藏或可使用可读性的引用技术,那便可使用完整UUID。

比如,可通过E-mail或其他消息机制发送具有URI的超媒体资源。此时,超媒体链接中的文本部分便可以用于隐藏UUID,就像 HTML中<a>text<a>里的text。

根据UUID能够表达实体的唯一程度,可只使用UUID的一部分标记实体。在聚合(10)边界内,可将缩短后的标识作为实体的本地标识。


本地标识表示在同一聚合中,一个实体的标识只需和该聚合中的其他实体区分即可。

Aggregate(聚合)是一组相关对象的集合,作为一个整体被外界访问,聚合根(Aggregate Root)是这个聚合的根节点。聚合根(Aggregate Root)的实体则需要全局的唯一标识


对于自己创建的标识生成器,依然可用UUID的某部分。 比如对于APM-P-08-14-2012-F36AB21C,该25字节的标识表示在敏捷项目管理上下文(APM)中创建的一个Product,创建时间为2012年8月14日。额外的F36AB21C唯一标识

即为UUID的第一部分,该部分用于区分同一天所创建的不同Product。

这样的标识


满足可读性要求

又提供很好的全局唯一性

用户并非唯一受益者,当这样的标识从一个限界上下文传到另一个时,开发者可立即识别实体源头。对于SaaSOvation来说,还可以向标识中加入租户信息。将这样的标识作为String来维护并不是一个好办法,此时使用一个值对象更加合适:

String rawId = "APM-P-0 8-14-2012-F36AB21C" ;
// 即将生成 Productld 
productld = new Productld(rawld);
Date productCreationDate = productld.creationDate();

客户可询问标识的细节信息,比如一个Product的创建时间,就已包含于标识。客户无需知道原始的标识格式,此时聚合根Product可通过creationDate方法向外界暴露该Produc啲创建时间,而客户并不 需要知道对创建时间的获取细节。

public class Product extends Entity {
  private ProductId productld;
  ...
  public Date creationDate() {
    return this.productld().creationDate();
  } 
  ...
}

也可通过第三方类库框架来生产实体的唯一标识。比如Apache Commons的Commons Id组件,该组件提供了5种标识生成器。

有些持久化存储,比如Redis也可生成唯一标识。


对于程序生成的标识来说,什么样的对象可以作为创建标识的工厂对象呢? 对于聚合根的唯一标识,我们可以采用资源库来生成唯一标识:

public class HibernateProductRepository implements ProductRepository (
  public Productld nextidentity() {
    return new Productld(
      java.util.UUID.randomUUID()
        .toString()
          .toUpperCase());
  }
}       

将唯一标识的生成放在资源库中是一种自然的选择。

目录
相关文章
|
3月前
|
存储 测试技术 数据库
仓储设计实现问题之聚合实体在DDD中定义如何解决
仓储设计实现问题之聚合实体在DDD中定义如何解决
52 0
|
4月前
领域驱动设计问题之将多个服务类都直接修改单据实体的状态如何解决
领域驱动设计问题之将多个服务类都直接修改单据实体的状态如何解决
|
4月前
|
架构师 测试技术 领域建模
项目管理问题之实体、命令、事件之间有什么关系
项目管理问题之实体、命令、事件之间有什么关系
|
消息中间件 缓存 前端开发
DDD 实战 (4):战略设计之系统上下文和限界上下文
DDD 实战 (4):战略设计之系统上下文和限界上下文
DDD 实战 (4):战略设计之系统上下文和限界上下文
|
设计模式 搜索推荐 领域建模
DDD领域驱动设计实战(一)-领域模型、子域、核心域、通用域和支撑域等基本概念
DDD领域驱动设计实战(一)-领域模型、子域、核心域、通用域和支撑域等基本概念
1036 0
|
供应链 定位技术
验证限界上下文的原则
验证限界上下文的原则
验证限界上下文的原则
|
缓存 Oracle Java
DDD领域驱动设计实战 - 创建实体身份标识的常用策略(下)
DDD领域驱动设计实战 - 创建实体身份标识的常用策略(下)
185 0
DDD领域驱动设计实战 - 创建实体身份标识的常用策略(下)
DDD领域驱动设计实战 - 创建实体身份标识的常用策略(上)
DDD领域驱动设计实战 - 创建实体身份标识的常用策略(上)
274 0
DDD领域驱动设计实战 - 创建实体身份标识的常用策略(上)