01 September 2005

今天正在思考 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 方面的我不熟,就不談了。


回響

可以用 Tag <I>、<B>,程式碼請用 <PRE>