今天正在思考 Server side 架構的問題,假設一個書藉及作者的管理系統好了,依 layered 架構來設計,我們會有這麼多物件:
** View Layer (web, 以 struts 為例) CreateBookAction CreateBookForm ViewBookAction ListAuthorsAction ... ** Application Layer (or Service Facade Layer) BookService (interface) BookServiceImpl (implementation) AuthorService (interface) AuthorServiceImpl (implementation) ** Domain Layer Book Category Author ** Persistence Layer BookDAO AuthorDAO
該怎麼做 packaging 呢?有一種想法是這樣的:
**不怎麼好的包裝法 foo.hibernate.book /BookDAO foo.hibernate.author /AuthorDAO foo.spring.book /BookService /BookServiceImpl foo.spring.author /AuthorService /AuthorServiceImpl foo.domain.book /Book /Category foo.domain.author /Author foo.struts.book /CreateBookAction ... foo.struts.author /ListAuthorsAction ...
上述是 IT 人常見的想法,最上層的分別是 foo.hibernate, foo.spring, foo.domain, foo.struts 等四個 module。乍看之下很有規畫,但這種做法是依IT技術的架構來實施 package,而不是以 domain 為考量來設計。換句話說,如果你想查看 "建立Book" 這樣的一個 use case,你必需跳著好幾個 package 來看,才能夠拼湊出全貌。這樣維護性很差,而且也沒有什麼 package 存取範圍的規畫。
如果以 domain 來規畫,我想應該會比較好:
**依 domain 來規畫: * 1st module: Book foo.book/BookService /Book /Category foo.book.dao /BookDAO foo.book.impl /BookServiceImpl foo.book.web /CreateBookAction /CreateBookForm * 2nd module: Author foo.author/AuthorService /Author foo.author.dao /AuthorDAO foo.author.impl /AuthorServiceImpl foo.author.web /ListAuthorsAction
最上層只有兩個模組: foo.book, foo.author。如果要維護某個模組就簡單多了,都在同一個 package裡查就好了。而且各個模組內可以自由發揮,上面的例子中兩個模組一樣,都再分了 /, dao, web, impl 等四層。其實這是看各模組大小、複雜度、需求而定的,不見得都硬要再細分為四層。
OK,再來談談各模組內細分成四層的構想:
- 跨模組存取的東西,都要放在該模組的根目錄下。比如說 AuthorServiceImpl 想要得到 book 模組中最新的暢銷書藉,這時 AuthorServiceImpl 不該去找 foo.book/*/xxx 下面這些 package 的東西,而只能找位在根目錄的 foo.book/BookService 這個interface 幫他查。並且回傳 foo.book/Book 這個 domain 物件。AuthorServiceImpl 不該,也不能夠直接去找底層的 BookServiceImpl 或是 BookDAO 幫他做,因為這樣 Author 模組會開始 depend 其他模組的實作細節,分模組就沒意義了。
- 同模組的 interface 和 implementation 分開在不同 package 放。以 book 模組的例子就是 foo.book/BookService 和 foo.book.impl/BookServiceImpl 這兩個配對。這樣的設計是讓 BookServiceImpl 有自己的package,可以用 package scope 的技巧,不讓其他層存取它的實作。反過來說,也是要讓 BookServiceImpl 不去存取其他層的實作,像是放在 foo.book/Book、foo.book/Category 這些的 domain 物件通常會有很多的 package scope 的 method,這些 method 都是 domain 物件自己溝通用的,BookServiceImpl 只限於存取它們的 public method,而不能碰觸到太多的 domain logic 細節。
- foo.book.web/* 這一層裡都是 view 的物件,它的存取範圍要比照跨模組的方式辦理 -- 它們只能存取 foo.book/ 根目錄的物件,包含 service interface 和 domain 物件。而不該存取內部實作,像是 foo.book.dao, foo.book.impl 等等。
- foo.book.dao 這一層我想跟 impl 的用意一樣 -- 自成一個 package 來保護實作細節。而且 dao 和 domain 物件並非是一對一的關係的,像上面的 Category 就沒有自己的 DAO,而是依附在 BookDAO 之下,我想分開來放會比較清楚吧。
這是目前對於 server side package 的構想... 不知有沒有其他更好的作法?其他 Desktop/J2ME 方面的我不熟,就不談了。