浅谈企业软件架构(1)
http://www.cnblogs.com/haozi0804/archive/2009/07/31/1535524.html
第一章 简单的开始
本章我们从一个简单的项目开始,一个基于NHibernate2.1数据库O/R Mapping工具的简单编程实例,来说明即使我们使用了O/R Mapping工具仍然有可能会把程序写成没有层次结构和杂乱无章。不是说这样的看上去简单的结构不是好结构,正如很多程序员说的一样,能解决问题的结构就是好的结构。问题的关键是怎么解决问题的?项目有多大、业务领域有多复杂、项目团队有多少人等等这些因素都会决定着软件架构本身。作为编写围绕企业应用的行业软件,项目的成功的关键是我们在规定的时间、预算范围内,交付能够满足用户需求的软件产品。
只是在项目的前期多考虑一下项目的维护问题、升级问题和项目在下一个项目的重用性问题等等,一开始就着手一个简单和“好”的框架会在某些时候带来意想不到的收获,同时为了这个收获并没有付出太多额外的工作,只是你的团队成员和你一起知道如何运用这些技术——领域模型(Domain Model)。
让我们一步一步的来,先从简单的开始。这里先说明例子所使用的开发环境:
开发工具:Microsoft Visual Studio 2008 Version 9.0.21022.8 RTM Microsoft .NET Framework Version 3.5 SP1 Microsoft Visual Studio 2008 Version 9.0.21022.8 RTM Microsoft .NET Framework Version 3.5 SP1 开发语言:C# 操作系统:Microsoft XP professional Service Pack 2 数据库开发工具:Microsoft SQL Server 2005 Service Pack1 数据映射工具: NHibernate2.1 配置管理工具:Visual SourceSafe 8.0 单元测试工具:TestDriven.NET-2.21.2448_Personal 支持NUnit-2.5 |
1.1 建立数据库表
假定我们有一张关于Customer的数据库表,我的“简单的开始”就是从如何实现这个表的基本业务逻辑开始。
下面实现创建表的脚本:注意USE [Data]请使用你自己的数据库名称
USE [Data] GO /****** 对象: Table [dbo].[Customer] 脚本日期: 05/31/2009 21:50:49 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO SET ANSI_PADDING ON GO CREATE TABLE [dbo].[Customer]( [CustomerId] [int] NOT NULL, [Firstname] [varchar](50) COLLATE Chinese_PRC_CI_AS NULL, [Lastname] [varchar](50) COLLATE Chinese_PRC_CI_AS NULL, [Gender] [varchar](2) COLLATE Chinese_PRC_CI_AS NULL, [Address] [varchar](50) COLLATE Chinese_PRC_CI_AS NULL, [Remark] [varchar](100) COLLATE Chinese_PRC_CI_AS NULL, CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED ( [CustomerId] ASC )WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY] ) ON [PRIMARY]
GO SET ANSI_PADDING OFF |
1.2 创建ASP.NET Web Application项目
在VS2008中创建ASP.NET Web Application项目命名为DemoWebApp,把Default.aspx更名为customer.aspx如图所示:
本DEMO程序我们使用NHibernate2.2为映射工具,关于NHibernate2.2的使用请参考相关网络资源如:NHibernate之旅系列文章导航。
1.3 创建NHibernate2.2领域模型映射文件
我添加NHibernate2.2 Model文件Customer.cs和它的映射文件Customer.hbm.xml。
Customer.cs代码如下:
using System; using System.Collections.Generic; using System.Text;
namespace DemoWebApp { 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代码如下:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"> <class name="DemoWebApp.Customer, DemoWebApp" table="Customer"> <id name="CustomerId" type="Int32" unsaved-value="null"> <column name="CustomerId" length="4" sql-type="int" not-null="true" /> <generator class="assigned" /> </id> <property name="Firstname" type="String"> <column name="Firstname" length="50" sql-type="varchar" not-null="false"/> </property> <property name="Lastname" type="String"> <column name="Lastname" length="50" sql-type="varchar" not-null="false"/> </property> <property name="Gender" type="String"> <column name="Gender" length="2" sql-type="char" not-null="false"/> </property> <property name="Address" type="String"> <column name="Address" length="50" sql-type="varchar" not-null="false"/> </property> <property name="Remark" type="String"> <column name="Remark" length="100" sql-type="varchar" not-null="false"/> </property> </class> </hibernate-mapping> |
这两个文件是实现Model对象与表之间的关系映射所必需的,通过这两个文件我们才能完成对象到数据库表的数据持久化。
1.4 实现Customer模型的增加、查询、修改和删除基本业务
接下来我们实现关于Customer的增加、查询、修改和删除简单的业务操作,基于Web的程序我们是如何完成的这样的功能实现的。如图,我们在WEB界面上增加操作按钮,然后在按钮的点击事件里实现相应的业务代码。
我们需要给项目添加NHibernate库文件的引用。这样才能调用NHibernate类库文件提供的API访问,如图:
同时记得在Web.Config配置NHibernate的项目,以确保程序能够使用NHibernate类库。
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2"> <session-factory> <property name="dialect">NHibernate.Dialect.MsSql2000Dialect</property> <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property> <property name="connection.connection_string">Data Source=MES9;Initial Catalog=Data;Persist Security Info=True;User ID=sa;Password=sa</property> </session-factory> </hibernate-configuration>
|
然后在Web页面的Page_Load事件里初始化NHibernate的会话,才能根据Session去调用持久层提供的API操作数据。
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 System.Text;
using NHibernate; using NHibernate.Cfg; using NHibernate.Criterion;
namespace DemoWebApp { public partial class _Default : System.Web.UI.Page { private Configuration _config; private ISessionFactory _factory; private ISession _session; private Customer _customer;
protected void Page_Load(object sender, EventArgs e) { _config = new Configuration().AddAssembly("DemoWebApp"); _factory = _config.BuildSessionFactory(); _session = _factory.OpenSession(); //获得一个NHibernate当前会话
} |
1.4.1创建一个业务对象并持久化到数据库
现在我们来看看如何增加一个客户数据吧。我们首先创建一个Customer对象,然后把文本框里的值赋给Customer对应的属性,最后向NHibernate Session 提交该Customer对象完成Customer对象的数据库提交工作。同时提交过程中我们启动了Session的事务管理,已确保提交出现异常时事务会回滚等。
protected void bt_Add_Click(object sender, EventArgs e) { _customer = new Customer(); _customer.Firstname = this.TextBox1.Text.Trim(); ; _customer.Gender = this.TextBox2.Text.Trim(); _customer.Address = this.TextBox3.Text.Trim(); _customer.CustomerId = 1000;
ITransaction tran = _session.BeginTransaction(); try { _session.Save(_customer); tran.Commit();
} catch { tran.Rollback(); if (_session.Contains(_customer)) { _session.Evict(_customer); }
} this.tb_name.Text = _customer.CustomerId.ToString();
} |
1.4.2根据标识查询一个持久化对象
protected void Button3_Click(object sender, EventArgs e)
{
_customer = (Customer)_session.Get(typeof(Customer), Convert.ToInt32(tb_name.Text.Trim()));
if (_customer != null)
{
this.TextBox1.Text = _customer.Firstname;
this.TextBox2.Text = _customer.Gender;
this.TextBox3.Text = _customer.Address;
this.TextBox5.Text = _customer.Remark;
}
}
protected void Button3_Click(object sender, EventArgs e)
{
_customer = (Customer)_session.Get(typeof(Customer), Convert.ToInt32(tb_name.Text.Trim()));
if (_customer != null)
{
this.TextBox1.Text = _customer.Firstname;
this.TextBox2.Text = _customer.Gender;
this.TextBox3.Text = _customer.Address;
this.TextBox5.Text = _customer.Remark;
}
}
1.4.3修改一个持久化对象
protected void Button1_Click(object sender, EventArgs e) { _customer = _session.Get<Customer>(Convert.ToInt32(tb_name.Text.Trim())); _customer.Firstname = this.TextBox1.Text.Trim(); ; _customer.Gender = this.TextBox2.Text.Trim(); _customer.Address = this.TextBox3.Text.Trim(); _customer.Remark = this.TextBox5.Text.Trim(); ITransaction tran = _session.BeginTransaction(); try { _session.SaveOrUpdate(_customer); tran.Commit(); } catch { tran.Rollback(); if (_session.Contains(_customer)) { _session.Evict(_customer); } } } |
1.4.4删除一个持久化对象
protected void Button2_Click(object sender, EventArgs e) { _customer = (Customer)_session.Get(typeof(Customer), Convert.ToInt32(tb_name.Text.Trim())); ITransaction tran = _session.BeginTransaction(); try { _session.Delete(_customer); tran.Commit(); } catch { tran.Rollback(); if (_session.Contains(_customer)) { _session.Evict(_customer); } } } |
1.5 结束语
上例我们可以看出即使使用面向对象的持久化工具,我们面向领域编程,如果没有良好的代码组织和层次结构,我们同样可以写出领域模型与业务逻辑及对象映射都揉杂在界面层的事件中,这样的软件结构在面向简单业务和快速的实现上对于许多程序员来说是容易入手和理解的。可是当这样的代码需要升级维护以满足趋于复杂的业务时,我们发现系统的多么的脆弱和难于理解。在我的项目生涯中,听到程序员抱怨最多的就是与其读懂并修改别人的代码还不如自己重新写一个(当然这还与代码缺乏良好的命名习惯、编写组织及注释等等有关)。于是我们的项目中会不断的重复这样的现象,另一个程序员接受别人的功能模块时,总是打定主意重新开始,但是他往往忽略了当别人维护他的代码时也会发出同样的感叹。