c#获取对象的唯一标识_DDD领域驱动设计实战 - 创建实体身份标识的常用策略
從簡單到復雜依次為:
3.1.1 用戶提供唯一標識
這時用戶將輸入一些可識別的數值或符號,或從已有標識中選其一,然后創建實體對象。這是一種非常簡單方案,但也可能變得復雜。
由于需用戶自己生成高質量的標識。所以標識可能唯一,卻有可能是不正確的。
缺陷
多數情況下標識不可變,用戶無法修改標識。但有時賦予用戶修改標識值的權限有好處。
例如,若將Forum和Discussion的名字作為唯一標識,那么發生拼寫錯誤時怎么辦,或用戶之后想采用新名字怎么辦?
- Forum名字拼寫錯誤,Discussion名字長度小于所要求的
要改變這些標識值需要多大代價?雖然用戶提供的身份標識看似一種節約成本的做法,但也有可能不是。此時我們還可以依賴用戶來提供唯一的、正確、穩定的對象標識嗎?
為避免上述問題,需重新設計。開發需采用無故障的方法來保證用戶輸入的確是唯一的身份標識。雖然基于工作流的標識審批過程,對于高吞吐量的領域并無多大幫助,但是它對于生成具有可讀性的身份標識來說卻是必需的。如果這種方式生成的標識會在將來繼續使用,而工作流也是可能的,那么添加一個額外的階段來保證身份標識的質量是值得的。
通常將一些用戶輸入作為實體屬性,這些屬性可用于對象匹配,但并不將這樣屬性作為唯一身份標識。
簡單屬性可作為實體狀態的一部分, 他們更容易修改,在這種情況下,我們需要考慮另外的方法來生成實體的唯一標識。
3.1.2 應用程序生成唯一標識
很多可靠方法可自動生成唯一標識,但若應用程序處于集群環境或分布在不同計算節點,就要注意了!
有些方法可以生成完全唯一的標識,比如UUID或者GUID。
以下是生成唯一標識的另一種方法,其中每一步生成的結果都將添加到最終的文本標識中:
以上可產生一個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:
還可加密所生成的偽隨機數
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中text里的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());}}將唯一標識的生成放在資源庫中是一種自然的選擇。
持久化機制生成唯一標識
若從DB獲取一個序列值(Sequence)或遞增值,結果總是唯一。根據標識所需范圍,數據庫可生成2字節、4字節和8字節的唯一標識。在Java中的這些大小整數分別可表示
- 32,767
- 2,147,483,647
- 9,223,372,036,854,775,807
種不同標識值。
缺陷
性能。
從DB獲取標識比APP生成慢得多。一種解決方法是將數據庫序列緩存在APP,比如緩存在資源庫。
這固然是一種好方法,但若服務器節點需重啟,那么將失去很大一部分標識值區間。若丟失區間無法接受或只需相對較小標識值(2字節整數),這緩存機制便不實用,也沒必要。當然可以找回丟掉的標識值區間,但可能引入新麻煩。
如果可使用延遲生成,那緩存標識便不是問題。以下是如何使用Hibernate和Oracle的序列來生成標識:
在采用MySQL的自增列時配置如下
這種方式的性能是很好的,同時配置Hibernate映射也是簡單的。
3.1.3 另一個限界上下文提供唯一標識
若另一個限界上下文用于給實體標識賦值,那需要對每個標識進行查找、匹配和賦值。
最重要的是精確匹配。此時用戶需提供一或多種屬性,比如賬戶、用戶名和E-mail地址,以精確定位需要匹配的結果。
通常匹配的輸入是模糊的,導致多個查詢結果,此時用戶需要手動選擇,如圖
- 從外部系統中獲取需要查找的唯一標識。用戶界面中可顯示唯一標識(本圖),也可不顯示
用戶輸入了模糊查找信息,通過調用外部限界上下文的API,返回的結果可能是0、1或多個匹配對象。接著用戶要在結果中選擇某特定對象。所選對象的身份標識將作為本地標識。外部實體的一些額外屬性也可能被復制到本地實體。
缺陷
對象同步可能是個問題。外部對象的改變將如何影響本地對象?如何知道所關聯的對象已經改變了呢?
可通過事件驅動架構和領域事件解決。本地限界上下文訂閱外部系統中的領域事件,當本地上下文接收到外部系統的事件通知時,它將相應更新本地對象。有時同步事件可能由本地上下文發出,外部系統在接受到該事件時同樣會做相應的更新操作。
要達到這樣的目的并不容易,但這樣做能夠創建出更加具有自治性的系統。可將對象查找限定在本地對象中。這并不是說將外部對 象緩存在本地系統中,而是將外部概念翻譯成本地限界上下文中的概念。
這是最為復雜的標識創建策略。要維護本地實體,我們不但需要考慮由本地 領域行為所導致的改變,還需要將外部系統也考慮在內。所以在使用這種策略時,應持保守態度。
參考
- 《實現領域驅動設計》
總結
以上是生活随笔為你收集整理的c#获取对象的唯一标识_DDD领域驱动设计实战 - 创建实体身份标识的常用策略的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python写算法求最短路径,Pytho
- 下一篇: linux qt5.9交叉编译,QT5.