浅谈企业软件架构(2)
第二章 分层架构
本章我们重点来描述如何实现开发中软件层次结构,通过对第一章的例子的重构,以实例的方式展示一个分层结构是何样子,力求简要说明如何考虑软件开发中的分层问题,建立一个关于软件分层一个初步的印象。在个人以往的项目经历中,遇到了各种各样的软件层次概念,尤其对物理分层与逻辑分层没有清晰的认识,很多开发人员一谈软件分层必然是远程调用、远程服务之类;要不就是过度分层,不管项目和开发环境情况的实际需要,就搞一个三层软件架构,结果呢、层与层之间又没有良好的封装和隔离性,反倒是层与层之间处处是交叉引用,业务逻辑与技术逻辑在层与层之间盘根错节纠缠不清,未能获得分层给项目开发带来的优势的同时,反倒增加了软件开发人员掌握和理解架构的难度、降低了开发效率和系统维护的复杂度。
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代码如下:
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代码如下:
<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中来,实现代码如下:
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对象函数
{
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对象函数
{
Customer customer = (Customer)_session.Get(typeof(Customer), customerId);
if (customer != null)
{
return customer;
}
else
{
return null;
}
}
2.2.2.3. 修改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对象函数
{
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业务操作,实现代码如下:
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. 实现简单的业务逻辑代码
{
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项目引用。代码如下:
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层来实现系统的业务逻辑,继续重构我们的界面层代码,最终的结果如下:
{
_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等;还有服务器硬件性能的提升,面向对象额外性能开销带来的成本,已经逐步小于其编程效率提高带来的效益。
可是在使用这些工具前,如果没有很好的面向对象的系统思考模式,往往也会把项目带另一极端,而使得项目走进过度设计或者过度使用灵活性带来的复杂度。同样把项目带入一个沼泽。在这里比较推崇“敏捷软件开发”的模式,尽量在开始的时候遵从简单而满足当前需要的方式,只有在需要时通过重构来进化你的软件架构使得满足新的需求。这样的好处是:不是在一开始去假定一个大而全的需求,而是逐步的挖掘需求来推进项目的“进化”,通过若干过迭代开发来完成最终交付的产品。
下一章,通过一个或两个不同的界面层来进一步描述分层给我们带来的好处。