領域驅動設計,讓程序員心中有碼(五)


1      從搬磚談領域對象

  有一個古老的故事,大概是這樣的。作者問三個建築工地上的工人他們在干什么?有一個沒精打采的說,我在挖洞!而另一一個人卻說,我在蓋一座房子。還有一個人說,我在建立一座巨大的城市。不同的思維模式決定了不同的發展,十年過后,第一個工人,還是在挖洞,而第二個則成為了工頭。第三個最終卻成為了大設計師。

  在軟件開發領域,往往會使用搬磚這個詞來形容我們所開發的每個功能模塊,實際上也確實如此,如果把我們需要完成的每個項目,比作一座高樓大廈,那么在項目中所完成的各種模塊,也確實是我們在計算機世界中利用磚塊設計出來的精美建築構建。而從領域驅動的角度來說,可以把關系,類比為建築工程圖紙中使用的各種輔助線,也可以把領域驅動中所涉及的各個對象,類比成磚塊,這些磚塊,大概有兩種:一種是實體(Entity),一種是值對象(Value Object),而使用這些對象的工具,則成為服務(Service),完成的各個建築構建,被成為包或者模塊(Module).

2      關聯關系

  在介紹領域驅動設計的第三篇文章《領域驅動設計,讓程序員心中有碼(三)》中,筆者提到了UML中常用的幾種關系,而關聯關系是一種最為常見的關系。在軟件設計過程中,無所不在的關聯,有時候會讓軟件工程設計變得更加復雜。因此,在設計關聯關系時,應該讓關聯更加易於控制,這意味着需要采取下列三種措施:

  1、規定一個遍歷方向。對象與對象間,過於雙向關聯是一種低效的關系,而指定唯一的遍歷方向,將有效的減少相互的依賴,實現設計的簡化。

  2、添加一個限定符,以便有效地減少多重關聯。過於復雜的多對多關系,最終形成一個紛繁復雜難以控制的圖結構,而限定多對多關聯的遍歷方向,可以有效的簡化多對多關系為一對多關聯。

  3、消除不必要的關聯。上述兩個步驟的目的,也正是為了消除對於當前工作或模型對象的基本含義來說不重要的關聯。實際上正是為了當前模型對象的簡化。

3      實體

  在軟件開發過程中,我們通常會定義模型和實體對象,這種實體對象同樣也是領域驅動中的基本對象。按照大家的理解,通常而言,實體是指能夠與數據庫直接映射的對象。在領域驅動設計中,使用的則是更加妥當的說法:對象具有貫穿整個生命周期(甚至會經歷多種形式)的抽象的連續性。 實體標識任何事物,只要滿足兩個條件即可:一個是它在整個生命周期中,具有聯系性,二是他的區別並不是有哪些對用戶來說非常重要的屬性決定,而是通過標識來決定的。

    3.1   實體建模

  由於實體對象的基本職責是為了確保連續性,其行為應該是非常清楚並且可以預測的。因此保持實體的簡練是實現責任的關鍵。應該抓住實體的基本特征,而不要一味地過分求全求完美。對於實體而言,應該只添加對概念來說至關重要的行為和這些行為所必須的屬性。其他行為,應當轉移到與核心實體關聯的其他對象中。實體則通過協調與之關聯的其他對象來完成自己的基本職責。

3.2   設計實體的標識

  在面向對象開發中,會使用建立標識這種操作方式來實現與其他對象的區分。哪怕是在分布式系統中,同樣需要使用標識來確保標識的唯一性。可以使用具有唯一性的屬性來提供標識,也可以使用ID的方式來實現。這種ID如果使用系統自動生成,往往需要有一些手段確保生成的唯一性,尤其是在分布式系統中,更是一個非常困難的問題。經常使用的方式是使用redis或zookeeper這些中間件來生成唯一標識,還有一種常見的方案是使用twitter的Snowflake算法,這些算法就不再贅述了。

4      值對象

  值對象則不具備Entity這種明確的連續性,如果在設計系統時,將所有的對象都定義為實體對象,實際上將會極大的增加系統的復雜度,所以需要定義一些用於描述領域的某個方面,本身沒有概念標識的喜愛那個。例如,可以通過郵編對地址進行檢索,郵編的變更,對地址也可能會發生變化,那么地址就是具有連續性的實體對象。而在電子商務系統中,只需根據地址即可完成投遞,而無需確保地址的連續性,那么他就是值對象了。

  值對象,往往使用與需要通過一個模型元素的屬性來定義模型的場景,主要作為參數在對象間傳遞消息。通常是臨時對象,在操作結束后,就可以被丟棄。值對象可以作為實體的屬性,例如,一個人,是一個完整的實體,而他的名字,則是值對象。當然,也並非意味着值對象是一個單純的屬性,實際上值對象是指某一個特定概念下,具有完整意義的、通過屬性進行理解的對象。例如,地址由省、市、區、街道、郵編等綜合屬性組成,這些組成對象,實際上也是實體,他們聯系起來,就組成了值對象。      

5      服務

  在軟件設計中,並非所有的對象都需要通過標識或屬性進行區分。領域驅動設計中,使用服務(Service)來定義具有活動或動作的對象。事實上也確實如此,並非所有的對象都適合使用實體或值對象來進行建模。服務強調與其他對象的操作,是通過定義能夠為使用者做什么來實現的。也就是說,服務傾向於動詞領域,而非名詞領域。

  5.1   服務對象的基本特征

  按照領域驅動設計的說法,一個好的服務應該具有以下特征:

  1)與領域概念相關的操作,不是Entity或ValueObject的一個自然組成部分。

  2)接口是根據領域模型的其他元素定義。

  3)操作是無狀態的。操作的無狀態是指任何調用者都能使用,而無需關注實例的歷史狀態。

5.2   服務與領域層

  在領域涉及中,服務無處不在,大體上包括以下幾種不同層次。

  1、應用層:定義與應用相關的基礎服務,例如在處理資金轉賬業務時,定義一系列服務,1、包括獲取輸入,2、發送消息給領域層服務,由其完成動作的執行;3、監聽確認消息等。

  2、領域層:處理與相關的服務,例如,處理有上述轉賬業務發起的請求,例如進行結果的確認等。

  3、基礎設施層:發送消息通知。

  5.3   服務的粒度

       在概念建模中,通過控制領域層中接口的力度,可以有效的實現客戶端與實體和值對象的耦合。通過合理的模式確保接口的簡單性,將便於在大型或分布式系統中對組件進行打包的粒度控制,這實際上也是微服務架構中,服務粒度細分的理論基礎。

6      包或模塊

  模塊,是軟件工程學中自古有之的基本概念。在軟件系統設計中,經常會按照各種各樣的類別進行分解,有時候按照技術架構來分割,有時候則按照開發者的任務例如按照用例來進行細分,有的在軟件重構過程中,甚至會沿用歷史架構早期形成的模塊划分。

  在軟件工程學中,高內聚,低耦合是基本的概念,而在模塊之間的關系,成為耦合,而模塊內部的關系,成為內聚。因此,好的軟件項目,模塊之間應該低耦合,而模塊內部則應該高內聚。但是模塊的划分,跟軟件分層划分一樣,不應該僅僅只是代碼層面的划分,而應該是概念模型角度的划分。不連貫的思想或者“一鍋粥”式的模塊划分,最終只會造成系統開發的嚴重不可控。

  領域驅動設計認為,模塊,是一種非常重要的表達機制。模塊的選擇應該取決於被花費到模塊中的對象的意義。當某些對象在模塊中被創建時,實際上相當於告訴下一位開發者,這些對象間是通過模塊來實現了某種關系。

  選擇能夠描述系統的模塊,並使之飽含一個內聚的概念集合。應該基於模塊來實現概念組合的方式,從而可以向相互獨立地理解和分析這些概念。對模型進行精化,直到可以更具高層領域概念對模型進行划分,同時,相應的代碼也不會產生耦合。

7      結論

  隨着系統設計規模和復雜度的增加,模塊化變得更加重要。領域模型中的每個概念都需要在實現元素中反映出來。實體、值對象、他們之間的關聯關系、領域服務以及用於組織元素的模塊都是實現領域模型相對應的地方。實現中的對象、指針和檢索機制必須直接、清楚地映射到模型對象。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM