[轉]爲何我說ORM是一種反模式

原文地址:http://www.nowamagic.net/librarys/veda/detail/2217web

 

上週我在在上討論了ORM,在那之後有人但願我澄清個人意思。事實上,我曾經寫文章討論過ORM, 但那是在一場關於SQL的大討論的上下文中,我不該該把這將兩件事情混爲一談。 所以,在本文中我將關注ORM自己。同時,我盡力保持簡略,由於從個人SQL文章中顯而易見的是:人們傾向於一旦讀到讓他們發怒的內容就會離開(同時留下一句留言,而不論他們所關注的東西是否在後面會討論到)。數據庫

什麼是反模式?

我很高興地發現Wikipedia有一個至關全面的關於反模式的列表,包括來自編程界及其以外的內容。我之因此稱ORM爲反模式的緣由是由於,反模式的做者定義了用來區分反模式和普通的壞習慣的兩個條件,而ORM徹底符合這些條件:編程

  1. 它開始的時候看起來頗有用,可是從長期來看,壞處要大過好處
  2. 存在已驗證而且可重複的替代方案

因爲第一個因素致使了ORM使人抓狂(對我來講)的流行性:它第一眼看上去像是個好主意,可是當問題更加明顯的時候,已經很難離開了。後端

這對ORM來講是什麼意思?

我想說的主要問題在於 ActiveRecord,它因爲 Ruby on Rails 而著名, 從那之後已經移植到了許多其餘語言。然而,這些問題一樣存在於其餘的ORM層,好比Java的Hibernate和PHP的Doctrine。函數

ORM的優勢性能

  • 簡單:一些ORM層告訴你它們「消除了對SQL的要求」。我至今仍然看到這種承諾在傳播。其餘一些會更加現實地聲稱它們能夠減小手寫SQL的須要,可是仍然容許你在須要的時候使用它。對於簡單的模型以及項目的早期,這確實是一個優勢:使用ORM,無疑你可以更快地開始啓動。然而,你將會走向錯誤的方向。
  • 代碼生成:使用ORM從模型中消除用戶層面的代碼,這一作法開啓了通向代碼生成的大門。經過對schema的簡單描述,「腳手架」模式能夠爲你的全部表生成一個可工做的界面。更加具備魔力的是,你能夠修改你的schema描述,而後從新生成代碼,從而消除了CRUD。一樣,這在開始的時候確實是可行的。
  • 性能「足夠好」:我沒有看到任何ORM層聲稱在性能上更加優越。很明顯,爲了代碼的敏捷性須要付出性能的代碼。若是哪裏變慢了,你老是能夠用更加有效的手寫SQL覆蓋你的ORM方法。不是嗎?

ORM的問題

1. 不充分的抽象學習

ORM最明顯的問題是它並不能徹底從實現細節中抽象出來。全部主流ORM的文檔中處處都引用了SQL的概念。其中一些介紹的時候並不會代表其在SQL中的等價物,而其餘一些則將庫看做用來生成SQL的過程函數。優化

抽象的要點在於它應該使問題得以簡化。對SQL進行抽象,同時又要求你懂得SQL,這使得你須要學習的東西成倍增長了:首先,你必須理解你正在試圖執行的SQL是什麼,而後你還要學習ORM的API,來讓它爲你編寫這些SQL。在Hibernate中,爲了完成複雜的SQL你甚至須要學第三種語言:HQL,它幾乎就是SQL(但又不徹底是),其在幕後被翻譯成SQL。.net

ORM的支持者會辯解說並不是每一個項目都是如此,並不是每一個人都須要複雜的join,而且ORM是一個"80/20"解決方案,其中80%的用戶只須要SQL中20%的功能,ORM能夠處理這些問題。我能說的是,我15年來編寫web應用的數據庫後端的經歷代表,事實並不是如此。只有在項目剛開始的時候你不須要join和本地join。在那以後,你就要優化和鞏固你的查詢。即便80%的用戶只用到SQL中30%的功能,但是100%的用戶都須要打破ORM的抽象纔可以完成工做。翻譯

2. 不正確的抽象

若是你的項目確實不須要任何關係數據功能,那麼ORM能夠很是完美地爲你工做。可是接下來你又遇到另一個問題:你用錯了了數據存儲。關係存儲的額外付出是很是高的;這就是爲何NoSQL數據要快得多的重要緣由之一。然而,若是你的數據是關係型的,那麼額外的付出就是值得的:你的數據庫不只存儲數據,它還表達了你的數據,而且能夠基於關係概念回答關於它的問題,這比你用過程代碼可以作到的要快速得多。

可是,若是你的數據不是關係型的,那麼你就是在不適當的場合使用SQL,這爲你增長了巨大且沒必要要的負擔;爲了讓問題更加嚴重,你在其上又增長了一重額外的抽象。

另外一方面,若是你的數據是關係型的,那麼你的對象映射最終會失敗。SQL是關於關係代數的:SQL的輸出不是對象,而是對於某個問題的解答。若是你的對象「是一個」X的實例,而且「擁有一些」Y,且每一個Y「屬於」Z,那麼對象在內存中正確的表達形式是什麼? 它應該是X的屬性,或者所有包含在Y中,或者/而且所有包含在Z中?若是你只獲得X的屬性,那麼什麼時候你運行查詢來得到Y呢?並且,你是想要其中一個仍是所有?現實中,答案是依賴於條件的:這就是爲何我說SQL是對於問題的回答。對象在內存中的表達形式取決於你的意圖,然而面向對象設計沒有依賴於上下文的表達這樣的功能。關係不是對象;對象也不是關係。

3. 多個查詢致使失敗

這天然的引出了ORM的另外一個問題:效率低下。當你獲取一個時,你須要哪些屬性?ORM並不知道,因此它老是取得所有(或者它要求你告訴它,可是這又打破了抽象)。開始的時候這不成問題,可是當你一次取出上千條紀錄的時候,若是你只須要3個屬性卻不得不取出所有30列,這時就產生了嚴重的性能問題。許多ORM層很是不善於推斷join,從而不得不使用分離的查詢來獲取關聯數據。如前所述,許多ORM層明確聲明效率將會有所犧牲,其中一些提供了某些機制來調整引發問題的查詢。我從過去的經歷中發現的問題代表,不多有隻須要調整單個「銀彈」查詢的狀況:應用的數據庫後端之因此死掉不是由於其中某一條查詢,而是衆多的查詢引發的。ORM缺乏上下文敏感的性質意味着它沒法鞏固查詢,相反必須藉助cache或其餘機制來進行必定程度的補償。

那麼替代方案是什麼?

但願到這裏我已經澄清ORM在設計上的一些缺陷。可是要做爲一個反模式,還須要存在替代的解決辦法。事實上有兩個取代方法:

1. 使用對象

若是你的數據是對象,那麼中止使用關係數據庫。編程界當前正在出現鍵-值對存儲的浪潮,它容許你以閃電般的速度訪問優雅的、自我包含的海量數據。沒有法律規定編寫Web應用的第一步必須安裝MySQL。對於對象的每一種表達方式都使用關係數據庫是一種過分使用,這也是近幾年SQL的名稱不太好的緣由之一。事實上,問題在於偷懶的設計。

2. 在模型中使用SQL

編程中做任何事情都只有一種正確的方式,這是一種危險的說法。然而根據個人實踐,在面向對象的代碼中表達關係模型的最佳方法仍然是模型層:將你的全部數據表示封裝在一個單獨的區域是一個好注意。然而,記住模型層的工做簿在於表達對象,而在於回答問題。提供一個能夠回答你的應用程序所包含的問題的API,儘可能保持簡潔高效。有時候,這些回答顯得格格不入,以至於看上去是「錯誤的」,甚至對於資深的OO開發者也是如此。可是,你能夠根據經驗來更好地找到其中的廣泛性,從而容許你將多個查詢方法重構爲單個。

相似的,有時候輸出會是單個對象X,它很容易表達。 可是也有時候輸出是聚合的對象表格,或者單個整數值。你要忍住將這些內容用過多抽象來包裝的誘惑,用對象自身的術語來描述。首要的是,不要相信OO可以表達任何對象和全部對象。OO自己是一種優美和靈活的抽象,但關係數據在其範圍以外,把它不能表達的東西假裝成對象是ORM的核心與真正的問題。

總結

  • ORM最初比編寫基於SQL的模型代碼更快,也更容易理解
  • 它在任何項目早期都是足夠有效的
  • 不幸的是,這些優勢在項目複雜性提高的時候就消失了:抽象被打破,開發者被迫使用並理解SQL
  • 徹底是非正式的聲明,我認爲ORM對抽象的破壞不是僅僅涉及20%的項目,而是幾乎100%。
  • 對象並不足以充分表達關係查詢的結果。
  • 關係查詢映射到對象的不充分性致使了ORM後端應用的效率低下,這些問題廣泛分佈在應用的各處,而且除了徹底放棄ORM以外,沒有簡單的解決辦法。
  • 不要對任何問題都使用關係存儲與ORM,而是更加仔細地思考你的設計
  • 若是你的數據天生就是對象,那麼請使用對象存儲("NoSQL")。它們要比關係數據庫快得多。
  • 若是你的數據天生就是關係型的,那麼關係數據庫帶來的開銷是值得的。
  • 把你的關係查詢封裝在模型層中,設計你的API從而爲應用提供數據訪問支持;拒絕過度泛化的誘惑。
  • 面向對象沒法以有效的形式表達關係數據;這是面向對象設計的一個基本限制,ORM沒法修復它。
相關文章
相關標籤/搜索