走向ASP.NET架构设计第四章业务层分层架构(中篇)

  在上一篇文章中,我们讨论了两种组织业务逻辑的模式:Transaction Script和Active Record。在本篇中开始讲述Domain Model和Anemic Model。

  Domain Model

  在开发过程中,我们常常用Domain Model来对目标的业务领域建模。通过Domain Model建模的业务类代表了目标领域中的一些概念。而且,我们会看到通过Domain Model建模的一些对象模拟了业务活动中的数据,有的对象还反映了一些业务规则。

  我们就来看看电子商务系统的开发,在开发中我们建立了一些概念的模型来反映电子商务领域中的一些概念:购物车,订单,订单项等。这些模型有自己的数据,行为。例如一个订单模型,它不仅仅包含一些属性(流水号,创建日期,状态)来包含自己的数据,同时它也包含了一些业务逻辑:下订单的用户时候合法,下订单用户的余额是否充足等。

  一般来说,我们对领域了解的越深,我们在软件中建立的模式越接近现实中的概念,最后实现的软件就越符合客户的需求。同时在建模的过程中,也要考虑模型的可实现行,可能我们对领域进行了很好的建模,和符合目标领域的一些概念,但是在软件实现起来非常的困难,那么就得权衡一下:找出一个比较好的模式,同时也便于实现。

  在以前的文章中其实也提到过一些有关Domain Model的一些东西,其实Domain Model和Active Record的一个区别在于:Domain Model不知道自己的数据时如何持久化的,即PI(Persistence Ignorance).也就是说,通过Domain Model建立的业务类,都是POCO(Plain Old Common Runtime Object)。

  下面我们就用一个银行转账的例子来讲述一下Domain Model的应用。创建一个新的解决方案,命名为ASPPatterns.Chap4.DomainModel,并且添加如下的项目:

  ASPPatterns.Chap4.DomainModel.Model

  ASPPatterns.Chap4.DomainModel.AppService

  ASPPatterns.Chap4.DomainModel.Repository

  ASPPatterns.Chap4.DomainModel.UI.Web

  编译整个,Solution,然后添加引用:

  为Repository项目添加Model 的引用。

  为AppService项目添加Model和Repository的引用。

  为Web项目添加AppService的引用。

  下面就来看看每个项目代表的含义:

  ASPPatterns.Chap4.DomainModel.Model:在这个project中包含了系统中所有的业务逻辑和业务对象,以及业务对象之间的关系。这个project也定义了持久化业务对象的接口,并且用Repository 模式来实现的(Repository 模式我们后面会谈到的)。大家可以看到:这个Model的project没有引用其他的project,也就是说这个Model的project完全关注于业务。

  ASPPatterns.Chap4.DomainModel.Repository:这个Repository的project实现了包含在Model project中定义的持久化接口。而且Repository还引用了Model project,就是用来持久化Model的数据的。

  ASPPatterns.Chap4.DomainModel.AppService:AppService project就扮演者一个应用层的角色,或者理解为门户入口,因为提供了一些比较粗颗粒度的API,并且它和Presenter层之间通过消息的机制来进行通信。(消息模式我们以后也会讲述)而且在AppService中,我们还会定义一些view model,这些view model的就符合也最后要显示的数据结构,view model的数据可能是很多业务对象数据的组合,或者仅仅就是这业务对象数据的格式转换等等。

  ASPPatterns.Chap4.DomainModel.UI.Web:这个Web.UI project主要是负责最后的显示逻辑和一些用户体验的实现。这个project就调用AppService提供的API,获取符合界面显示的强类型的view model,然后显示数据。

  系统的这整个结构如下:

  下面就开始创建保存数据的数据库,和以前一样,为了演示的作用,我们在Web project中添加一个名为BankAccount.mdf的数据库,并且建立如下的表:

  BankAccount 表

  Transaction 表

  下一步就开始为领域建模,因为这里的例子比较简单和常见,建模的过程就省了,最后就得到了表示领域概念的两个领域对象(或者说业务对象):

public class Transaction

{

public Transaction(decimal deposit, decimal withdrawal, string reference, DateTime date)

{

this.Deposit = deposit;

this.Withdrawal = withdrawal;

this.Reference = reference;

this.Date = date;

}

public decimal Deposit

{ get; internal set; }

public decimal Withdrawal

{ get; internal set; }

public string Reference

{ get; internal set; }

public DateTime Date

{ get; internal set; }

}

  在上面的代码中,Transaction对象不包含任何的标识属性(标识对象唯一的属性,常常和数据库中的表的主键对应),因为Transaction对象就是表示订单中的每一笔交易,而且在这个系统中我们往往关心的只是每个Transaction的数据,而不关系这个Transaction到底是那个Transaction。也就是说此时在这个系统中Transaction是一个值对象(后篇讲述DDD会提到)。

  再看看BankAccount类:

public class BankAccount

{

private decimal _balance;

private Guid _accountNo;

private string _customerRef;

private IList<Transaction> _transactions;

public BankAccount() : this(Guid.NewGuid(), 0, new List<Transaction>(), "")

{

_transactions.Add(new Transaction(0m, 0m, "account created", DateTime.Now));

}

public BankAccount(Guid Id, decimal balance, IList<Transaction> transactions, string customerRef)

{

AccountNo = Id;

_balance = balance;

_transactions = transactions;

_customerRef = customerRef;

}

public Guid AccountNo

{

get { return _accountNo; }

internal set { _accountNo = value; }

}

public decimal Balance

{

get { return _balance; }

internal set { _balance = value; }

}

public string CustomerRef

{

get { return _customerRef; }

set { _customerRef = value; }

}

public bool CanWithdraw(decimal amount)

{

return (Balance >= amount);

}

public void Withdraw(decimal amount, string reference)

{

if (CanWithdraw(amount))

{

Balance -= amount;

_transactions.Add(new Transaction(0m, amount, reference, DateTime.Now));

}

}

public void Deposit(decimal amount, string reference)

{

Balance += amount;

_transactions.Add(new Transaction(amount, 0m, reference, DateTime.Now));

}

public IEnumerable<Transaction> GetTransactions()

{

return _transactions;

}

}

  代码中包含了一些保存数据的业务属性,同时还包含了三个简单的业务方法:

  CanWithdraw:是否可以取款

  Withdraw:取款

  Deposit:存款

  为了代码的健壮性,在调用Withdraw方法的时候,如果取款的数量超过了存款的数额,那么就抛出一个余额不足的异常:InsufficientFundsException.其实这里到底是抛异常还是给出其他的返回值,主要是个人的选择,没有一定要,非要什么的。

public class InsufficientFundsException : ApplicationException

{

}

  所以业务方法Withdraw修改如下:

public void Withdraw(decimal amount, string reference)

{

if (CanWithdraw(amount))

{

Balance -= amount;

_transactions.Add(new Transaction(0m, amount, reference, DateTime.Now));

}

else

{

throw new InsufficientFundsException();

}

}

  最后就考虑下如何持久化业务对象的数据。在上面业务类的设计中,我们尽量的保持业务类的干净------只包含业务逻辑,关系和业务的数据。至于数据从何而来,最后如何保存,我们都委托给了一个Repository的接口IBankAccountRepository。

public interface IBankAccountRepository

{

void Add(BankAccount bankAccount);

void Save(BankAccount bankAccount);

IEnumerable<BankAccount> FindAll();

BankAccount FindBy(Guid AccountId);

}

  本系统是一个银行转账的系统,转账的操作不是一个业务对象就能够独立的完成的,往往需要多个业务类,以及数据持久化类的一些相互配合,这些操作放在任何一个业务类中都会把职责搞乱,而且后期的维护还得到处去找这个方法。所以我们在业务层中又剥离一层service,其中service中的每个方法其实和需求中的用例有个对象关系,例如在需求中就有转账的一个用例,那么在service中就有一个Transfer转账的方法,这个方法把很多的业务对象组合在一起完成这个转账的流程,也就是说,在每个业务类中的业务方法都是原子性的,细颗粒度的,可以被重用,而在业务层的service的方法就是粗颗粒度的,目的是为调用者提供简化的API。

public class BankAccountService

{

private IBankAccountRepository _bankAccountRepository;

public BankAccountService(IBankAccountRepository bankAccountRepository)

{

_bankAccountRepository = bankAccountRepository;

}

public void Transfer(Guid accountNoTo, Guid accountNoFrom, decimal amount)

{

BankAccount bankAccountTo = _bankAccountRepository.FindBy(accountNoTo);

BankAccount bankAccountFrom = _bankAccountRepository.FindBy(accountNoFrom);

if (bankAccountFrom.CanWithdraw(amount))

{

bankAccountTo.Deposit(amount, "From Acc " + bankAccountFrom.CustomerRef + " ");

bankAccountFrom.Withdraw(amount, "Transfer To Acc " + bankAccountTo.CustomerRef + " ");

_bankAccountRepository.Save(bankAccountTo);

_bankAccountRepository.Save(bankAccountFrom);

}

else

{

throw new InsufficientFundsException();

}

}

}

  清楚了上面的之后,我们就把Repository那层实现,其实因为我们在业务层中使用的只是Repository的接口,至于采用哪种数据持久化方法可以替换的,例如如果用数据库来保存数据,我们可以选择用Linq To Sql,ADO.NET,EF等。业务层不用关心这些的。

  在下面,就用了最原始的ADO.NET来实现的,大家可以任意替换实现策略:(下面的代码大家过过就行了,可以不用细看)

public class BankAccountRepository : IBankAccountRepository

{

private string _connectionString;

public BankAccountRepository()

{

_connectionString = ConfigurationManager.ConnectionStrings["BankAccountConnectionString"].ConnectionString;

}

public void Add(BankAccount bankAccount)

{

string insertSql = "INSERT INTO BankAccounts " +

"(BankAccountID, Balance, CustomerRef) VALUES " +

"(@BankAccountID, @Balance, @CustomerRef)";

using (SqlConnection connection =

new SqlConnection(_connectionString))

{

SqlCommand command = connection.CreateCommand();

command.CommandText = insertSql;

SetCommandParametersForInsertUpdateTo(bankAccount, command);

connection.Open();

command.ExecuteNonQuery();

}

UpdateTransactionsFor(bankAccount);

}

public void Save(BankAccount bankAccount)

{

string bankAccoutnUpdateSql = "UPDATE BankAccounts " +

"SET Balance = @Balance, CustomerRef= @CustomerRef " +

"WHERE BankAccountID = @BankAccountID;";

using (SqlConnection connection =

new SqlConnection(_connectionString))

{

SqlCommand command = connection.CreateCommand();

command.CommandText = bankAccoutnUpdateSql;

SetCommandParametersForInsertUpdateTo(bankAccount, command);

connection.Open();

command.ExecuteNonQuery();

}

UpdateTransactionsFor(bankAccount);

}

private static void SetCommandParametersForInsertUpdateTo(BankAccount bankAccount, SqlCommand command)

{

command.Parameters.Add(new SqlParameter("@BankAccountID", bankAccount.AccountNo));

command.Parameters.Add(new SqlParameter("@Balance", bankAccount.Balance));

command.Parameters.Add(new SqlParameter("@CustomerRef", bankAccount.CustomerRef));

}

private void UpdateTransactionsFor(BankAccount bankAccount)

{

string deleteTransactionSQl = "DELETE Transactions WHERE BankAccountId = @BankAccountId;";

using (SqlConnection connection =

new SqlConnection(_connectionString))

{

SqlCommand command = connection.CreateCommand();

command.CommandText = deleteTransactionSQl;

command.Parameters.Add(new SqlParameter("@BankAccountID", bankAccount.AccountNo));

connection.Open();

command.ExecuteNonQuery();

}

string insertTransactionSql = "INSERT INTO Transactions " +

"(BankAccountID, Deposit, Withdraw, Reference, [Date]) VALUES " +

"(@BankAccountID, @Deposit, @Withdraw, @Reference, @Date)";

foreach (Transaction tran in bankAccount.GetTransactions())

{

using (SqlConnection connection =

new SqlConnection(_connectionString))

{

SqlCommand command = connection.CreateCommand();

command.CommandText = insertTransactionSql;

command.Parameters.Add(new SqlParameter("@BankAccountID", bankAccount.AccountNo));

command.Parameters.Add(new SqlParameter("@Deposit", tran.Deposit));

command.Parameters.Add(

时间: 2024-08-01 13:02:31

走向ASP.NET架构设计第四章业务层分层架构(中篇)的相关文章

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

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

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

今天的内容比较简单,也是本章的一个收尾! Anemic Domain Model 这种模式和之前讲述的Domain Model有很多的相似的地方.在之前的Domain Model中,每个业务类都包含了自己的业务逻辑和数据,以及对象之前的关系:但是在Anemic Domain Model,每个业务类仅仅只是包含了一些保存业务数据的属性,把相应的业务规则从原本的业务类中移到了另外的一个专门的业务规则类(Specification Pattern,我们后面的章节讲述),同时把相应的业务方法移到了ser

走向ASP.NET架构设计第四章:业务层分层架构(后篇)

今天的内容比较简单,也是本章的一个收尾! Anemic Domain Model 这种模式和之前讲述的Domain Model有很多的相似的地方.在之前的Domain Model中,每个业务类都包含了自己的业务逻辑和数据,以及对象之前的关系:但是在Anemic Domain Model,每个业务类仅仅只是包含了一些保存业务数据的属性,把相应的业务规则从原本的业务类中移到了另外的一个专门的业务规则类(Specification Pattern,我们后面的章节讲述),同时把相应的业务方法移到了ser

走向ASP.NET架构设计第四章:业务层分层架构(前篇)

在讨论完四种模式之后,我将会和大家一起来看看DDD的一些知识.每种模式的讲解,我都会用实例的形式给出完整的代码,也希望大家多琢磨! 不是所有的应用程序都是一样的,也不是所有的系统都需要用复杂的架构来组织业务逻辑.作为开发人员,我们必须清楚每一种业务逻辑组织的模式,这样我们才能在需要的时候做出合适的选择. Transaction Script 这种组织业务逻辑的模式是最简单,也是最容易理解的.Transaction Script模式就是用面向过程的方式来组织业务逻辑的.通常情况下,系统的一个流程就

走向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

走向ASP.NET架构设计第五章:业务层模式,原则,实践(前篇)

在上一章中,我们讲述了有关业务层分层的一些知识,下面我们就来看看,在具体的业务层的设计中,我们可以采用哪些模式可以将业务层设计的更加的灵活! 架构模式 首先我们就来看看,如何更加有效的组织业务规则. Specification Pattern(需求规格模式) 这个模式的使用方法就是:把业务规则放在业务类的外面,并且封装成为一个个返回boolean值的算法.这些一个个的业务规则的算法不仅仅便于管理和维护,并且还可以被重用,而且很方便的组织成为复杂的业务逻辑. 下面我们就来看一个以在线租DVD的公司

一起谈.NET技术,走向ASP.NET架构设计——第五章:业务层模式,原则,实践(前篇)

在上一章中,我们讲述了有关业务层分层的一些知识,下面我们就来看看,在具体的业务层的设计中,我们可以采用哪些模式可以将业务层设计的更加的灵活! 架构模式 首先我们就来看看,如何更加有效的组织业务规则. Specification Pattern(需求规格模式) 这个模式的使用方法就是:把业务规则放在业务类的外面,并且封装成为一个个返回boolean值的算法.这些一个个的业务规则的算法不仅仅便于管理和维护,并且还可以被重用,而且很方便的组织成为复杂的业务逻辑. 下面我们就来看一个以在线租DVD的公司

走向ASP.NET架构设计第六章:服务层设计(前篇)

本篇主要是为后文做铺垫,所以理论的东西相对而言比较的多一点! 服务层的概述 首先解释一下什么是"服务Service",从广义来讲:只要是你使用了别人的东西,那么你就在使用别人提供的服务.在这里,服务就是指可能被一个或者多个系统使用的核心的业务逻辑,我们可以把服务简单的想象成为一些可供调用的API. 在之前的第四章中,我们讲述了如何组织业务逻辑,第五章讲述了在业务层的设计中可以采用的一些模式.但是还有一个问题需要大家考虑的是:如何把业务层提供给其他的层来调用? 可能认为这个问题有莫名奇妙