第二章 分層架構
本章我們重點來描述如何實現開發中軟件層次結構,通過對第一章的例子的重構,以實例的方式展示一個分層結構是何樣子,力求簡要說明如何考慮軟件開發中的分層問題,建立一個關于軟件分層一個初步的印象。在個人以往的項目經歷中,遇到了各種各樣的軟件層次概念,尤其對物理分層與邏輯分層沒有清晰的認識,很多開發人員一談軟件分層必然是遠程調用、遠程服務之類;要不就是過度分層,不管項目和開發環境情況的實際需要,就搞一個三層軟件架構,結果呢、層與層之間又沒有良好的封裝和隔離性,反倒是層與層之間處處是交叉引用,業務邏輯與技術邏輯在層與層之間盤根錯節糾纏不清,未能獲得分層給項目開發帶來的優勢的同時,反倒增加了軟件開發人員掌握和理解架構的難度、降低了開發效率和系統維護的復雜度。
2.1 層次演化
關于系統的層次結構我們最常見的例子是:OSI網絡結構的七層模型,它們分別為:
應用層(Application)
表示層(Presentation)
會話層(Session)
傳輸層(Transport)
網絡層(Network)
數據鏈路層(Data Link)
物理層(Physical)
|
以及經典的TCP/IP四層模型:
們的分層描述及功能作用,在網上可以搜索到很多,這里就不做詳細描述了。總之,用分層的觀點來考慮系統時,就是把分層考慮成“多層蛋糕”的形象,上一層基于下一層來實現,使用了下層定義的各種服務(接口),下一層不知道其上有幾層,功能是什么。下一層對上一層隱藏自己的實現細節,這樣上一層只關心下一層提供了什么樣的服務(接口)可供調用,具體的實現技術算法就不是它關心的事情了。每一層專注在自己的功能領域,通過接口方式為上一層提供服務。
分層架構最大的困難就是如何決定建立那些層次以及每一層有哪些相應的職責。不是盲目的為了分層而分層,這方面TCP/IP的四層模型就是很好的例子,根據自己的需要把七層模型合并成4層模型。
企業應用的層次演化是從最早的沒有層次——〉C/S2層結構模式——〉表現層、領域層、數據源層的3層結構模式。大部分的企業應用是與數據的存儲息息相關的,所以企業應用是伴隨著關系數據庫的發展而一起興起和廣泛應用。C/S結構模式便成了大家最耳熟能詳的企業軟件架構。但是我們在討論企業應用的層次結構時,一定要記住它包含兩個層次意思:一個是物理鏈路的分離,客戶端是一臺計算機,數據庫服務則安裝在另一臺計算機上,通過遠程訪問的模式實現兩個物理節點的數據交流。同時層次結構還有的另一層含義就是邏輯層次,通過把不同的邏輯封裝在不同的軟件開發層次上,來實現邏輯意義上的層次結構,從而實現軟件功能的封裝性和相對獨立性。這樣,我們就可以根據不同的需要把不同的層次部署到不同或相同的計算機上。關于這一點會在后面的章節中給出具體的例子來繼續加以說明。
2.1.1. 經典的軟件三層架
隨著面向對象開發方式的崛起和廣泛應用,企業應用開發從二層結構逐步演進到了三層結構。表現層實現用戶界面、在領域層實現業務邏輯、在數據源層存取數據。如下表:
層次
|
職責
|
表現層
|
顯示信息、處理用戶請求、命令行調用等
|
業務邏輯層(領域層)
|
業務邏輯,系統商業價值部分
|
數據源層
|
主要與數據庫,存儲文件等,保存系統產生的信息
|
隨著O/R mapping的廣泛使用,在實際的軟件架構中,根據映射工具的需要出現了一個專門Model模型層,或者不能模型單獨叫一層,它其實貫穿三層的數據載體(值對象),本身不包含太多的業務邏輯(少量或沒有),形象的說只簡單的承載數據在層與層之間的傳輸的交通工具。
2.2 例子的重構
下面我們來看看如何根據三層架構模型把第一章的例子重構成一個三層架構的軟件解決方案。三層分別命名為:Dal層、Biz層和界面層。
2.2.1. 添加Model的類庫項目
首先,我們新建一個命名為Model的類庫項目把Customer.cs和它的映射文件Customer.hbm.xml移入到該項目中。如下圖:


修改Model文件Customer.cs和它的映射文件Customer.hbm.xml。
Customer.cs代碼如下:

Code
using System;
using System.Collections.Generic;
using System.Text;
namespace Model
{
public class Customer
{
public virtual int CustomerId { get; set; }
public virtual string Firstname { get; set; }
public virtual string Lastname { get; set; }
public virtual string Gender { get; set; }
public virtual string Address { get; set; }
public virtual string Remark { get; set; }
}
}
Customer.hbm.xml代碼如下:

Code
xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Model" namespace="Model">
<class name ="Model.Customer,Model" table="Customer">
<id name="CustomerId" column="CustomerId" type="Int32" unsaved-value="0">
<generator class ="assigned">generator>
id>
<property name="Firstname" column ="Firstname" type="string" length="50" not-null="false" />
<property name ="Lastname" column="Lastname" type="string" length="50" not-null="false" />
<property name="Gender" column ="Gender" type="string" length="2" />
<property name ="Address" column="Address" type="string" length="50" />
<property name ="Remark" column="Remark" type="string" length="50" />
class>
hibernate-mapping>
注意:Customer.hbm.xml的編譯行為一定要設置成嵌入式資源。
2.2.2. 添加Dal層類庫項目
解決方案中新建好Dal類庫項目后,把Class.cs更名為CustomerDal.cs,根據分層對Dal層的功能定義,其負責領域數據的持久化和和從數據庫中裝載領域數據,對Biz層和界面層隱藏具體的數據存儲細節。在我們例子中該層負責與數據持久相關的產品進行數據交互。于是我們把關于 NHibernate的初始化和交換相關的對象放在Dal層進行初始化。把前面例子相應的Add、Get、Edit和Delete移入到CustomerDal.cs中來,實現代碼如下:

Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Criterion;
using Model;
namespace Dal
{
public class CustomerDal
{
private Configuration _config;
private ISessionFactory _factory;
private ISession _session;
public CustomerDal()
{
_config = new Configuration().AddAssembly("Model"); //注意加載的程序集發生了變化
_factory = _config.BuildSessionFactory();
_session = _factory.OpenSession();
需要給Dal項目添加NHibernate庫文件的引用和Model項目引用。如下圖:

2.2.2.1. 增加Customer對象函數

Code
public Boolean Add(Customer customer)
{
ITransaction tran = _session.BeginTransaction();
try
{
_session.Save(customer);
tran.Commit();
return true;
}
catch
{
tran.Rollback();
if (_session.Contains(customer))
{
_session.Evict(customer);
}
return false;
}
}
2.2.2.2. 獲取Customer對象函數

Code
public Customer Get(Int32 customerId)
{
Customer customer = (Customer)_session.Get(typeof(Customer), customerId);
if (customer != null)
{
return customer;
}
else
{
return null;
}
}
2.2.2.3. 修改Customer對象函數

Code
public Boolean Edit(Customer customer)
{
ITransaction tran = _session.BeginTransaction();
try
{
_session.SaveOrUpdate(customer);
tran.Commit();
return true;
}
catch
{
tran.Rollback();
if (_session.Contains(customer))
{
_session.Evict(customer);
}
return false;
}
2.2.2.4. 刪除Customer對象函數

Code
public Boolean Delete(Customer customer)
{
ITransaction tran = _session.BeginTransaction();
try
{
_session.Delete(customer);
tran.Commit();
return true;
}
catch
{
tran.Rollback();
if (_session.Contains(customer))
{
_session.Evict(customer);
}
return false;
}
}
2.2.3. 添加Biz層類庫項目
解決方案中新建好Biz類庫項目后,把Class.cs更名為CustomerBiz.cs。Biz層作為業務邏輯層(領域層)其包含系統的核心處理業務,其需要引用Dal層但是不需要知道Dal層的實現細節,所以它無須引用NHibernate.dll文件。這里是比較關鍵的變化,Biz層只關心Dal層提供的接口,也只跟接口打交道。如果在Biz層引用到了NHibernate提供的接口,我們的分層就沒有達到分層封裝的目的,這一點我們后面還會談到它的好處。
同樣,先簡單的實現Add、Get、Edit和Delete業務操作,實現代碼如下:

Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Model;
using Dal;
namespace Biz
{
public class CustomerBiz
{
private CustomerDal _customerDal = null;
public CustomerBiz()
{
_customerDal = new CustomerDal();
}
添加對Dal和Model的項目引用,如下圖:

2.2.3.1. 實現簡單的業務邏輯代碼

Code
public Boolean Add(Customer customer)
{
return _customerDal.Add(customer);
}
public Customer Get(Int32 customerId)
{
return _customerDal.Get(customerId);
}
public Boolean Edit(Customer customer)
{
return _customerDal.Edit(customer);
}
public Boolean Delete(Customer customer)
{
return _customerDal.Delete(customer);
}
2.2.4. 修改界面層項目
現在我們來重構原來的Web項目,刪除原來的NHibernate引用,添加Model和Biz項目引用。代碼如下:

Code
using System;
using System.Collections;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using Model;
using Biz;
namespace Demo
{
public partial class _Default : System.Web.UI.Page
{
private Customer _customer;
private CustomerBiz _customerBiz;
protected void Page_Load(object sender, EventArgs e)
{
_customerBiz = new CustomerBiz();
}
界面層只引用Model和Biz這樣對于界面層來說Dal層是一個無需關心其是否存在的。它通過調用Biz層來實現系統的業務邏輯,繼續重構我們的界面層代碼,最終的結果如下:

Code
protected void Add_Click(object sender, EventArgs e)
{
_customer = new Customer();
_customer.Firstname = this.TextBox1.Text.Trim();
_customer.Lastname = this.TextBox4.Text.Trim();
_customer.Gender = this.TextBox2.Text.Trim();
_customer.Address = this.TextBox3.Text.Trim();
_customer.Remark = this.TextBox5.Text.Trim();
_customer.CustomerId = Convert.ToInt32( this.Textbox_Id.Text.Trim());
_customerBiz.Add(_customer);
}
protected void Get_Click(object sender, EventArgs e)
{
_customer = _customerBiz.Get( Convert.ToInt32(Textbox_Id.Text.Trim()));
if (_customer != null)
{
this.TextBox1.Text = _customer.Firstname;
this.TextBox4.Text = _customer.Lastname;
this.TextBox2.Text = _customer.Gender;
this.TextBox3.Text = _customer.Address;
this.TextBox5.Text = _customer.Remark;
}
}
protected void Edit_Click(object sender, EventArgs e)
{
_customer = _customerBiz.Get(Convert.ToInt32(Textbox_Id.Text.Trim()));
_customer.Firstname = this.TextBox1.Text.Trim();
_customer.Lastname = this.TextBox4.Text.Trim();
_customer.Gender = this.TextBox2.Text.Trim();
_customer.Address = this.TextBox3.Text.Trim();
_customer.Remark = this.TextBox5.Text.Trim();
_customerBiz.Edit(_customer);
}
protected void Delete_Click(object sender, EventArgs e)
{
_customer = _customerBiz.Get(Convert.ToInt32(Textbox_Id.Text.Trim()));
_customerBiz.Delete(_customer);
}
注意代碼發生的變化,界面層通過調用Biz層接口來實現業務邏輯的操作。到這里可能會有很多疑問,如Biz層還只是增、刪、查、改這些操作呀!是的,如果只是簡單的業務那么分層只會帶來額外的工作量,問題的關鍵在于企業應用的實際情況就是業務總會越來越復雜、越來越龐大,當這個條件發生變化時,我們就會體會到分層帶來的額外的好處,美妙的好處。
2.3 結語
本章我們完成了一個三層結構的軟件開發例子,從一個簡單單層的例子進入到了有三個邏輯層次的軟件架構:界面層、業務邏輯層和數據源層。通過面向對象的編程方式來達到分層解耦和的目的,當我們的領域邏輯(商業邏輯)是操作一個又一個的對象,而不是Record Set或者Table的時候,我們就會發現面向對象帶來的極大好處,這幾年隨著各種O/R mapping的廣泛使用,如:NHibernate,ADO.NET Entity Framework, Linq等;還有服務器硬件性能的提升,面向對象額外性能開銷帶來的成本,已經逐步小于其編程效率提高帶來的效益。
可是在使用這些工具前,如果沒有很好的面向對象的系統思考模式,往往也會把項目帶另一極端,而使得項目走進過度設計或者過度使用靈活性帶來的復雜度。同樣把項目帶入一個沼澤。在這里比較推崇“敏捷軟件開發”的模式,盡量在開始的時候遵從簡單而滿足當前需要的方式,只有在需要時通過重構來進化你的軟件架構使得滿足新的需求。這樣的好處是:不是在一開始去假定一個大而全的需求,而是逐步的挖掘需求來推進項目的“進化”,通過若干過迭代開發來完成最終交付的產品。
下一章,通過一個或兩個不同的界面層來進一步描述分層給我們帶來的好處。