星期五, 7月 09, 2004

Architech and Domain

這個標題取的很大,其實不是要講這麼厲害的事。從轉行到資訊業已經過了四個月了,對我來說這是第一個接觸到開發計劃 (Project) 的進行,而且內容是偏向服務性質的。這個算是資訊業的 "本行" 吧,以前雖然寫過工程應用方面的程式,不過都是兼職的性質在寫,而且是一個人做好玩的。哪有什麼時程壓力?需求變更?現在手上這個計劃目前為止大約完成一半吧... 雖然還沒有跑完整個開發流程,現在已經有很多感想。

Java 語言的技術,不敢說自己很好,但是對絕大多數的問題大多可以解決。再往上一層,OO 觀念和design pattern ,這個還不夠好,不過自己覺得也有一點鄒型了,只差更多的經驗累積。這四個月來讓我思考最多的便是 architech 和 domain -- 打造一個 Agile 的 system。以前買的書都是 thinking in java, Design pattern Java workbook, jsp/servlet... etc. 現在開始買了一些 Domain Driven Design, The Design of Site, Agile Database Technique, Unit Test in Java.... 等等屬於比較 "上一層思考" 的書。其中 Hibernate in Action 和 Domain Driven Design 給我很大的影響。

我自己覺得真的很幸運,我們的單位選用 Hibernate,而且正當緊鑼密鼓開發的時候,就出了 Hiberante In Action 這本書。對一個 project 開發的新新手來說等於是有一個名師在指點你最好的 practice。第一次學就用最好的,而不是走偏走遠了,繞了一大圈才恍然大悟。

anyway.... 我們單位目前沒 SA / SD, programmer 要自己搞定。其實還好要自己搞,不然我一定整天跟 SA/SD吵架,而且也沒有學習的機會了,況且在時程的壓力並沒有很大的情況下,還有 "驗證" 設計問題的空間咧。話說回來,有時候像是書上還沒看到的部份 (Hibernate in Action 還沒出完, Domain Driven Design 則太厚了..) 自己會先想一些方法去實作。後來在書上看到了一樣的 implemenation 時,心裡真是爽啊!會推導出與書上一樣的結論,表示現在的自己思考回路是往正確的方向在走啊!

之前我曾經做過有關 Hiberante architech 的一些小結論。現在再整理一些東西吧:


  • Entity Entity 就是帶有 id 的 class ,有 id 表示在 business 的意義上,你會希望能夠辨別它與別人不同,而且能夠獨立的存在,不依附其他 class,像是 user, project, order... 之類的。Entity 通常都需要儲存 (persistent) 它的 state,稍後在重新 load 出來繼續工作,在 Hibernate 裡就是 PO 了,每個PO 都會有自己的 id。而且身為 Domain的 Entity,PO 理所當然要有business 的行為囉,像是 project.assignJob() 之類的method都會在 PO 出現,不要只把 PO 當做 DTO 啊 (DTO, data transfer object, 只有 getter/setter,純用來傳遞的物件)

  • Value Object (VO) VO 跟 Entity 是相對應的,它沒有 id,多與 Entity, VO 協同工作,而完成任務後,就被 garbage collect 掉了。像這樣的 class 因為物件不具識辨性,所以最好是設計成 immutable 且 side effect free (因為生成多少個都不影響功能,可以看我之前寫的 Immutable 症候群)。當它依附在 Entity 之下時,像是 user 下的 address,它也會跟著 persistent,這時在 Hibernate 裡就是用 component的方式實做。

  • Aggregate Aggregate 就是 domain 裡面相關的東西結合起來的一個class族群,例如車子是由外殼、輪胎、引掣.... 等 class 組成。裡面這些 class 可能有 Entity(車子) 也有 VO(輪胎)。而重點是 Root of Aggregate (在這裡就是車子)的觀念,這個 Root 是這個 Aggregate 的對外唯一窗口,要存取車子的第三個輪胎,一定得通過 "車子" 這個物件來使用才行。唯有這樣,車子外部的使用者才不會接觸到車子底層的實作,未來在變更時才有彈性。這也表示在設計一個 Aggregate 時,Aggregate 一定要在 domain 上有意義才行 (未來的變更一定是依照 domain 的特性去變的,跟採用的IT技術無關)。而 Aggregate 在 Hibernate 裡就是用一個 Entity (PO) 當做 root,然後對其他的子 class 做 foreign key 連結(many-to-one, one-to-many... etc) 形成一個以 root 為中心的 Object graph. 比方說以 project 當做 root,而要存取 project 所有成員的地址,就要先取得 project 之後,再利用project.getAllMemberAddresses() 的方式來讀取。而不是另外找了一堆 user object,湊一湊 user.getAddress() 了事

  • Repository repository 就是類似 DAO 一樣的元件,方便做 CRUD 和 findByXxx()的動作。但是和傳統DAO做法不同的是,並不是一個 table 一個 DAO class,而是一個 Aggregate 一個 Repository,cover 的範圍以 domain 為單位。像是車子內的這幾個class 不會有自己的 repository ,只有 root 車子才會有 CarRepository,這個與上面的 Aggregate 相互應:root class 是對外的唯一窗口。在 domain 裡,我們不可能不找車子,而直接找第三個輪胎。如果胡亂寫個 WheelRepository 的話,不通過車子任意讓人讀取,這會造成架構的混亂,未來車子要變更設計時... 頭就很大了!anyway, 在 Hibernate 裡實作 repository 非常容易,像是儲存車子資料好了:

    public class CarRepository {
    public add(Car car) {
    session.save(car);
    }
    }

    其他的子 class 的 persistence code 都不用寫了,直接寫在 *.hbm.xml 裡: 將每個 Aggregate 的連結 (one-to-many, many-to-one...) 設好 cascade="all" 或是 "save-update"... 等。 這樣就 ok 了,輕鬆愉快,不用再寫 session.save(wheel), save(engine)...etc 這類瑣碎的code 了。像這樣子 coarse 的設計,也有助於 test;只要寫個 MockCarRepository,這樣外部的 code 就不用 hit database 也能做車子資料相關的測試,也不用再 mock 一堆 MockWheelRepository, MockEngineRepository... 了,這只會增加 test 難度而已

  • Service with Domain IOC Service是統合所有流程的地方,裡面會有數個 class 協同運作,這時我們可以利用 IOC (Inverse of Control, 又稱 dependency injection) 將 CarService 設計為 :

    public class CarService {
    public CarService(CarCleanUtil cleanUtil,
    CarRepository repository,
    ChargeManager manager) {
    // some construction...
    }
    public void washCar(Long carId) {
    Car car = repository.findById(carId);
    int cost = car.washBy(cleanUtil);
    manager.charge(car.getOwner(), cost);
    repository.updateCarRecord(car);
    //.... etc
    }
    }

    一些 CarService 會使用到的相關工具,資料,或服務,全部從 constructor 丟進來,(這種做法稱constructor injection) 這樣一來你可以輕鬆切換各種 implementation,像是可以隨時變換另一種清潔工具 (CarCleanUtil),也可以更改付費方式 (ChargeManager),再者可以換掉車子資料的來源 (CarRepository)。這樣的做法在測試時更具優勢,我們可以做個 MockCarReposotory 這樣就可以任意做出各種車子的資料而不用 hit database。也可以做個 FailChargeManger 專門用來測試是錢不夠的狀況... etc. 啊?不知道什麼東西要丟到 constructor 外面嗎?試試看 test-first 吧,很快就會發現的。不過要小心,往外丟的 class 最好是在 domain上具有意義。有個簡單的方法可以檢查:替這個 class 取個 domain 裡用的名詞,如果無法用 domain裡的名詞命名,那特意分離出去就沒意義啦!因為未來會改變 implementation 一定是來自 domain 的需求。舉個例,假設目前 CarService 裡面需要寄 email 給客戶,我們把mail相關的部份拿出去變成
        public CarService(EmailUtil emailUtil) 
    ,這樣是不錯,但是 EmailUtil 太過於 technical 了,跟 domain 比較無關。在這裡 domain的目的不外乎是為了傳達訊息給客戶,我們可以改成客戶的連絡
        public CarService(CustomerContact contact) 
    然後實作一個 EmailContact 給目前的需求使用。這樣一來以後要改用手機簡訊連絡也沒問題!


還有一個漏掉的是 Factory,到目前為止我用的還不是很多(大多是用 constructor 就搞定了)所以沒什麼心得。上述這些觀念多半是從 Domain-Driven Design 這本書學來的,經過實際與 Hibernate 並用後的一些心得。Domain-Driven Design 提到的觀念 和 Hibernate 的設計理念有很多雷同之處,因此 Hibernate 可以很簡單的 fit 這樣的架構。或者我們可以這樣說:Hibernate 幫助我們完成這樣的架構。

Again, Hibernate Rocks !

0 Comments:

張貼留言

<< Home