邁向架構師的暖身運動(5):系統開發的分層概念

適才點部落正在舉行 ASP.NET 的修練大會,雖然我這篇文章與 ASP.NET 修練大會本身沒啥關係,但我認為卻是很多想做分層應用程式(Multi-tier Application)或是迷惘在為什麼應用程式總是要重寫很多次的開發人員應該要知道的概念。

適才點部落正在舉行 ASP.NET 的修練大會,雖然我這篇文章與 ASP.NET 本身沒啥關係,但我認為卻是很多想做分層應用程式或是迷惘在為什麼應用程式總是要重寫很多次的開發人員應該要知道的概念。

在架構設計上,分層 (tier) 是指將應用程式在邏輯單元上分層,而且是橫向的層次(不論是哪一層,其地位和其他層是相同的),一般應用程式至少會有三個部份:

  • 使用者介面(User Interface)。
  • 運算邏輯(Computing Logics)。
  • 資料管理(Data Management)。

這三個部份在一般的初學用書中,都是全部放在一起寫的(因為省事,而且初學者開始學時先不要被分層傷腦筋),而且這些程式都被塞在同一支程式中。久而久之,很多開發人員會認為這樣做才是對的。不過三個寫在一起會有這些問題:

  • 資料和運算邏輯混在一起寫,若資料庫更換時,所有程式都要重寫。
  • 不論是哪一部份變更,程式修改後都要先更新主程式(因為只有一支)才會生效。
  • 要擴充程式時,因為各部份的相依性(黏性)很強,容易因改錯程式而讓系統當掉。
  • 程式太過龐大,維護起來要花很多心力(含時間)。

分層的基礎概念,最早先建立在降低程式間藕合性 (Dependency) 的基礎之上,因為主程式太過肥胖,容易佔用太多記憶體,當時記憶體又是嚇死人的貴(試想,8MB 要你快五千...),因此才會發展出動態連結 (dynamic linking) 的技術,也就是現在隨處可見的 DLL。動態連結要解決的就是程式肥胖佔用大量記憶體的問題,那麼程式要怎麼樣切,才可以又滿足動態連結的技術,又可滿足程式的高度可維護性 (maintenance) 與擴充性 (extensibility)?

其實早在 1974 年,就有一個很標準的架構被提出來:Model-View-Controller (MVC),這個架構已經說明了很多應用程式的三個基本功能,由此可見,如果程式可以將 M/V/C 三個部份分別獨立切層的話,對於應用程式的可維護性與擴充性將有很大的幫助。這個觀點到現在也被廣為採用,尤其是各家資料庫與應用程式服務激烈競爭的現代,MVC 已經被很多基礎平台 (Framework,尤其是 Java) 奉為圭臬,事實也證明了,應用程式必須要有適當的分層,才具有較強大的擴充性,在後續發展與維護上要花費的心力會少一些(只要元件的基礎知識充份)。

而 tier 這個分層方法,基本上我覺得是 MVC 的另一種體現而已,因為 tier 也分為使用者介面,運算邏輯以及資料管理三個部份,只是名詞換了:

  • 使用者介面(User Interface)>> 展示層 (Presentation)。
  • 運算邏輯(Computing Logics)>> 商業邏輯層 (Business Logics)。
  • 資料管理(Data Management)>> 資料存取層 (Data Access)。

我最早接受到多層應用程式 (Multi-tier Application) 是在微軟出版社的 Designing Component-Based Application 這本書,由 Mary Kirtland 著,她是當時 COM 團隊的經理,在這本書中除了將 COM 與開發應用程式所需的觀念說明以外,也利用了一支範例方案 Island Hopper 應用程式來展示分層應用程式的觀念,以及分散式應用程式 (Distributed Application) 的介紹,以及如何使用 Visual Basic 6.0 來開發這類型的應用程式。當時的前端是 ASP,中間是 Transaction Server,後端當然就是 SQL Server 7.0,撇開它的實作不提,分層的概念在當時還在初學的我真的是很新奇的觀念,那本書被我翻到爛,現在還供在我工作室的書架上。

這裡其實有一個重要的觀念,就是應用程式可能是會不在同一台電腦上的,也就是分散在各個電腦上,這種類型的應用程式就是所謂的分散式應用程式 (Distributed Application),分散的處理可以讓每台機器可以只專注在一件工作上,就是處理應用程式的要求並回傳正確的資訊給用戶端,這在大型應用程式(例如股票交易,航空訂位,銀行存提款,或是 ERP 這類的企業核心應用程式,Web 應用程式也是一種分散式應用程式) 中相當常見,因為只有一台電腦是不可能吃下每秒鐘數萬(甚至數百萬)個要求並且在極短時間內回應的,所以應用程式的設計師 (Designer) 會開始把應用程式一些大家都用的到的功能或流程(流程最重要)整合到一個程式的邏輯單元中,並分裝在不同的部署單元,然後放到不同的機器上以分散處理的流量,例如:

  • 交易處理單元。
  • 庫存管理單元。
  • 產品管理單元。
  • 物流管理單元。
  • 會計總帳單元。
  • ...

每個運算單元都有自己的工作要做,也有自己的流程,這些流程多半是企業內部流程 (Process),是屬於公司的重要資產,且大多數元件封裝的流程幾乎都是商業運作的相關流程,故運算邏輯層通常被稱為商業邏輯層 (Business Logics Tier) 的原因也在此。

有這麼多的運算邏輯單元,當然可能也會有一大堆的使用者介面,早期雖然只有終端機 (Terminal Client),但現在銀行系統早就不再只有終端機,最典型的例子就是 ATM (自動提款機)以及網路銀行,ATM 是一個不同於終端機的用戶端,而網路銀行的用戶端是瀏覽器,同時用戶端未必是使用者介面,其他的應用程式也有可能是你的應用程式的用戶端(例如與其他合作伙伴交換資料),那應用程式得要準備幾個用戶端才夠?因此分層應用程式將用戶端切割成一層 (Presentation Tier),也是為了將用戶端程式交由實際要服務的用戶端模組來做(Windows Forms 就由 Windows Forms 來控,Web 由 ASP.NET 來控,合作伙伴的應用程式由 BizTalk Server,Web Service 或其他中介應用程式來控等等),這樣不但可以避免多一個用戶端時會涉及的應用程式重寫問題,也可以讓各用戶端專注在自己處理使用者回應的事情上,不需要關心太多邏輯運算的細節問題。

說了邏輯以及使用者介面,那資料管理層也不能不提,通常資料管理層多半是以 DBMS 為主,因為 DBMS 本身就已經有非常強大的資料管理方法,以及最佳化的存取效率,所以將與資料庫較直接,或是一些在資料層級 (data-level) 做限制的規則,放在 DBMS 中再適合不過,然而前面就有提到,DBMS 有那麼多家,你無法保證哪天會不會有個客戶用 Oracle 而不是 SQL Server,用 PostgreSQL 而不是 Access,用 MySQL 而不是 DB2。為了要因應 DBMS 不同的情況,在資料管理層通常會再切出一個單元為資料存取層 (Data Access Tier), 以處理不同 DBMS 的介接,而運算邏輯層只要依照資料存取層所訂定的規則來呼叫,即便資料庫由 SQL Server 換成了 DB2,或是 MySQL 換成了 Oracle,運算邏輯層仍能正常運作,也不需改動任何程式碼。

在不改動(或改動的量最小化)程式碼為前提的概念下,分層絕對是必然的,就算你今天寫的是只運行在單一台電腦上的應用程式,你仍然會需要分層,即便不需要跑在其他電腦上,光是可以達成少量變更的能力,我認為在一開始開發應用程式時,就要把分層直接考量進去,若你的應用程式是未來的主框架或產品的話,那更要這麼做了。

寫到這裡,我似乎都沒有提到另一個分層的字:layer,因為 layer 其實是建立在縱向的層次上(每個層的地位不同)像是 OSI Model 的分層,就是一種 layer 的體現;作業系統的 kernel layer, user layer (ring0 to ring3);或是 Cisco 網路的交換器有 Layer 2 Switch, Layer 3 Switch (Core Switch) 的設計等等;若應用程式要用 layer 來分的話,不妨就將它以 HQ/Branch Office 的角色來看吧。在大多數的文件中,用 Tier 來代表分層的意思比用 Layer 要明確一些。