ORM是明顯的反模式

banq發表於2014-12-02
作為Java和Ruby程式設計師與架構師的Yegor昨天發表一篇博文:ORM Is an Offensive Anti-Pattern,認為ORM是一個可怕的反模式,違反了所有的物件導向原則,撕裂了物件,將它們變成啞巴和被動的資料袋,沒有任何藉口在任何應用程式中使用ORM,無論是成千上萬的小型Web應用或企業級的基於資料表的CRUD作業系統(ORM包括Java的Hibernate/JPA,python的django,),那麼取而代之是什麼?會講SQL的物件 (SQL-speaking object)。

ORM是如何工作的
物件關聯式資料庫ORM技術或模式是使用物件導向技術如Java訪問一個關聯式資料庫,每個語言都有ORM實現,如Java的Hibernate,Ruby的active record, PHP的Doctrine, Python的 SQLAlchemy,在java中,ORM甚至被設計為標準,如JPA。

首先,讓我們看看ORM是如何工作的,。 讓我們使用Java,PostgreSQL,Hibernate。 假設我們有一個表在資料庫中,稱為post :

+-----+------------+--------------------------+
| id  | date       | title                    |
+-----+------------+--------------------------+
|   9 | 10/24/2014 | How to cook a sandwich   |
|  13 | 11/03/2014 | My favorite movies       |
|  27 | 11/17/2014 | How much I love my job   |
+-----+------------+--------------------------+
<p class="indent">

現在,我們需要為這個表產生Java應用的CRUD方式(增刪改查),首先,我們曾經一個Post類:

@Entity
@Table(name = "post")
public class Post {
  private int id;
  private Date date;
  private String title;

  @Id
  @GeneratedValue
  public int get[author]Id[/author]() {
    return this.id;
  }

  @Temporal(TemporalType.TIMESTAMP)
  public Date getDate() {
    return this.date;
  }

  public Title getTitle() {
    return this.title;
  }

  public void setDate(Date when) {
    this.date = when;
  }

  public void setTitle(String txt) {
    this.title = txt;
  }
}
<p class="indent">


在使用Hibernate操作之前,我們得建立一個session工廠:

SessionFactory factory = new AnnotationConfiguration()
  .configure()
  .addAnnotatedClass(Post.class)
  .buildSessionFactory();
<p class="indent">

工廠每次我們要使用Post物件時產生一個session,每次使用session應當如下使用程式碼包裝:

Session session = factory.openSession();
try {
  Transaction txn = session.beginTransaction();
  // your manipulations with the ORM, see below
  txn.commit();
} catch (HibernateException ex) {
  txn.rollback();
} finally {
  session.close();
}
<p class="indent">

當session準備好後,下面我們就可以從資料表中獲取所有的post:

List posts = session.createQuery("FROM Post").list();
for (Post post : (List<Post>) posts){
  System.out.println("Title: " + post.getTitle());
}
<p class="indent">

我認為這是清楚的, Hibernate是一個強大的連線到資料庫的引擎,透過執行必要的SQL SELECT請求,獲取檢索資料。 然後它建立了類Post的例項,並將資料裝入其中。 當這個物件過來時,它填滿了資料,我們應該使用getter方法將這些資料取出,比如我們使用 getTitle() 方法。

當我們想做一個反向操作,將一個物件傳送到資料庫,我們做的都基本相同,只不過以相反的順序。 我們建立類Post的一個例項 文章,然後塞進入資料,請求Hibernate儲存它:

Post post = new Post();
post.setDate(new Date());
post.setTitle("How to cook an omelette");
session.save(post);
<p class="indent">


這是幾乎是每一個ORM工作原理。 基本原則始終是相同的——ORM裝有資料的貧血物件。 我們談論的是ORM框架,這些框架與資料庫互動, 物件只有幫助我們將請求傳送給ORM框架,並理解其響應。 除了getter和setter,物件沒有其他方法。 他們甚至不知道他們來自哪個資料庫。

這是物件關係對映是如何工作的。

也許你會問,怎麼了?

ORM怎麼了?
說真的,有什麼錯嗎? Hibernate是最流行的Java庫,已經超過10年了。 世界上幾乎每一個SQL-intensive應用程式都是使用它。 每個Java教程會提及Hibernate(或者 其他一些ORM 像TopLink或OpenJPA)用於database-connected應用程式。 它實際上是一個標準, 然而我還要說它錯了? 是的。

我聲稱整個ORM背後的想法是錯誤的。 它的發明是也許OOP領域空引用之後第二大錯誤 。

其實,我不是唯一一個說這樣的事情的人,絕對不是第一個。 很多非常受人尊敬的作者已經發表關於這個主題,包括 Matinfowler的OrmHate , Jeff Atwood的物件關係對映是電腦科學的越南戰爭 ,Ted Neward的電腦科學的越南 , Laurie Voss的ORM是一種反模式,等等還有許多其他人。

然而我的觀點不同於他們所說的,儘管他們的理由是來自實踐且有效,如ORM是慢的,資料庫升級很難等,他們錯失了主要點,你能從Bozhidar Bozhanov的ORM Haters Don’t Get It文章中看到非常好的實戰回答。

ORM主要點並不是封裝資料庫互動到一個物件,釋放資料,遍歷時撕開了堅固且聚合的living organism(實體),物件的一部分保持資料,而另外一部分是在ORM引擎(session factory)內部執行,這些引擎知道如何處理這些資料,並且轉換它到關聯式資料庫,看看這張圖,它模擬了ORM如何工作:

[img index=1]

我作為Post文章的讀者,得和兩個元件打交道,1是ORM,2是返回一個砍了頭的物件,我打交道的行為應該有一個單點(操作一個元件),這個物件才是OOP,而在ORM這種情況下,我得與兩個點打交道,ORM和資料物件,甚至我們都不能稱之為物件。

因為這個可怕的和明顯地違反了物件導向正規化,我們已經有很多實際中受人尊敬的出版物都在提到這個問題,這裡舉例一些:

SQL並沒有隱藏,ORM的使用者應該會使用SQL(或者方言如HQL),看看上面案例,我們呼叫session.createQuery("FROM Post")是為了獲得所有文章Posts,即使它不是SQL,也很型別,關係模型並沒有被封裝到物件中,相反,它暴露在整個應用程式中,使用這個物件的每個人都必須與關聯式資料庫打交道,以獲得或儲存什麼,這樣ORM並沒有隱藏和封裝SQL,而是汙染了整個應用程式。

難以測試。當一些物件要與Posts文章列表互動時,它需要處理一個例項 SessionFactory 。 我們如何在測試中模擬這種依賴性? 我們必須建立一個它的模擬嗎? 這個任務有多複雜? 看看上面的程式碼,你將意識到冗長和繁瑣的單元測試。 相反,我們可以編寫整合測試並將整個應用程式連線到一個測試版本的PostgreSQL。 在這種情況下,沒有必要模擬一個 SessionFactory ,但這種測試將會相當緩慢,更重要的是,我們卻將一個並沒有和資料庫有關係的資料物件卻作為資料庫的例項物件進行測試。非常糟糕的設計。

我要再次重申。 上面ORM問題導致的後果。 ORM基本缺點是撕裂了物件,可怕和明顯違反了物件理念:一個物件是什麼

待續見下貼:


[該貼被banq於2014-12-02 11:55修改過]

相關文章