翻譯:Annhy

Mapping to Relational Databases
對應至關聯式資料庫

 

 


下一頁


應用程式運行時,需要用到許多不同種類的基礎系統,資料來源層 (Data Source Layer) 便扮演著與這些基礎系統溝通的角色。首要的問題,便是與資料庫的溝通 -- 對當今大多數系統而言,指的是關聯式資料庫。當然還有些資料儲存於較舊的格式,像是早期大型主機 (mainframe) 的 ISAM 與 VSAM 檔案,但令當今大部分系統開發者所操心的,則是關聯式資料庫。

SQL 的出現,是關聯式資料庫成功最大的原因之一,它是與資料庫溝通時最標準的語言。儘管 SQL 中充滿了各種惱人又複雜的供應商自訂功能,但它的核心語法是共通且易懂的。

架構樣式 (Architectural Patterns)

第一個部分是由架構樣式所組成,它們決定了領域邏輯 (domain logic) 與資料庫溝通的方式。你在此時所做的抉擇,會廣泛地影響你的設計,並導致難以重構,因此你必須花一些注意力在它上面。你設計領域邏輯的方式,也會嚴重地影響你的抉擇。

雖然 SQL 已被廣泛用於企業軟體,但使用時仍有些陷阱。許多的應用程式開發者並不很懂 SQL,以致於他們無法寫出有效率的查詢與指令。儘管有許多技術,將 SQL 嵌入其他程式語言中,但這樣多少會有點難用。存取資料時,還是用適合於開發應用程式時所用語言的機制會比較好。資料庫管理者 (DBA) 也會希望,能夠為那些存取資料表 (table) 的 SQL 指令親自捉刀,這樣他們便可以知道,能夠把它調整到多好的程度,以及該如何安排索引。

基於這些理由,將 SQL 存取動作從領域邏輯中分離出來,並將之放入其他的類別中,是個明智的做法。將這些類別組織起來的好方法之一,是以資料表的結構做為類別的基礎。於是你會為資料庫的每個表格建立一個類別,這些類別便成為資料表的 Gateway (466) (閘道器)。應用程式的其他部分,則不需要知道任何有關 SQL 的事情,而且所有存取資料庫的 SQL 也都易於尋找。專職於資料庫的開發者,可以有一塊明確的地方來進行工作。

你在使用 Gateway (466) 時,可以有兩種主要的做法。最常見的做法,是為查詢所傳回的每列資料,產生一個物件實例 (圖 3.1)。這個 Row Data Gateway (152) 是一種順乎自然又符合物件導向式思考資料的方法。

 
Person Gateway
lastname
firstname
numberOfDependents
insert
update
delete
find(id)
findForCompany(companyID)

圖 3.1: Row Data Gateway (152) 對於查詢傳回的每一列,會有一個對應的物件實例。

許多作業環境提供了 Record Set (508) (紀錄集),那是一種表格 (table) 及列 (row) 的泛化資料結構,用來模仿資料庫表格的特性。由於 Record Set (508) 是一種泛化的資料結構,作業環境可以在應用程式的許多部分使用它。GUI 工具常常會有以 Record Set (508) 運作的控制項。如果你有 Record Set (508),則對於資料庫中的每個資料表,你只需要單一的類別。這種 Table Data Gateway (144) (圖 3.2) 提供了查詢資料庫並回傳 Record Set (508) 的函式。

 
Person Gateway
 
find(id) : RecordSet
findWithLastName(String) : RecordSet
update(id, lastname, firstname, numberOfDependents)
insert(lastname, firstname, numberOfDependents)
delete(id)

圖 3.2: Table Data Gateway (144) 對於每個資料表,會有一個對應的物件實例。

即使只是一些簡單的應用程式,我都會傾向使用這些閘道器樣式的其中之一。想要確認的話,只要稍微瞄一下我寫的 Ruby 與 Python 的文稿 (script) 就可以了。我發現將 SQL 與領域邏輯乾淨地分割,是非常有幫助的。

事實上 Table Data Gateway (144) 非常合適於 Record Set (508),它無疑是你使用 Table Module (125) 時必備的選擇。Table Module (125) 是你想要整頓預存程序 (stored procedure) 時可用的樣式。許多開發者喜歡透過預存程序來做所有的資料庫存取,而不是經由清楚寫下的 SQL。這種情形下,你可以把預存程序想像成,在為資料表定義 Table Data Gateway (144) 一樣。我通常仍舊會用一個存於記憶體中的 Table Data Gateway (144),來包裝預存程序的呼叫,這樣就可以把呼叫預存程序的特殊技巧封裝起來。

如果你使用 Domain Model (116),則會有一些額外的選項可以使用。當然你可以拿一個 Row Data Gateway (152) 或 Table Data Gateway (144) 來與 Domain Model (116) 一起使用。然而就我自己的經驗來看,這樣所產生的間接性不是太多就是太少。

在簡單的應用程式中,Domain Model (116) 是一種不複雜的構造,而且確實非常符合資料庫結構,它把每個資料表對應至一個領域類別 (domain class)。這樣的領域物件 (domain object),通常只有中等複雜度的事務邏輯。在此狀況下,讓每個領域物件負責從資料庫中載入與儲存是合理的,這就是 Active Record (160) (圖 3.3)。另一種思考 Active Record (160) 的方式,是你以一個 Row Data Gateway (152) 開始,然後將事務邏輯加入此類別中,尤其是當你在許多個 Transaction Scripts (110) 中看到重複的程式碼時。

 

圖 3.3: 在 Active Record (160) 中,customer 領域物件知道如何與資料表互動。

在這種情況下,Gateway (466) 所增加的間接性並沒有什麼價值。當事務邏輯漸趨複雜,而你開始傾向使用較複雜的 Domain Model (116) 時,Active Record (160) 這種簡單的解法便開始失靈。領域類別與資料表間的一一對應,在當你把事務邏輯分解為較小的類別時開始失效。關聯式資料庫無法掌控繼承,因此在套用 Strategy [Gang of Four] 和其他精巧的物件導向樣式上變得很困難。當事務邏輯開始愈來愈多時,你會希望能夠在不需隨時與資料庫保持連線的情況下測試它。

當你的 Domain Model (116) 內容變得複雜時,所有的力量都把你推向間接性。這種情況下,Gateway (466) 可以解決一些問題,但它仍舊留給你 Domain Model (116) 與資料庫架構連結的問題。結果便會產生一些從 Gateway (466) 的欄位到領域物件的轉換,而這些轉換會讓你的領域物件變得複雜。

較好的做法,是經由你的間接層負責整個領域物件與資料表的對應,將 Domain Model (116) 從資料庫中完全獨立出來。這個 Data Mapper (165) (圖 3.4) 掌控了所有資料庫與 Domain Model (116) 之間的載入與回存,並允許它們兩者的運作可以非常獨立。這是資料庫對應架構中最複雜的,但它的好處是可將兩層完全分離。

我不建議以 Gateway (466) 當作 Domain Model (116) 的主要永續性儲存機制。如果事務邏輯很簡單,而且你的類別與資料表的符合度很高,Active Record (160) 是很簡單的做法。如果你有些更複雜的東西,則 Data Mapper (165) 就是你所需要的。

這些樣式並不是完全互斥的。我們大部分討論的是主要的永續性儲存機制,指的是你如何將存於某些記憶體格式中的資料存入資料庫中。這時,你只需在這些樣式中擇一使用即可,你不會想要把它們混用,因為這樣會看起來很混亂。然而,如果你用 Data Mapper (165) 作為主要的永續性儲存機制,你有可能會用 Gateway (466) 包裝資料表或其他被視為外部介面的服務。

 

圖 3.4: Data Mapper (165) 將領域物件與資料庫彼此隔開。

在這裡以及對那些樣式的討論中,我傾向於使用 "資料表" 這個字。然而,這些技術大部分都可以良好的運用於檢視表 (view)、封裝於預存程序中的查詢以及常見的動態查詢。只可惜沒有一個泛指資料表/檢視表/查詢/預存程序的常見術語,所以我用 "資料表",因為它象徵表格式的資料結構。我常常把檢視表想成虛擬的資料表。當然,SQL 語法也是這樣對待它們,用來查詢檢視表的語法,是與查詢資料表相同的。

顯然,檢視表和查詢會使修改動作變得更複雜。通常你無法直接對一個檢視表做更改,但又確實需要更改位於底層的資料表。這種情況下,用一個合適的樣式封裝檢視表/查詢,是將更改邏輯放在同一處的好方法,這樣可讓檢視表的使用上變得更簡單可靠。

用這種方式來使用檢視表與查詢的問題之一,是當開發者不清楚檢視表是如何組成時,它有可能導致令人驚訝的不一致性。他們可能會對兩個不同的結構做更改,但它們卻更改了相同的底層資料表,導致第二個更改動作會蓋過第一個。假設更改的邏輯有確實做過適當的檢查,你應該不會因此得到不一致的資料,但你還是有可能讓你的開發者感到訝異。

我應該也表揚一下最簡單的永續性做法,它甚至也可以用於最複雜情況的 Domain Model (116) 上。自從物件時代的早期開始,許多人便體認到,在物件與關聯性之間存在著根本上的不協調。因此,後來便花費了大量的努力在物件導向資料庫 (object-oriented database) 上,它從本質上把物件導向的典範帶到磁碟儲存空間上。在物件導向資料庫上,你不必煩惱該如何對應。你操作一個由物件互相連結所組成的大型結構,而由資料庫負責算出何時將物件從磁碟中取出或置入。同時,你可以把許多修改動作組成一次交易,並允許分享資料的儲存區。就程式員看來,這像是一個由磁碟儲存空間所提供的無限量交易空間。

物件導向資料庫最主要的優點是它們提高了生產力。儘管我並不清楚任何經過實驗的測試數據,但有些馬路消息認為,對應至關聯式資料庫的工作量,大約是整個寫程式時的三分之一,這是在維護程式時所仍需持續付出的花費。

然而,大多數的專案並沒有使用物件導向資料庫,阻止它們的主要原因是風險。關聯式資料庫是已被充分了解並證實可行的技術,並且已由大型供應商支援許久。SQL 為各類的工具提供了一個相對穩定的介面。(如果你在考慮關於效率的問題,我只能說我從未看過任何最終的數據,顯示物件導向的效率打敗了關聯式系統。)

即使你無法使用物件導向資料庫,如果你有用到 Domain Model (116),你也應該慎重考慮是否需要購買物件/關聯對應 (O/R mapping) 的工具。雖然本書所提到的樣式,教你許多關於如何建造 Data Mapper (165) 的知識,但它仍需要付出大量的努力。工具的供應商在這個問題上花了許多年的時間,因此商業化的物件/關聯對應工具,會比一般狀況下你用手工做出來的東西來得精巧。由於這些工具並不便宜,你必須將工具的價錢,與自行撰寫及維護此物件層所需的預估成本做比較。

現在有一種趨勢,是藉由提供一種中間層,讓你以物件導向資料庫型式操作關聯式資料庫。 JDO 就在 Java 世界中扮演這樣的角色,但現在還不到告訴你它們成果的時候。我對它們還沒有足夠的經驗,因此無法在本書中下任何的結論。

然而,即使你買了工具,注意這些樣式仍是個好主意。好的物件/關聯工具,提供你許多對應至資料庫時的選項,而這些樣式會幫助你,瞭解何時使用不同的選擇。不要認為工具能把所有困難的事情解決掉,它會給你很大的進展,但你仍會發現使用物件/關聯工具並做調整,可以解決一些微小但重要的工作。

 


下一頁