设计模式学习日记三(持续更新)
posted @ 2013-01-06 08:59 丶Terminator 阅读(337) | 评论 (4) 编辑
本篇介绍Domain Model模式,一个银行领域建模,涉及帐号的创建以及帐号之间的现金转账的小程序。
前面提到过,Domain Model 与 Active Record 模式不同,它不知道持久化。术语持久化(persistence ignorance,PI)表示普通CLR对象(plain old common runtime object,POCO)业务实体的朴实本质。通常Repository模式(后续中将会介绍该模式)将 Domain Model 的业务对象持久化。
创建名为Terminator.Practice.DomainModel的解决方案,并添加下面的类库项目:
Terminator.Practice.DomainModel.Model -- 包含应用程序内所有的业务逻辑。领域对象将存放在此处,并与其他对象建立关系,从而表示应用程序正则构建的银行领域。该项目还将以接口的形式为领域对象持久化和检索定义契约,采用Repository模式来实现所有的持久化管理需求。Model项目不会引用其他任何项目,从而确保:让它与任何基础设施关注点保持隔离,只关注业务领域。
Terminator.Practice.DomainModel.AppService -- 充当应用程序的接口(API)。表示层将通过消息(简单的数据传输对象,后续中将会介绍消息传送模式)与 AppService 通信。AppService 还将定义视图模型,这些是领域模型的展开视图,只用于数据显示(后续中将会更详细的介绍该方式)。
Terminator.Practice.DomainModel.Repository -- 包含Model项目中定义的资源库接口的具体实现。Repository引用了Model项目,从而从数据库提取并持久化领域对象,Repository项目只关注领域对象持久化和检索的责任。
添加一个新的Web应用程序 Terminator.Practice.DomainModel.UI.Web -- Web项目负责应用程序的表示和用户体验需求,这个项目只与
AppService交互,并接收专门为用户体验视图创建的强类型视图模型。
对Repository项目添加对Model项目的引用,AppService项目添加对Model和Repository项目的引用,Web项目添加对AppService项目的引用。
创建名为BankAccount.mdf的数据库,创建两张表:
列名 | 数据类型 | 是否允许空 |
BankAccountId | uniqueidentifier,Primary key | False |
Balance | money | False |
CustomerRef | nvarchar(50) | False |
列名 | 数据类型 | 是否允许空 |
BankAccountId | uniqueidentifier | False |
Deposit | money | False |
Withdrawal | money | False |
Reference | nvarchar(50) | False |
Date | datetime | False |
现在开始领域建模,在这个场景中,BankAccount为发生的每个动作创建一个Transaction对象,类似于操作日志的功能。
在Model项目中添加一个新类Transaction:
注:Transaction对象被称为值对象,这是DDD(领域驱动设计)中的一个术语,后续中将会介绍DDD。
添加第二个类 BankAccount:
BankAccount类有3个简单的方法:CanWithdraw,Withdraw,Deposit。因为有一个CanWithdraw方法,所以应该期望调用代码在尝试从某个帐号取出现金时使用Test-Doer(先测试再执行)模式:
if(myBankAccount.CanWithdraw(amount)) { myBankAccount.Withdraw(amount); }
如果在不进行检查的情况下对一个没有足够现金金额的帐号使用Withdraw方法,则应该抛出一个异常,为此,定义一个异常类,向Model项目添加InsufficientFundsException类:
using System; namespace Terminator.Practice.DomainModel.Model { public class InsufficientFundsException : ApplicationException { } }
现在需要某种方法来持久化BankAccount和Transaction,因为不希望污染Domain Model项目,所以添加Repository的接口来定义契约,以满足实体的持久化和检索需求,这就是前面提到的PI和POCO的概念。
创建一个带有如下契约的接口IBankAccountRepository:
using System; using System.Collections.Generic; namespace Terminator.Practice.DomainModel.Model { public interface IBankAccountRepository { void Add(BankAccount bankAccount); void Save(BankAccount bankAccount); IEnumerable<BankAccount> FindAll(); BankAccount FindBy(Guid AccountId); } }
向Model项目中添加BankAccountService类:
在BankAccountService 的当前实现中,在保存两个银行帐号之间发生的任何错误均会让数据处于非法状态,因此我们需要一个事务保护机制(本篇没有这么做),后续中将会介绍UoW(工作单元模式),它能够确保所有的事务作为一个原子动作进行提交,万一出现异常就回滚。
现在可以编写方法来持久化BankAccount和Transaction对象,在Repository项目添加一个BankAccountRepository类,该类将实现接口IBankAccountRepository:
由于Repository项目需要访问web.config,所以需要添加对System.Configuration的引用。
注:在后续中将介绍一些常见的对象关系映射器,能够节省编写ADO.NET基础设施代码的时间
现在可以为客户端添加一个服务层,让其与系统已一种简单的方式进行交互。
在AppService项目中添加一个新文件夹 ViewModel,并向其中添加两个类BankAccountView 和 TransactionView:
using System; using System.Collections.Generic; namespace Terminator.Practice.DomainModel.AppService.ViewModels { public class BankAccountView { public Guid AccountNo { get; set; } public string Balance { get; set; } public string CustomerRef { get; set; } public IList<TransactionView> Transactions { get; set; } } }
using System; namespace Terminator.Practice.DomainModel.AppService.ViewModels { public class TransactionView { public string Deposit { get; set; } public string Withdrawal { get; set; } public string Reference { get; set; } public DateTime Date { get; set; } } }
BankAccountView 和 TransactionView 提供了领域模型用于表示的展开视图,为了将领域实体转换成数据传输视图模型,需要映射器类(后续中将会介绍一种将这个过程自动化的方法)。
在AppService项目根目录创建一个新类ViewMapper:
using System.Collections.Generic; using Terminator.Practice.DomainModel.AppService.ViewModels; using Terminator.Practice.DomainModel.Model; namespace Terminator.Practice.DomainModel.AppService { public static class ViewMapper { public static TransactionView CreateTransactionViewFrom(Transaction tran) { return new TransactionView { Deposit = tran.Deposit.ToString("C"), Withdrawal = tran.Withdrawal.ToString("C"), Reference = tran.Reference, Date = tran.Date }; } public static BankAccountView CreateBankAccountViewFrom(BankAccount acc) { return new BankAccountView { AccountNo = acc.AccountNo, Balance = acc.Balance.ToString("C"), CustomerRef = acc.CustomerRef, Transactions = new List<TransactionView>() }; } } }
向AppService项目中再添加一个文件夹Messages,该文件夹将包含所有用来与服务层通信的Request-Reponse模式对象,因为所有的Reponse都共享一组相同的属性,所以可以创建一个基类ResponseBase:
namespace Terminator.Practice.DomainModel.AppService.Messages { public abstract class ResponseBase { public bool Success { get; set; } public string Message { get; set; } } }
Success属性表示被调用的方法是否成功运行,而Message属性包含该方法运行结果的详细信息。
在Messages文件夹添加几个类:BankAccountCreateRequest、BankAccountCreateResponse、DepositRequest、FindAllBankAccountResponse、FindBankAccountResponse、TransferRequest、TransferResponse、WithdrawalRequest
namespace Terminator.Practice.DomainModel.AppService.Messages { public class BankAccountCreateRequest { public string CustomerName { get; set; } } }
using System; namespace Terminator.Practice.DomainModel.AppService.Messages { public class BankAccountCreateResponse : ResponseBase { public Guid BankAccountId { get; set; } } }
using System; namespace Terminator.Practice.DomainModel.AppService.Messages { public class DepositRequest { public Guid AccountId { get; set; } public decimal Amount { get; set; } } }
using System.Collections.Generic; using Terminator.Practice.DomainModel.AppService.ViewModels; namespace Terminator.Practice.DomainModel.AppService.Messages { public class FindAllBankAccountResponse : ResponseBase { public IList<BankAccountView> BankAccountView { get; set; } } }
using Terminator.Practice.DomainModel.AppService.ViewModels; namespace Terminator.Practice.DomainModel.AppService.Messages { public class FindBankAccountResponse : ResponseBase { public BankAccountView BankAccount { get; set; } } }
using System; namespace Terminator.Practice.DomainModel.AppService.Messages { public class TransferRequest { public Guid AccountIdTo { get; set; } public Guid AccountIdFrom { get; set; } public decimal Amount { get; set; } } }
namespace Terminator.Practice.DomainModel.AppService.Messages { public class TransferResponse : ResponseBase { } }
using System; namespace Terminator.Practice.DomainModel.AppService.Messages { public class WithdrawalRequest { public Guid AccountId { get; set; } public decimal Amount { get; set; } } }
消息传送对象就位之后,就可以添加服务类来协调对领域实体(服务和资源库)的方法调用,在AppService项目的根目录添加一个新类ApplicationBankAccountService:
ApplicationBankAccountService类协调应用程序活动并将所有的业务任务委托给领域模型,该层并不包含任何业务逻辑,有助于防止任何与业务无关的代码污染领域模型项目,该层还将领域实体转换成数据传输对象,从而保护领域的内部操作,并为一起工作的表示层提供了一个易于使用的API。
为了简单起见,这里选择了简陋的依赖注入方式并对默认的构造器硬编码,以便使用已经编码的资源库领域服务实现,后续中将介绍IoC和IoC容器来提供类的依赖关系。
最后就是创建用户界面,从而能够创建帐号并执行交易。
web.config数据库连接字符串配置
<connectionStrings> <add name="BankAccountConnectionString" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\BankAccount.mdf;Integrated Security=True;User Instance=True" providerName="System.Data.SqlClient"/> </connectionStrings>
解决方案结构图
程序运行图:
最后附上demo源码
posted @ 2013-01-06 08:59 丶Terminator 阅读(337) | 评论 (4) 编辑
posted @ 2013-01-05 09:16 丶Terminator 阅读(687) | 评论 (3) 编辑
posted @ 2012-12-29 11:46 丶Terminator 阅读(125) | 评论 (2) 编辑