走向ASP.NET架构设计第三章:分层设计,初涉架构(前篇)

  本篇主要讲述ASP.NET应用中如何进行逻辑分层。本篇的前篇会从Smart UI 反模式和它的一些缺点开始讲述,然后一步步的讲述如何逻辑分层,而且在后篇中也会给出一个ASP.NET设计中常用的仅供参考的分层架构的Demo。

  一个稳定和易维护的系统必须建立在一个好的基础之上。计划和设计一个好的架构对一个项目的成败起着至关重要的作用。可能在我们一般做项目的时候,经验告诉我们:3层,N层的设计,基本就能把问题解决了,很多的情况确实是这样的。在提出一个设计的时候,常常要考虑为什么要这样划分结构,而且常常要承担风险和责任,特别是万一这个项目因为最初的设计而导致崩溃,那就郁闷了。所以设计的提出一定和考虑业务。

  下面就先来看看Smart UI的设计方式。

  Smart UI

  想想我们最初是如何开发ASP.NET的应用的:在页面设计界面中把界面布局好,然后双击控件就开始编写功能代码。很多的时候把逻辑判断和数据访问都写在页面的.cs的文件中。后来我们学习到了分层,逐渐的明白了这种方式的缺点:导致业务逻辑代码到处分散而且重复,不利于以后的更改和维护等。

  尽管有上述说的一些缺点,Smart UI还是有它的用途的,如为项目快速的建立一个原型或者开发一个功能比较的小的项目。还有一个问题,如何最初用Smart UI的方式开发的小项目很成功,慢慢的变大,变复杂了,那么很多的问题就出来了。就像Flower在架构模式一书中提到的:尽量用领域模型来组织一个项目的业务逻辑,尽管在开始的时候逻辑不复杂或者看不出这种方式的好处,一旦项目变化,好处就显而易见了。在对项目原型开发中,尽量不用Smart UI。

  其实Smart UI最大的问题就是:职责不清—把所有的东西全部写在一起。为了和以后讲述的内容的比较,我还是写一个例子出来,很多朋友都已经对这种Smart UI的开发方式很熟悉了,可以跳过下面的例子。在例子中,我们会用电子商务中一个常见的场景:一个页面来显示一个产品的列表信息,如名字,推荐的零售价格(Recommend Retail Price),折扣,和库存等。(如果朋友们愿意,可以照着下面的步骤一起做)

  1. 打开Visual Studio,并且建立一个”空白的解决方案”,命名为:ASPPatterns.Chap3.SmartUI,然后添加一个新的Web项目,命名为:ASPPatterns.Chap3.SmartUI.Web.

  2. 在新建的Web项目中右击:Add—New Item,添加一个Sql Server的数据文件:Shop.mdf.

  如下:

  3. 在新加的数据库文件上右击,并且打开。然后添加一个新表:如下:

  其中ProductId设置为自动标示。然后保存为Products表。

  4. 添加一些测试的数据:

  5. 然后选择Products表,并且把表拖放到Default.aspx页面上。这样之后,在页面上就自动添加一个GridView和SqlDataSource.

  界面就如下图:

  6. 我我们添加额外的两列来显示折扣信息和库存信息。Default.aspx的Source代码最后如下:

  7. 然后,我们在Default.aspx.cs后编码:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

namespace ASPPatterns.Chap3.SmartUI.Web

{

public partial class Default : System.Web.UI.
Page

{

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)

{

if (e.Row.RowType == DataControlRowType.DataRow)

{

decimal RRP = decimal.Parse(((System.Data.DataRowView)e.Row.DataItem)["RRP"].ToString());

decimal SellingPrice = decimal.Parse(((System.Data.DataRowView)e.Row.DataItem)["SellingPrice"].ToString());

Label lblSellingPrice = (Label)e.Row.FindControl("lblSellingPrice");

Label lblSavings = (Label)e.Row.FindControl("lblSavings");

Label lblDiscount = (Label)e.Row.FindControl("lblDiscount");

lblSavings.Text = DisplaySavings(RRP, ApplyExtraDiscountsTo(SellingPrice));

lblDiscount.Text = DisplayDiscount(RRP, ApplyExtraDiscountsTo(SellingPrice));

lblSellingPrice.Text = String.Format("{0:C}", ApplyExtraDiscountsTo(SellingPrice));

}

}

protected string DisplayDiscount(decimal RRP, decimal SalePrice)

{

string discountText = "";

if (RRP > SalePrice)

discountText = String.Format("{0:C}", (RRP - SalePrice));

return discountText;

}

protected string DisplaySavings(decimal RRP, decimal SalePrice)

{

string savingsTest = "";

if (RRP > SalePrice)

savingsTest = (1 - (SalePrice / RRP)).ToString("#%");

return savingsTest;

}

protected decimal ApplyExtraDiscountsTo(decimal OriginalSalePrice)

{

decimal price = OriginalSalePrice;

int discountType = Int16.Parse( this.ddlDiscountType.SelectedValue);

if (discountType == 1)

{

price = price * 0.95M;

}

return price;

}

protected void ddlDiscountType_SelectedIndexChanged(object sender, EventArgs e)

{

GridView1.DataBind();

}

}

}

  在上面的 GridView1_RowDataBound方法在GridView的每个row被创建的时候调用。这个方法获取每个产品的推荐的零售价格RRP(Recommend Retail Price),然后调用 DisplayDiscount和DisplaySavings方法来获取折扣和库存,然后再更新UI的显示。

  在上面的代码中,就将计算折扣和计算库存的逻辑写在了UI中,而且数据的访问代码也写在UI中了。这就意味着:如果我们想要在不同的页面显示产品的信息,那么这些逻辑就得一遍遍的重写。如果我们在加一些新的功能,那么页面后面的代码就开始修改,开始缝缝补补。

  解决Smart UI的方法就是划分职责,我想大家都知道“单一职责的原则”,这个原则不仅仅适用于类,方法,而且对项目的层次划分也有作用。分层,最主要的目的就是:把不通的功能放在各自对应的地方,这样清晰的职责划分,也是对变化点进行分离。

  下面的图就是一个典型的企业级ASP.NET项目的分层结构:

  下面我们就来看看,按照我们的一般的分层的经验来如何设计这功能:

  1. 创建一个新的空白的解决方案,命名为ASPPatterns.Chap3.Layered.

  2. 添加四个新的C#类库,分别命名为:

  a) ASPPatterns.Chap3.Layered.Repository.

  b) ASPPatterns.Chap3.Layered.Model.

  c) ASPPatterns.Chap3.Layered.Service.

  d) ASPPatterns.Chap3.Layered.Presentation

  3. 添加一个新的Web程序,命名为ASPPatterns.Chap3.Layered.Web.。

  注:朋友们一眼就应该可以看出,这些类库的命名是反映了一些DDD的一些概念,但是,不是说在一个项目的开发中用了这些概念名词就表明就开发的方式是DDD了。

  3. 不同的类库就分别承担不同的职责,而且每一层一般都只是引用自己的下一层,而且下一层不知道自己被上一层使用。本例中的引用关系如下:

  这里我先提一下上面类库的一起名字:尽管有关DDD和一些架构模式的概念我在以后的文章中会讲,但我这里还是先给大家提一下,目的仅仅是让大家对这个例子有一些更好的了解。

  在DDD中,一直主张业务模型,也就是我们常常所说的业务类,例如之前例子中的Product,只关注自身的业务逻辑,而不管如何去获取和保存数据,这些对数据的操作完全交给另外的对象去执行,也就是Repository,这样就达到了DDD中所说的PI(Persistence Ignore)。所以在上面的例子中,ASPPatterns.Chap3.Layered.Model就代表了一个业务模型,它之所以被Repository引用,是因为Repository负责将Model的数据持久化到存储设备中,而Model不管这些事情了。

  在讲ASPPatterns.Chap3.Layered.Service之前,首先给大家统一 一下Service的概念。

  有时在类的设计过程中,有些行为不适合放在任何的一个类中,如果把这些行为放在一个不真正拥有它的类中,只能把类的职责搞混了。为了给这些行为一个安置的地方,我们常常把这些行为放在一个称为服务的类中。

  作为服务的类一般没有状态的,可以简单的作为一个提供操作接口实现。

  在DDD中,Service也是用来提供一种服务的。很多人看到了DDD的类层次结构是这样的:Repository---Model---Service--- Presentation(包括本例),所以都以为Service只能出现在Model的上一层,如果看到Repository-- Service ---Model---Service--- Presentation这样的层次结构,又作何感想。如果被这些所谓的结构搞迷惑了,那就说明对DDD的理解只是在于“形”上。Service就是向外部提供的功能接口,和我们常见的Web Service的概念很相似,例如的Web Service就是向外部系统提供一些功能的。

  我们来看下面的一个图:

  有时候之所以要在Model层之上加上一个Service层,主要的原因就是实现粗颗粒度的API,往往和系统的User Case有一定的联系。例如,如果在系统用例中要实现一个用户订单的处理,那么可能就涉及到Customer, Product,Order等类,当然,如果我们调用这些类来共同完成这个任务是没问题的,但是这样就向调用者暴露这些类之间的复杂的关系,而且如果处理的过程变化了,那么调用者的代码就要改变,如果把这个处理的方法放在上面的任意一个类中,又显得不伦不类,这里的Service功能就类似于设计模式中的Façade外观模式。这样就向外界提供简单的API,向外界提供订单处理的服务!

  所以在一般在DDD中业务层被划分为两个逻辑层:Model (提供细粒度的业务逻辑处理,也便于重用), Service(提供业务处理的流程,提供粗颗粒度的供外部调用的方法)。

  但是,我们常见到的Model层之上的Service层仅仅只是对CRUD的再次封装,一个可能的原因就是业务不是很复杂,这时其实这个Service层可以拿掉的,但是考虑到以后可能逻辑会更多更复杂,所以还是保留Service这层。

  其实在Repository上的那个Service也是同样的概念。例如发送邮件通知用户的功能。例如上图中的最上层的Service可以调用业务层和基础设施层的Service来共同完成一个事情。

  今天的上篇的就讲述到这里吧,下篇会用一个例子,代码量还是有点的!敬请关注!

  我先发上代码,大家感兴趣的看看,我们下篇讲述!

时间: 2024-08-01 04:52:48

走向ASP.NET架构设计第三章:分层设计,初涉架构(前篇)的相关文章

架构漫谈(三):如何做好架构之识别问题

原文:架构漫谈(三):如何做好架构之识别问题 架构漫谈是由资深架构师王概凯Kevin执笔的系列专栏,专栏将会以Kevin的架构经验为基础,逐步讨论什么是架构.怎样做好架构.软件架构如何落地.如何写好程序等问题. 本文是漫谈架构专栏的第三篇.接之前的第二篇,作者将会讨论如何做好架构,并根据实际经验分析如何找出实际工作中需要解决的问题. 按照之前架构的定义,做好架构首先需要做的就是识别出需要解决的问题.一般来说,如果把真正的问题找到,那么问题就已经解决了80%了.这个能力基本上就决定了架构师的水平.

走向ASP.NET架构设计第三章:分层设计,初涉架构(后篇)

接上篇 4.数据访问层设计 数据访问层,这块要说的不多.但是要澄清一点:数据访问不一定就是访问数据库,虽然多数的情况下,我们确实把数据存储在数据库中. 这里我们用数据库存储数据,并且用Linq To Sql来进行数据访问操作. 下面我们就来实现数据操作的一些代码: public class ProductRepository : IProductRepository { public IList<Model.Product> FindAll() { var products = from p

一起谈.NET技术,走向ASP.NET架构设计——第三章:分层设计,初涉架构(中篇)

1.阐明示例需求 本篇还是用之前的电子商务网站中的一个简单的场景来讲述:在页面上需要显示产品的列表信息.并且根据产品的类型不同,计算出相应的折扣. 在上篇中,我们已经设计项目的逻辑分层.我们再来回顾下: 可能有的朋友认为从Smart UI立刻跳到这种分层设计,似乎快了些.其实也算是一个思想的跳跃吧.下面就来看看这种分层是如何解决之前Smart UI的问题的. 2.业务层设计 记得在之前的Smart UI的例子中,程序的业务逻辑是直接写在了ASPX页面后面的cs代码中的.现在,采用分层的方法,我们

走向ASP.NET架构设计第三章:分层设计,初涉架构(中篇)

1.阐明示例需求 本篇还是用之前的电子商务网站中的一个简单的场景来讲述:在页面上需要显示产品的列表信息.并且根据产品的类型不同,计算出相应的折扣. 在上篇中,我们已经设计项目的逻辑分层.我们再来回顾下: 可能有的朋友认为从Smart UI立刻跳到这种分层设计,似乎快了些.其实也算是一个思想的跳跃吧.下面就来看看这种分层是如何解决之前Smart UI的问题的. 2.业务层设计 记得在之前的Smart UI的例子中,程序的业务逻辑是直接写在了ASPX页面后面的cs代码中的.现在,采用分层的方法,我们

一起谈.NET技术,走向ASP.NET架构设计——第四章—业务层分层架构(中篇)

在上一篇文章中,我们讨论了两种组织业务逻辑的模式:Transaction Script和Active Record.在本篇中开始讲述Domain Model和Anemic Model. Domain Model 在开发过程中,我们常常用Domain Model来对目标的业务领域建模.通过Domain Model建模的业务类代表了目标领域中的一些概念.而且,我们会看到通过Domain Model建模的一些对象模拟了业务活动中的数据,有的对象还反映了一些业务规则. 我们就来看看电子商务系统的开发,在

走向ASP.NET架构设计第七章:阶段总结,实践篇(中篇)

服务层(中篇) 上一篇文章中,我们已经讲述了业务逻辑层和数据访问层层的设计和编码,下面我们就来讲述服务层的设计.如我们之前所讨论的:服务层想客户端暴露简单易用的API. 如下图所示: 在上图中: 1. ASPPatterns.Chap6.EventTickets.Contract: 这个类库中定义了服务层的接口契约. 2. ASPPatterns.Chap6.EventTickets.Service:这个类库中包含了上面接口契约的实现类以及业务逻辑的协调和数据的持久化和返回数据 3. ASPPa

一起谈.NET技术,走向ASP.NET架构设计——第七章:阶段总结,实践篇(中篇)

服务层(中篇) 上一篇文章中,我们已经讲述了业务逻辑层和数据访问层层的设计和编码,下面我们就来讲述服务层的设计.如我们之前所讨论的:服务层想客户端暴露简单易用的API. 如下图所示: 在上图中: 1. ASPPatterns.Chap6.EventTickets.Contract: 这个类库中定义了服务层的接口契约. 2. ASPPatterns.Chap6.EventTickets.Service:这个类库中包含了上面接口契约的实现类以及业务逻辑的协调和数据的持久化和返回数据 3. ASPPa

&amp;gt; 前言(补充) 和第三章 第一个C#程序(rainbow 翻译)(来自重粒子空间)

程序 <<展现C#>> 前言(补充) 和第三章 第一个C#程序(rainbow 翻译)   出处:http://www.informit.com/matter/ser0000001/chapter1/ch03.shtml 正文: 前言0.1  提要    欢迎阅读<展现 C#>(Presenting C#).这本书是你提高企业编程语言的一条捷径.这种企业编程语言带有下一代编程语言服务运行时(NGWS Runtime):C#(发音"C sharp").

一起谈.NET技术,走向ASP.NET架构设计——第二章:设计/ 测试/代码

再次申明一下:本系列不是讲述TDD的,只是用TDD来建立设计的思想.即便是用DDD,有时候还是结合TDD一起使用的. 开发方式比较 我们用下面的一段分析来引出今天的内容: 想想我们平时是如何在写代码:拿来需求,分析功能,编写功能代码.这样的方式,没有问题,大家也一直沿用很多年了.为了后面描述方便,我们称这种方式为传统流程. TDD的怎么做的: 拿来需求,分析功能,写功能测试代码,编写功能代码.其实两个过程差不多的,真的差不多的. 首先来分析下两种开发流程.个人认为:因为TDD多了一个角色转换的过