自救必看三大準則

ARCA架構:以 ACTIVE RECORD 為軸心的架構

越來越多的framework本身提供Active Record實作。
不管是Rails的Active Record,還是Laravel的Eloquent,
這些Active Record實作的功能越來越豐富而強大。
它讓開發速度更快了,但也因為太多功能塞在一起,容易寫出極度肥胖的萬能類別。
該怎麼使用這種萬能類別,又不至於讓它胖到難以維護呢?
我從工作中整理出了稱之為ARCA的架構。今天來跟各位分享。
註:本文程式碼多以Laravel框架舉例。但是相關概念通用在任何擁有Active Record的語言/框架。

簡介

ARCA全名「Active Record Centered Architecture」,
也就是「Active Record中心架構」,以Active Record為主軸的軟體架構。
ARCA試圖在享受Active Record帶來的開發速度的同時,拉出一定的架構。
這套架構適用於中型以上(連續開發3個月以上)專案,不適用於小型專案(像是餐廳官網)。

ARCA架構

ARCA架構由幾個元素組成,檔案結構看起來如下(以MVC舉例):
一個元素會是一個資料夾,或是一個檔案(類別)。
/View資料夾
/Controller資料夾
/Model資料夾
…./Entity Model資料夾
……../Entity類別
……../Repository類別
……../Presenter類別
……../Form類別
…./Service資料夾
……../Service類別
…./Operation Model資料夾

Entity Model資料夾

擺放與一種entity相關概念的程式碼。
將一種entity相關的程式碼放在同一個資料夾。

Entity類別

entity是公司在乎的商業領域(domain)中,代表現實世界的一種事物。
常見的entity包含訂單、商品、顧客、禮券、文章。
通常會代表現實生活中的一種事物、需要具備ID。
在ARCA中負責直接繼承Active Record的類別。
這是ARCA架構的重點所在。遵照少數的類別命名、資料表命名慣例(convention),
直接賦予這些entity類別多種能力:
  • 物件property存進資料庫的相關操作(CRUD)
  • entity間的relationship
  • 對資料庫取出的值做適當轉換
而開發者所要做的,只是寫好類別定義與幾個函式而已。
也正是Active Record的多功能,讓entity的建立如此迅速。
公司domain所需的商業行為,與entity相關的動作都是寫在此處。

Repository類別

封裝query logic,提供取得entity(一或多個)的不同方法。
不論是Rails的Active Record還是Laravel的Eloquent,都有提供查詢(query)的功能。
舉例來說,想要拿出新舊排序過的精選文章,可以這樣寫:
但是這幾行程式碼很可能常常用到,會到處重複。
這時就可以用repository封裝這段code:
以這段封裝來講,不但讓controller內的code可讀性提昇,同時還提供取出不同數量的參數功能。
ARCA內的repository都依賴Active Record提供的query實作,
所以建立一個abstract class再讓所有repository都繼承它是一個好主意。
參考這份EloquentRepositoryArticleRepository實作。

Presenter類別

封裝與business logic無關,只與呈現(presentation)相關的logic。
entity相關的名稱、金額、日期,常常在不同地方有不同呈現格式。
將這些寫成entity的function可不是好主意。
這類presentation logic可以獨立成presenter類別。
(或稱View Objects…。注意:軟體名詞在不同社群的用法極度混亂。)
概念上只是將entity塞進presenter,也就是decorator pattern
實作上可以依不同語言、框架的特色發揮創意。
像是這套善用了PHP特性,讓presentation 相關的code明確分離。
Presenter長這樣:
接著可以從entity直接使用presenter實體,呼叫presentation logic。

Form類別

處理使用者從HTTP表單填入的資料,封裝驗證(validation) logic。
web application少不了大量的運用html表單與使用者互動。
針對user input的驗證 logic(例如字串長度、日期、金額大小)可以抽出來成為Form類別。
(在Laravel 5稱為Form Request,在Rails社群稱為Form object
原理上來說,就是將表單傳來的參數傳給Form類別,在裡面驗證過後才決定是否執行下一步驟。
舉例來說,這件事若在controller內進行,看起來就像:
而Form類別的實作則依照語言/框架的不同各自發揮。

Service資料夾

擺放跨domain概念的程式碼。
多個service類別會放在此資料夾。

Service類別

概念上不屬於任何entity的business logic可以獨立成service。
通常是施加在多個entity上(來自不同domain)的複雜行為。
舉例來說,計算訂單的折扣與售價,通常會涉及訂單與禮券,並且運算邏輯較為複雜:
使用時,將entity傳進去即可:

Operation Model資料夾

圍繞某種行為概念的程式碼可以整理起來、放在一起。
當service開始依賴其他service時,代表這項行為開始變得複雜、開始造成理解的困難。
試著看清是哪個概念(這很不容易)、以它命名(也不容易),建立出Operation model。
舉例來說,電子商務公司提供報價會同時涉及「禮券折扣」、「出貨日期」、「總金額」的計算。
也就是QuotationService(報價)會用到DiscountService(計算折扣金額),
DueDateService(計算出貨日期), PriceService(計算總金額)。
這時可以將它們從Service資料夾移出、建立一個Quotation資料夾、再全部丟進去。
丟進去之後可以進行適當的refactoring,增加這幾個類別間的cohesion,降低之後理解的困難。
也可以進一步使用Facade Pattern封裝這些類別,建立一個統一對外的接口,
降低其他地方的程式碼與這個Operation Model的coupling。
以上面報價的例子來說:

結語

討論架構時,通常只討論概念,不將檔案結構列入討論。
我認為那樣的討論讓很多人有看沒有懂,所以將檔案結構一併納入ARCA說明了。
ARCA不是一種理論或是無敵的架構,只是一種用來搭配Active Record,
將常用元素(或說是Design Pattern)組合起來的一種方式而已。
可以將ARCA架構視為一種適合新創公司快速開發的、通用的中型基本架構。
隨著情況不同,做出調整、增減元素(例如好用的Factory類別)即可。
有任何疑問,或是有推薦的好用Design Pattern,覺得適合加進ARCA嗎?
歡迎在下方留言,或是在 Twitter @howtomakeaturn 和我討論。

Q&A

Q1: Service類別的例子怪怪的…明明可以寫進其中一種entity啊!像是這樣就可寫進訂單entity:
何時放在entity,何時獨立成service呢?
如你所說,一項行為究竟是否足夠「複雜」到需要獨立出來,的確屬於開發者的主觀判斷。
但是認定所有行為都不「複雜」的話,entity很容易變得極度肥胖喔。
Q2: 我覺得用Active Record會讓測試很難寫,有解法嗎?
這確實是Active Record的一大缺點:在測試時很難不去碰到database。
使用快速建立測資的套件,似乎是唯一能勉強接受的方法。
Ruby有factory_girl,PHP有Factory Muffin
參考看看吧。
(Photo via SuperCar-RoadTrip.fr, CC licensed)

留言

這個網誌中的熱門文章

IIS - ASP.NET 網站基本優化設定

Node.js 部署至 IIS 站台

遇見 Parameters 參數上限之大量資料寫入方法