1. 程式人生 > >面向物件方法中的資料庫設計

面向物件方法中的資料庫設計

   在面向物件中,是沒有資料流這一說法的。業務的完成是由物件及訊息來完成的,只有“物件流”,沒有資料流。

        只是在現實中,絕大部分的物件持久化是用關係資料庫實現的,我麼還沒有在效能上和查詢上可以頂替關係資料庫的物件資料庫。設計資料庫表的目的是不考慮所謂“流”的,考慮的是如何把物件高效地持久化。可以說,資料庫設計和之前的面向物件設計是兩個領域的問題,面向物件設計解決業務執行邏輯問題,資料庫設計解決資料高效的問題(它根本不考慮流控制的概念),它們中建通過OR-mapping的機制結合起來。如果你對此一直有疑問,那說明你試圖在設計資料庫表時考慮通過資料庫表設計表達業務邏輯問題,而不是考慮如何高效地持久化物件。

        假設,現在技術成熟到我們已經有效能不低於關係資料庫的XML持久化機制和物件查詢機制,任何物件都可以直接持久化而不需要OR-mapping,那麼還需要設計資料庫表麼?

        我想是時候談談面向物件資料庫設計的一些想法了,同時更多地講講面向物件方法裡如何設計資料庫。

       首先想說的是面向過程的資料流分析方法不是不正確,只是他不符合物件分析方法。兩者的出發點是不同的,就像向兩個不同方向錢緊的隊伍,是無法調和的。而現在很普遍的所謂面向物件設計時“先建立資料庫表,然後將其封裝,設計類”則是徹頭徹尾的錯誤!套上一個面向物件的馬甲,乾的是完全不面向物件的事情。面向過程方法下的表設計還有資料流為推導,而這種偽物件方法為了穿上面向物件的畫皮而拋棄了資料流的馬甲,卻又不按照物件分析方法行事,就更不知道資料庫表是如何推匯出來的了。

        運用最廣的Hibernate在實際中有太多的誤用,OR-mapping被僅僅當成資料庫物理表和物件之間的簡單一一對應,其本質還是先設計資料庫再設計類。再強調一次“面向物件設計解決業務執行邏輯問題,資料庫設計解決資料高效的問題”,它們本質上是兩個領域的設計,只是由OR-mapping來連線它們。要採用面向物件方法,首先要忘記資料庫的存在,採用物件分析方法,先把物件分析和定義出來,保證業務執行邏輯能夠被這些物件很好的完成。達到這一點後,再來考慮物件持久化的問題。一句資料庫的三大正規化以及效能要求來把物件持久化。注意!!這是我們設計資料庫要解決的問題是“物件資料高效持久化”,而不是業務邏輯!它不是從需求中推匯出來的。例如面向過程的設計中,一張申請表很可能被設計成一張物理表;而面向物件設計中,和可能沒有申請表這麼一張物理表,而只有“使用者資料”、“申請流程”、“申請資質”等物件表,所謂的申請物件,是在執行期由這些物件聚合而成的。

        每個物件都有自己的屬性和狀態,我們需要把這個物件的屬性和狀態儲存在資料庫中,那麼最理想最最簡單的情況,就是一個物件對應一張物理表,而物件之間的關聯關係(一對一,一對多,多對多)也可以簡答的對映成資料庫的主-外來鍵關係。但還有很多非資料庫關係需要考慮,如:繼承、聚合、依賴等。一張表如何繼承自另一張表呢?關係資料庫顯然沒有這樣的定義,這就需要用OR-mapping來完成這種語義的轉換。例如,當例項化一個子物件時,OR-mapping負責從代表了“父”物件的表中對出父物件屬性並將其賦值給子物件,並且當父物件變化時,OR-mapping需要把這一變化反映到所有子物件例項(這只是一種OR-mapping方案,也有在所有子表裡榮譽存貯父物件屬性來實現的)。再比如聚合物件,一個公司物件由公司基本資訊以及一個部門List構成,那麼在持久化這個物件時顯然需要把它分成公司表和部門表(一對多關係),在業務邏輯執行過程中操作公司物件時它們始終是一體的物件,但當CRUD這個物件時OR-mapping要負責將對物件的操作轉化為對兩張表的操作。而依賴表示了兩個物件之間相互依存的關係,當一個變化時另一個相應的要變化。這在資料庫中可以由Insert/update/delete引發的trigger來實現,但更好的做法顯然是由OR-mapping來實現這種關係管理。

        實際上我們所遇到的情況只會更加複雜,一個複雜的業務物件可能對應的資料庫中的許多張表;一些簡單的物件也可能只對應資料庫中某張表的一部分。現在我們應該明白OR-mapping的作用了,他不是負責將資料表直接翻譯成為物件那麼簡單,它負責的是將物件關係語義轉化成資料關係語義。換言之,OR-mapping負責的是“資料”和“表現”的分離,資料如何存貯和查詢是一回事(由三大正規化和效能優化考慮決定),資料如何表現又是另一回事(由業務執行邏輯和高效面向物件設計決定)。如果一個or-mapping做的足夠好,能完美支援物件關係和資料關係的轉換的話,你就可以獨立的更改物件和資料庫,之後只需要重新配置一下mapping關係即可。

        一個典型的例子,在面向物件的設計中,業務邏輯和控制邏輯通常是分離的。比如一個訂單物件,在業務執行邏輯上,除了業務資料,它還需要一些狀態屬性來標識流程控制程序;但是流程控制進行通常都不是業務資料的一部分,他只是系統的控制邏輯。在好的面向物件設計中,這種控制邏輯是可以分離出來用另一組物件來標識,再通過物件的聚合或者物件之間的依賴注入來將兩者動態編訂的。在以資料流為基礎的資料庫設計中,通常的做法是將狀態控制欄位與業務欄位設計在同一張表裡的。其結果是控制邏輯與業務邏輯被靜態繫結,這意味著兩者都不能獨立變化。只要檢視一下現在的很多系統中,當流程變化時導致要更改業務表,或當業務資料變化要改流程,就說明該設計不是一個面向物件的設計,或者至少是一個糟糕的面向物件設計。真正好的面向物件設計會分離業務邏輯和控制邏輯,在執行過程中業務物件與流程控制物件是獨立載入並在流程控制框架下動態繫結的。這意味著兩者都獲得了獨立變更的能力。在此基礎下持久化業務物件和流程控制物件的結果是必然會形成一組流程控制表和一組業務資料表,它們兩者之間是沒有靜態依賴關係的,某個流程例項的控制狀態只會存在於流程控制表而不會存在於業務表中。因此,流程控制與業務得以解耦而獨立變更。

        如果將革命進行得更徹底一些,我們甚至可以僅僅將資料庫視為儲存資料的一種手段,而放棄資料庫的約束,如主-外來鍵約束關係。在以前進行的一個專案中進行了這樣的嘗試,所有資料庫表之間均沒有主-外來鍵關係,沒有trigger,沒有約束,每張表都是獨立的,每張表都是直接物件的持久化的結果,資料庫甚至不管理物件之間的關聯關係,每張表僅由一個唯一的主鍵ID來標識物件例項。而物件之間的關係全部抽象出來用一組物件關係表來管理,一條關係表記錄表示兩個物件ID之間的一種關係,由一個物件關係管理框架來管理它們。物件關係管理框架管理物件之間的“關聯”、“繼承”、“依賴”等簡單關係,同事經過擴充套件,這些關係可以擴充套件成為更復雜的物件關係,例如可以在關係當中加入時間因素,表示某兩個物件在一定時間之內是“內聯”的或“繼承”的;也加入版本因素,表示某兩個物件產生“關聯”關係。在這個管理框架下,物件理論上擁有無限的擴充套件能力,而這種能力卻不依賴於資料庫。一張資料庫表的變化僅僅影響它對應的持久化的那個物件而已。我們完全可以在程式中動態的創造出物件關聯(從關係管理框架中加入一個關係例項)從而動態的創造出一個全新的物件,我麼也可以擴充套件關係管理框架中的關係而得到更加複雜的物件組合。

        但是徹底的革命也不是完美的,這種與資料庫關係徹底的決裂意味著我們同事放棄了資料庫的高效,完全由程式來管理物件關係不但引入了一個複雜的框架,同時整體效能也大受影響!例如,一個擁有子物件的物件在採用資料庫關係管理時,我們可以用一條SQL語句來載入這個物件;在採用物件關係管理框架以後,我們必須先得到一個物件,然後向關係管理框架諮詢它所關聯的物件的ID,然後再載入它,這個過程必然產生多條SQL呼叫。CRUD所有操作都需要額外的向關係管理框架諮詢和操作,得到擴充套件的同時犧牲了效能。但現實就是這樣,人生不如意十之八九,得到一些總是會失去一些的。好在在效能要求不太高的場合,這個框架是相當有效的!以致於在專案過程中我們從未對資料庫修改頭疼過,因為我們的程式邏輯、現實邏輯等與資料庫是無耦合的,我們使用的是一種稱為ValueObject的POJO來作為業務實體物件和顯示物件;而這個ValueObject是由關係管理框架根據物件關係將持久物件(Persistence Object)動態組合出來的。等效於我們解耦了實體物件和實體物件的持久化結果,自然的,資料庫的修改就變得輕鬆很多了。

        今天的文章裡詳細討論了面向物件方法裡資料庫的設計方法。如果你是一個面向物件的革命者或憤青,那麼你可以宣稱面向物件不需要資料庫設計(估計這是少數派)!如果你是一個面向過程的保守派,那麼你可以宣稱資料庫設計是一切的核心(估計這是多數派)!然而我們還是現實一些,站在實用主義的角度,承認:

        1、面向物件方法是非常行之有效的;資料庫設計應當圍繞著物件的高效持久化進行而不是以資料庫設計為核心;

        2、關係資料庫的高效及方便不是物件資料庫模式在短期內可以輕易達到的,我們不能因為倒髒水把嬰兒也潑掉了。

        3、最好的方法是根據實際專案對效能和擴充套件性的要求,在效能要求高的場合可以適當犧牲面向物件的特性來達到效能要求,在擴充套件性要求高的場合則可以適當犧牲資料庫效能來滿足擴充套件性。