天天看點

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());
    }
}                     

将唯一辨別的生成放在資源庫中是一種自然的選擇。