by 零雨其蒙
2008-1-29
1.1.1 關系的映射
對象和關系處理連接的方式不同,會造成兩大問題:
1、表現方法不同:
a、對象是通過在運行時(內存管理環境或內存地址)中保存引用的方式來處理連接;
public class A{
private B b;
}
A和B的連接,通過在A的實例a中保存的B的實例b建立。
b、關系數據庫則通過創建到另外一個表的鍵值來處理連接。
表A中保存表B的ID,作為A的外鍵,來建立A和B的連接。
2、對象可以很容易的通過集合來表示多個引用。
規范化則要求所有的關系連接都必須是單值。
解決辦法:通過對象中的一個標識域來保持每個對象的關系特性,并且通過查找這些值來保持對象引用和關系鍵之間的互相映射。
1.1.2 繼承
三種模式:單表繼承,具體表繼承,類表繼承。
|
優點
|
缺點
|
單表繼承
(為繼承體系中所有類建立一個統一的表)
|
把所有內容放到一起
a、這樣修改起來容易
b、并且避免了join操作
|
1、浪費空間,因為每一行都必須為每種可能的子類保留一些類,導致很多空類。
2、它的大小將成為訪問的瓶頸。
|
具體表繼承
(為繼承體系中每個具體類建立一張表)
|
避免join操作,效率高
|
1、改變困難
a、對超類的任何改變都不得不改變所有表(還有映射代碼)
b、改變層次結構自身會帶來更大的改變
2、缺乏超類表
a、使主鍵管理十分可怕
b、引用完整性也有問題
|
類表繼承
(為繼承體系中每個類創建一張表)
|
簡單
|
使用多個join操作載入一個對象,會損失性能
|
Fowler傾向于使用單表繼承:因為易于實現和重構。
1.1.3 建立映射
Fowler建議:首先使用領域建模,然后在不超過六個月的迭代中建立數據庫模型。
如果領域設計和數據庫表同構有意義,則考慮使用活動記錄代替。(原文:If a database design isomorphic to the Domain Model (116) makes sense, you might consider an Active Record (160) instead.)
零雨其蒙注解:isomorphic,同構指的應該是結構相同,比如Employee類和Employee表擁有相同的屬性/字段。
2 Web表現層
MVC中M是指領域模型。
零雨其蒙注解:當然,看Larman的UML和模式應用中介紹是,原來模型是指數據模型,后來成立領域模型。
2.1 輸入控制器模式
MVC中的控制器:
一種理解為“輸入控制器”,主要作用為:
1、從請求消息流中讀取數據
2、將業務邏輯傳遞給一個合適的領域對象
3、將返回的數據放到HTTP會話(Session)對象中,與視圖共享。
第二種理解為“應用控制器”,主要作用為:
處理應用程序流,決定視圖應該按什么順序出現。
Fowler對輸入控制器的結論,更為準確地說法是為每個動作準備一個頁面控制器(Page Controller)。
零雨其蒙注解:應用控制器負責處理頁面流程。Struts的Aciton是頁面控制器(除了包含輸入控制器功能外,還負責創建視圖對象FormBean),ActionServlet是前端控制器,負責解釋URL來判斷調用哪個頁面控制器。配置文件起到應用控制器的作用。
2.2 視圖模式
A two-stage view (Figure 4.3) breaks this process into two stages, producing a logical screen from the domain data and then rendering it in HTML. There's one first-stage view for each screen but only one second-stage view for the whole application.
零雨其蒙注解:兩步視圖中的第一步邏輯屏幕應該是指View Object或者是Struts中FormBean這種東西,第二步應該是使用相應的自動化程序將其變成HTML或者是Swing客戶端,在Sturts中應該就是Struts自己的類,它可以將數據渲染(rendering)成HTML頁面。
3 并發
并發造成的問題:
1、更新丟失。A讀取了一張表中的數據,然后對其修改。這時B也讀取了該表中的數據,修改后將其保存。這時A才修改完,將其保存。這樣A就覆蓋了B的修改。
2、不一致讀。
并發涉及到兩個層面:
1、客戶端訪問應用服務器,使用線程或進程,一個進程中可包含多個線程,線程使用共享內存,這樣就會造成并發問題。
2、訪問數據庫,這會涉及到事務,并發訪問數據庫。
系統事務:發生在應用程序到數據庫之間
業務事務:發生在用戶到應用程序之間
解決并發的兩種方式:
1、隔離
2、不變性
預防死鎖!
2008-1-30
3.1 事務
在企業應用中處理并發最主要的工具是事務。
事務:1、事務是一個有邊界的工作序列,開始和結束都有明確定義
2、所有相關資源在事務開始和結束時都保持一致。
3、每個事務都必須保證要么全部完成,要么什么都不做。
ACID:
原子性:
一致性:在事務開始和完成的時候,系統地資源都必須處于一致的,沒有被破壞的狀態。比如一個人買啤酒,雖然少了錢,但是得到了等價值的啤酒,其擁有的總資產不變。
隔離性:一個事務,直到它被成功提交之后,它的結果對于任何其他的事務才是可見的。
持久性:一個已提交的事務的任何結果都必須是永久性的,即“在任何系統崩潰的情況下都能保存下來”。
事務資源:在技術討論時,使用事務資源表示進行事務處理的任何事物——即使用事務來控制并發過程。
長事務:跨越多個請求的事務叫長事務
幾種不一致讀的情況:
臟讀:臟讀是指事務A訪問并修改了一個數據,但還沒有提交回表中,這時事務B訪問并使用了該數據,則事務B讀到的可能就是一個“臟”的數據。依據臟數據所作的操作就很可能是錯誤的。
不可重復讀:不可重復讀是指事務A對某數據進行一次讀取后,數據被事務B訪問并修改。當事務A再一次訪問數據時,會發現跟前一次讀到的數據不一致。
幻讀:幻讀是指當事務不是獨立執行時發生的一種現象。例如事務A對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。同時,事務B也修改這個表中的數據,這種修改是向表中插入一行新數據。那么,操作事務A的用戶就會發現表中還有未修改的數據行,就好象發生了幻覺一樣。
不可重復讀的重點是修改:
同樣的條件,你讀取過的數據,再次讀取出來發現值不一樣了
幻讀的重點在于新增或者刪除
同樣的條件,第1次和第2次讀出來的記錄數不一樣
幾個隔離級別:
讀未提交:意思就是說,允許用戶讀取其他用戶未提交的事務中修改的數據。比如B修改了數據D(比如從2改成了1,此時D被修改了,成了臟數據),但是還沒有提交,在這種隔離級別下,數據庫允許此時A讀取數據D(即允許臟讀),讀完顯示給A后,B做修改的事務回滾了,也就是修改實際上沒有完成,那么A讀到的數據D(值為1)就不是真實的情況(應該還是為2)。這時A對數據D進行操作,就可能出現錯誤,比如將D(商品價格)*0.8(折扣)然后保存到另一張表(比如是用戶訂單)中,那么就保存了一個錯誤的結果。
讀已提交:意思就是說,數據庫允許用戶讀取其他用戶已經提交的事務中修改的數據。允許不可重復讀這種不一致的情況發生。這樣允許事務A未提交時,事務B對A已讀到的數據進行修改。但是事務A讀到的數據一定是已經提交的數據(因此不可能出現臟讀),而在讀未提交隔離級別下,可以讀未提交的數據。
可重復讀:鎖定查詢中使用的所有數據以防止修改(避免臟讀和不可重復讀),但是不防止插入數據,這時重新讀取時,就會讀到之前沒讀到的數據,出現了幻讀。此隔離級別允許出現幻讀。Fowler:這種幻讀出現在你向一個集合添加一部分元素而讀的人只能讀到其中一部分的時候。
可序列化:實際上是獨占方式讀取數據。
隔離級別與所允許的不一致讀:
隔離級別
|
臟讀
|
不可重復讀
|
幻讀
|
讀未提交
|
是
|
是
|
是
|
讀已提交
|
否
|
是
|
是
|
可重復讀
|
否
|
否
|
是
|
可序列化
|
否
|
否
|
否
|
2008-1-31
3.2 離線并發控制的模式
處理業務事務的模式
樂觀離線鎖
悲觀離線鎖
3.3 應用服務器并發
應用服務器自身的進程并發,這里不涉及事務。
最簡單的處理辦法是使用每會話一進程,就是每個會話都在自己的進程中運行。問題是:大量資源的耗費,因為進程是昂貴的。可以通過進程池來提高利用率,這時每個進程在一個時刻只處理單個請求,但可以在時間序列上處理來自不同會話的多個請求。問題是:必須保證每個請求結束時都釋放其占用的所有資源。
每會話一線程,即在一個進程中運行多個線程來進一步提高吞吐率。每次請求由進程中的某個線程處理。
4 會話狀態
Fowler建議:
1、傾向于使用服務器會話狀態模式,特別是在以下情況下:
a、備忘文件被遠程存儲以備系統在服務器崩潰之后仍能恢復
2、使用會話狀態模式來存放會話標識號和數據量較小的會話。
3、我個人并不喜歡數據庫會話狀態模式,建議只在以下三種情況使用
a、需要故障恢復和集群時
b、無法存儲遠程備忘文件時
c、不關心會話間數據隔離時
5 分布策略
分布式對象第一定律:不要分布使用對象。(大多數情況下使用集群)
2008-2-1
6 通盤考慮
Core J2EE分層模型
Core J2EE
|
Fowler
|
客戶層層
|
運行于客戶端的表現層(如Java客戶端)
|
表現層
|
運行于服務器端的表現層(如HTTP處理程序,服務器頁面(Java Server Page))
|
業務層
|
領域層
|
集成層
|
數據源層)
|
資源層
|
需要與數據源層通信的外部資源
|
Marinescu分層模型
Marinescu
|
Fowler
|
表現層
|
表現層
|
應用層
|
表現層(應用控制器)
|
服務層
|
領域層(服務層)
|
領域層
|
領域層(領域模型)
|
持久層
|
數據源層
|
服務層將工作流邏輯從純粹領域邏輯中剝離出來。服務層所包含的邏輯一般都特定于某個用例,并與其他一些基礎設施相互通信。(Fowler個人認為這種分離偶爾有用,但一般情況下沒用)
Mircosoft DNA分層模型
Mircosoft DNA分層模型
|
Fowler
|
表現層
|
表現層
|
業務層
|
領域層
|
數據訪問層
|
數據源層
|
DNA中的記錄集實際上充當一種各層的數據傳輸對象(DTO)。業務層能夠根據自己的方式修改記錄集,甚至可以自己創建一個新的記錄集(當然,這是極少數情況)。好處是允許表現層使用一些數據敏感的GUI控件,這些控件甚至可以操作由業務層修改了的數據。領域層通常組織成表模塊形式,而數據層使用表數據入口。
第二部分 模式
7 領域邏輯模式
7.1 領域模型模式
在應用程序中使用領域模型需要建立一個完整的對象組成的層。有的對象模擬業務活動中數據,有的對象捕捉業務使用的規則。數據和處理一般整合在一起,從而使得數據和數據之上的操作緊密聚合。
使用領域邏輯的一個常見問題是領域對象過于臃腫。這使人們開始考慮哪些職責是通用的,應當放到領域對象(例如訂單)中,哪些職責是特殊的,應當放到針對具體使用的類中,例如事務腳本或表現層本身。但是Fowler建議,不要這樣做。
使用領域模型時,首選的數據庫交互方式為數據映射器。
使用領域模型時,你可能會考慮設立一個服務層,以便給領域模型一個更清晰的API。
7.2 服務層
服務層定義了應用的邊界[Cockburn PloP]和從接口客戶層角度所能看到的可用操作集。它封裝了應有的業務邏輯、事務控制及其操作實現中的響應協調。
設計動機:
通過職責的細分來避免冗余代碼和提高可重用性。
業務邏輯:
1、領域邏輯:只與問題領域有關,如計算合同收入確認的策略等
2、應用邏輯:與應用的職責有關[Cockburn UC],如關于收入確認計算的相關事宜,通知合同管理者,集成系統等。應用邏輯有時被稱為“工作流邏輯”,但不同的人對“工作流”有不同的解釋。
零雨其蒙注解:應用邏輯層應該負責的是跟具體應用有關的責任,那具體應用是指什么呢?就是指用例中描述的業務操作功能。除了完成業務操作(如計算工資)之外,還可能包括將工資單通過發送Email的方式寄給職員,或者通知財務人員進行帳務處理。
實現方法:
1、領域外觀:服務層以領域層模型之上的瘦外觀稽核方式實現。負責實現外觀的類不包含任何業務邏輯,所有業務邏輯均由領域模型實現。
2、操作腳本:服務層由一組相對復雜的類組成,這些類直接實現應用邏輯,但將領域邏輯委托給封裝好的領域對象類。服務層客戶所能使用的操作(比如Java類中的方法)以腳本方式實現,數個腳本組成一個類,一個類定義與某一個主題相關的邏輯。每一個類組成一個應用程序“服務”,通常服務類型的名字為“XXXService”。服務層由這些應用程序服務類組成,在它們之上應當擴展出一個抽象了職責和公共行為的層超類型。
領域對象類應當只對問題域中與應用有關的部分建模,而不是應用的全部用例職責。
將應用邏輯封裝在較高的層中可以使得變更該層的實現更為容易——你可能用一個工作流引擎就做到這一點。
零雨其蒙注解:我的理解是可以直接將服務配置為工作流引擎的節點。
8 數據源架構模式
8.1 活動記錄
一個對象,它包含數據庫表或者視圖中某一行,封裝數據庫訪問,并在這些數據上增加領域邏輯。