丶Terminator

导航

设计模式学习日记三(持续更新)

本篇介绍Domain Model模式,一个银行领域建模,涉及帐号的创建以及帐号之间的现金转账的小程序。

1. POCO 和 PI

前面提到过,Domain Model 与 Active Record 模式不同,它不知道持久化。术语持久化(persistence ignorance,PI)表示普通CLR对象(plain old common runtime object,POCO)业务实体的朴实本质。通常Repository模式(后续中将会介绍该模式)将 Domain Model 的业务对象持久化。

2. 示例程序

创建名为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的数据库,创建两张表:

BankAccounts表
列名 数据类型 是否允许空
BankAccountId uniqueidentifier,Primary key False
Balance money False
CustomerRef nvarchar(50) False

 

 

 

 

 

Transactions表
列名 数据类型 是否允许空
BankAccountId uniqueidentifier False
Deposit money False
Withdrawal money False
Reference nvarchar(50) False
Date datetime False

 

 

 

 

 

现在开始领域建模,在这个场景中,BankAccount为发生的每个动作创建一个Transaction对象,类似于操作日志的功能。

在Model项目中添加一个新类Transaction:

Transaction类代码
 1 using System;
 2 
 3 namespace Terminator.Practice.DomainModel.Model
 4 {
 5     public class Transaction
 6     {
 7         public Transaction(decimal deposit, decimal withdrawal, string reference, DateTime date)
 8         {
 9             Deposit = deposit;
10             Withdrawal = withdrawal;
11             Reference = reference;
12             Date = date;
13         }
14 
15         /// <summary>
16         /// 存款金额
17         /// </summary>
18         public decimal Deposit
19         { get; internal set; }
20         /// <summary>
21         /// 取出金额
22         /// </summary>
23         public decimal Withdrawal
24         { get; internal set; }
25 
26         public string Reference
27         { get; internal set; }
28 
29         public DateTime Date
30         { get; internal set; }
31     }
32 }

注:Transaction对象被称为值对象,这是DDD(领域驱动设计)中的一个术语,后续中将会介绍DDD。

添加第二个类 BankAccount:

BankAccount类代码
 1 using System;
 2 using System.Collections.Generic;
 3 
 4 namespace Terminator.Practice.DomainModel.Model
 5 {
 6     public class BankAccount
 7     {
 8         private readonly IList<Transaction> _transactions;
 9 
10         public BankAccount() : this(Guid.NewGuid(), 0, new List<Transaction>(), "")
11         {
12             _transactions.Add(new Transaction(0m, 0m, "account created", DateTime.Now));
13         }
14 
15         public BankAccount(Guid Id, decimal balance, IList<Transaction> transactions, string customerRef)
16         {
17             AccountNo = Id;
18             Balance = balance;
19             _transactions = transactions;
20             CustomerRef = customerRef;
21         }
22 
23         public Guid AccountNo { get; internal set; }
24         /// <summary>
25         /// 账户金额
26         /// </summary>
27         public decimal Balance { get; internal set; }
28 
29         public string CustomerRef { get; set; }
30 
31         /// <summary>
32         /// 是否可以取出某个金额
33         /// </summary>
34         /// <param name="amount"></param>
35         /// <returns></returns>
36         public bool CanWithdraw(decimal amount)
37         {
38             return (Balance >= amount);
39         }
40 
41         /// <summary>
42         /// 取出某金额
43         /// </summary>
44         /// <param name="amount"></param>
45         /// <param name="reference"></param>
46         public void Withdraw(decimal amount, string reference)
47         {
48             if (!CanWithdraw(amount))
49             {
50                 throw new InsufficientFundsException();
51             }
52             Balance -= amount;
53             _transactions.Add(new Transaction(0m, amount, reference, DateTime.Now));
54         }
55 
56         /// <summary>
57         /// 存款
58         /// </summary>
59         /// <param name="amount"></param>
60         /// <param name="reference"></param>
61         public void Deposit(decimal amount, string reference)
62         {
63             Balance += amount;
64             _transactions.Add(new Transaction(amount, 0m, reference, DateTime.Now));
65         }
66 
67         public IEnumerable<Transaction> GetTransactions()
68         {
69             return _transactions;
70         }
71     }   
72 }

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类代码
 1 using System;
 2 
 3 namespace Terminator.Practice.DomainModel.Model
 4 {
 5     public class BankAccountService
 6     {
 7         private readonly IBankAccountRepository _bankAccountRepository;
 8 
 9         public BankAccountService(IBankAccountRepository bankAccountRepository)
10         {
11             _bankAccountRepository = bankAccountRepository;
12         }
13         
14         public void Transfer(Guid accountNoTo, Guid accountNoFrom, decimal amount)
15         {
16             BankAccount bankAccountTo = _bankAccountRepository.FindBy(accountNoTo);
17             BankAccount bankAccountFrom = _bankAccountRepository.FindBy(accountNoFrom);
18 
19             if (!bankAccountFrom.CanWithdraw(amount))
20             {
21                 throw new InsufficientFundsException();
22             }
23             bankAccountTo.Deposit(amount, "From Acc " + bankAccountFrom.CustomerRef + " ");
24             bankAccountFrom.Withdraw(amount, "Transfer To Acc " + bankAccountTo.CustomerRef + " ");
25 
26             _bankAccountRepository.Save(bankAccountTo);
27             _bankAccountRepository.Save(bankAccountFrom);
28         }      
29     }
30 }

在BankAccountService 的当前实现中,在保存两个银行帐号之间发生的任何错误均会让数据处于非法状态,因此我们需要一个事务保护机制(本篇没有这么做),后续中将会介绍UoW(工作单元模式),它能够确保所有的事务作为一个原子动作进行提交,万一出现异常就回滚。

现在可以编写方法来持久化BankAccount和Transaction对象,在Repository项目添加一个BankAccountRepository类,该类将实现接口IBankAccountRepository:

BankAccountRepository类代码
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Data.SqlClient;
  4 using System.Data;
  5 using System.Configuration;
  6 using Terminator.Practice.DomainModel.Model;
  7 
  8 namespace Terminator.Practice.DomainModel.Repository
  9 {
 10     public class BankAccountRepository : IBankAccountRepository
 11     {
 12         private readonly string _connectionString;
 13 
 14         public BankAccountRepository()
 15         {
 16             _connectionString = ConfigurationManager.ConnectionStrings["BankAccountConnectionString"].ConnectionString;
 17         }
 18 
 19         public void Add(BankAccount bankAccount)
 20         {
 21             string insertSql = @"INSERT INTO BankAccounts(BankAccountID, Balance, CustomerRef)
 22                                             VALUES(@BankAccountID, @Balance, @CustomerRef)";
 23             using (SqlConnection connection = new SqlConnection(_connectionString))
 24             {
 25                 SqlCommand command = connection.CreateCommand();
 26                 command.CommandText = insertSql;
 27 
 28                 SetCommandParametersForInsertUpdateTo(bankAccount, command);
 29 
 30                 connection.Open();
 31 
 32                 command.ExecuteNonQuery();
 33             }
 34 
 35             UpdateTransactionsFor(bankAccount);
 36         }
 37 
 38         public void Save(BankAccount bankAccount)
 39         {
 40             string bankAccoutnUpdateSql = "UPDATE BankAccounts " +
 41                                  "SET Balance = @Balance, CustomerRef= @CustomerRef " +
 42                                  "WHERE BankAccountID = @BankAccountID;";
 43 
 44             using (SqlConnection connection = new SqlConnection(_connectionString))
 45             {
 46                 SqlCommand command = connection.CreateCommand();
 47                 command.CommandText = bankAccoutnUpdateSql;
 48 
 49                 SetCommandParametersForInsertUpdateTo(bankAccount, command);
 50 
 51                 connection.Open();
 52 
 53                 command.ExecuteNonQuery();
 54             }
 55 
 56             UpdateTransactionsFor(bankAccount);
 57         }
 58 
 59         private static void SetCommandParametersForInsertUpdateTo(BankAccount bankAccount, SqlCommand command)
 60         {
 61             command.Parameters.Add(new SqlParameter("@BankAccountID", bankAccount.AccountNo));
 62             command.Parameters.Add(new SqlParameter("@Balance", bankAccount.Balance));
 63             command.Parameters.Add(new SqlParameter("@CustomerRef", bankAccount.CustomerRef));
 64         }
 65 
 66         private void UpdateTransactionsFor(BankAccount bankAccount)
 67         {
 68             string deleteTransactionSQl = "DELETE Transactions WHERE BankAccountId = @BankAccountId;";
 69 
 70             using (SqlConnection connection = new SqlConnection(_connectionString))
 71             {
 72                 SqlCommand command = connection.CreateCommand();
 73                 command.CommandText = deleteTransactionSQl;
 74                 command.Parameters.Add(new SqlParameter("@BankAccountID", bankAccount.AccountNo));
 75                 connection.Open();
 76                 command.ExecuteNonQuery();
 77             }
 78 
 79             string insertTransactionSql = "INSERT INTO Transactions " +
 80                                  "(BankAccountID, Deposit, Withdrawal, Reference, [Date]) VALUES " +
 81                                  "(@BankAccountID, @Deposit,  @Withdrawal,  @Reference, @Date)";
 82 
 83             foreach (Transaction tran in bankAccount.GetTransactions())
 84             {
 85                 using (SqlConnection connection = new SqlConnection(_connectionString))
 86                 {
 87                     SqlCommand command = connection.CreateCommand();
 88                     command.CommandText = insertTransactionSql;
 89 
 90                     command.Parameters.Add(new SqlParameter("@BankAccountID", bankAccount.AccountNo));
 91                     command.Parameters.Add(new SqlParameter("@Deposit", tran.Deposit));
 92                     command.Parameters.Add(new SqlParameter("@Withdrawal", tran.Withdrawal));
 93                     command.Parameters.Add(new SqlParameter("@Reference", tran.Reference));
 94                     command.Parameters.Add(new SqlParameter("@Date", tran.Date));
 95 
 96                     connection.Open();
 97                     command.ExecuteNonQuery();
 98                 }
 99             }
100         }
101 
102         public IEnumerable<BankAccount> FindAll()
103         {
104             IList<BankAccount> accounts;
105 
106             string queryString = "SELECT * FROM dbo.Transactions INNER JOIN " +
107                                  "dbo.BankAccounts ON dbo.Transactions.BankAccountId = dbo.BankAccounts.BankAccountId " +
108                                  "ORDER BY dbo.BankAccounts.BankAccountId;";
109 
110             using (SqlConnection connection = new SqlConnection(_connectionString))
111             {
112                 SqlCommand command = connection.CreateCommand();
113                 command.CommandText = queryString;
114 
115                 connection.Open();
116 
117                 using (SqlDataReader reader = command.ExecuteReader())
118                 {
119                     accounts = CreateListOfAccountsFrom(reader);
120                 }
121             }
122             return accounts;
123         }
124 
125         private IList<BankAccount> CreateListOfAccountsFrom(IDataReader datareader)
126         {
127             IList<BankAccount> accounts = new List<BankAccount>();
128             string id = "";
129             IList<Transaction> transactions = new List<Transaction>();
130 
131             while (datareader.Read())
132             {
133                 if (id != datareader["BankAccountId"].ToString())
134                 {
135                     id = datareader["BankAccountId"].ToString();
136                     transactions = new List<Transaction>();
137                     BankAccount bankAccount = new BankAccount(new Guid(id), Decimal.Parse(datareader["Balance"].ToString()), transactions, datareader["CustomerRef"].ToString());
138                     accounts.Add(bankAccount);
139                 }
140                 transactions.Add(CreateTransactionFrom(datareader));
141             }
142 
143             return accounts;
144         }
145 
146         private Transaction CreateTransactionFrom(IDataRecord rawData)
147         {
148             return new Transaction(Decimal.Parse(rawData["Deposit"].ToString()),
149                                    Decimal.Parse(rawData["Withdrawal"].ToString()),
150                                    rawData["Reference"].ToString(),
151                                    DateTime.Parse(rawData["Date"].ToString()));
152         }
153 
154 
155         public BankAccount FindBy(Guid AccountId)
156         {
157             BankAccount account;
158 
159             string queryString = "SELECT * FROM dbo.Transactions INNER JOIN " +
160                                  "dbo.BankAccounts ON dbo.Transactions.BankAccountId = dbo.BankAccounts.BankAccountId " +
161                                  "WHERE dbo.BankAccounts.BankAccountId = @BankAccountId;";
162 
163             using (SqlConnection connection = new SqlConnection(_connectionString))
164             {
165                 SqlCommand command = connection.CreateCommand();
166                 command.CommandText = queryString;
167 
168                 SqlParameter Idparam = new SqlParameter("@BankAccountId", AccountId);
169                 command.Parameters.Add(Idparam);
170 
171                 connection.Open();
172 
173                 using (SqlDataReader reader = command.ExecuteReader())
174                 {
175                     account = CreateListOfAccountsFrom(reader)[0];
176                 }
177             }
178             return account;
179         }
180     }
181 }

由于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类代码
  1 using System;
  2 using System.Collections.Generic;
  3 using Terminator.Practice.DomainModel.AppService.Messages;
  4 using Terminator.Practice.DomainModel.AppService.ViewModels;
  5 using Terminator.Practice.DomainModel.Model;
  6 using Terminator.Practice.DomainModel.Repository;
  7 
  8 namespace Terminator.Practice.DomainModel.AppService
  9 {
 10     public class ApplicationBankAccountService
 11     {
 12         private readonly BankAccountService _bankAccountService;
 13         private readonly IBankAccountRepository _bankRepository;
 14 
 15         public ApplicationBankAccountService() :
 16             this(new BankAccountRepository(), new BankAccountService(new BankAccountRepository()))
 17         { }
 18 
 19         public ApplicationBankAccountService(IBankAccountRepository bankRepository, BankAccountService bankAccountService)
 20         {
 21             _bankRepository = bankRepository;
 22             _bankAccountService = bankAccountService;
 23         }
 24 
 25         public ApplicationBankAccountService(BankAccountService bankAccountService, IBankAccountRepository bankRepository)
 26         {
 27             _bankAccountService = bankAccountService;
 28             _bankRepository = bankRepository;
 29         }
 30 
 31         public BankAccountCreateResponse CreateBankAccount(BankAccountCreateRequest bankAccountCreateRequest)
 32         {
 33             BankAccountCreateResponse bankAccountCreateResponse = new BankAccountCreateResponse();
 34             BankAccount bankAccount = new BankAccount();
 35 
 36             bankAccount.CustomerRef = bankAccountCreateRequest.CustomerName;
 37             _bankRepository.Add(bankAccount);
 38 
 39             bankAccountCreateResponse.BankAccountId = bankAccount.AccountNo;
 40             bankAccountCreateResponse.Success = true;
 41 
 42             return bankAccountCreateResponse;
 43         }
 44 
 45         public void Deposit(DepositRequest depositRequest)
 46         {
 47             BankAccount bankAccount = _bankRepository.FindBy(depositRequest.AccountId);
 48 
 49             bankAccount.Deposit(depositRequest.Amount, "");
 50 
 51             _bankRepository.Save(bankAccount);
 52         }
 53 
 54         public void Withdrawal(WithdrawalRequest withdrawalRequest)
 55         {
 56             BankAccount bankAccount = _bankRepository.FindBy(withdrawalRequest.AccountId);
 57 
 58             bankAccount.Withdraw(withdrawalRequest.Amount, "");
 59 
 60             _bankRepository.Save(bankAccount);
 61         }
 62 
 63         public TransferResponse Transfer(TransferRequest request)
 64         {
 65             TransferResponse response = new TransferResponse();
 66 
 67             try
 68             {
 69                 _bankAccountService.Transfer(request.AccountIdTo, request.AccountIdFrom, request.Amount);
 70                 response.Success = true;
 71             }
 72             catch (InsufficientFundsException)
 73             {
 74                 response.Message = "There is not enough funds in account on: " + request.AccountIdFrom.ToString();
 75                 response.Success = false;
 76             }
 77             return response;
 78         }
 79 
 80         public FindAllBankAccountResponse GetAllBankAccounts()
 81         {
 82             FindAllBankAccountResponse FindAllBankAccountResponse = new FindAllBankAccountResponse();
 83             IList<BankAccountView> bankAccountViews = new List<BankAccountView>();
 84             FindAllBankAccountResponse.BankAccountView = bankAccountViews;
 85 
 86             foreach (BankAccount acc in _bankRepository.FindAll())
 87             {
 88                 bankAccountViews.Add(ViewMapper.CreateBankAccountViewFrom(acc));
 89             }
 90 
 91             return FindAllBankAccountResponse;
 92         }
 93 
 94         public FindBankAccountResponse GetBankAccountBy(Guid Id)
 95         {
 96             FindBankAccountResponse bankAccountResponse = new FindBankAccountResponse();
 97             BankAccount acc = _bankRepository.FindBy(Id);
 98             BankAccountView bankAccountView = ViewMapper.CreateBankAccountViewFrom(acc);
 99 
100             foreach (Transaction tran in acc.GetTransactions())
101             {
102                 bankAccountView.Transactions.Add(ViewMapper.CreateTransactionViewFrom(tran));
103             }
104 
105             bankAccountResponse.BankAccount = bankAccountView;
106 
107             return bankAccountResponse;
108         }
109     }
110 }

ApplicationBankAccountService类协调应用程序活动并将所有的业务任务委托给领域模型,该层并不包含任何业务逻辑,有助于防止任何与业务无关的代码污染领域模型项目,该层还将领域实体转换成数据传输对象,从而保护领域的内部操作,并为一起工作的表示层提供了一个易于使用的API。

为了简单起见,这里选择了简陋的依赖注入方式并对默认的构造器硬编码,以便使用已经编码的资源库领域服务实现,后续中将介绍IoC和IoC容器来提供类的依赖关系。

最后就是创建用户界面,从而能够创建帐号并执行交易。

Default.aspx页面
 1 <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Terminator.Practice.DomainModel.UI.Web._Default" %>
 2 
 3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 4 
 5 <html xmlns="http://www.w3.org/1999/xhtml" >
 6 <head runat="server">
 7     <title></title>
 8 </head>
 9 <body>
10     <form id="form1" runat="server">
11     <div>
12      
13      <fieldset>
14         <legend>Create New Account</legend>    
15      <p>        
16         Customer Ref:
17         <asp:TextBox ID="txtCustomerRef" runat="server"></asp:TextBox>
18                 
19         <asp:Button ID="btCreateAccount" runat="server" Text="Create Account" 
20             onclick="btCreateAccount_Click" />
21     </p>
22     </fieldset>
23     
24     <fieldset>
25         <legend>Account Detail</legend> 
26         <p>
27         <asp:DropDownList AutoPostBack="true" ID="ddlBankAccounts" runat="server" 
28                 onselectedindexchanged="ddlBankAccounts_SelectedIndexChanged"></asp:DropDownList>
29         </p>        
30         <p>
31             Account No:
32             <asp:Label ID="lblAccountNo" runat="server" />
33         </p>
34         <p>
35             Customer Ref:
36             <asp:Label ID="lblCustomerRef" runat="server" />
37         </p>
38         <p>
39             Balance:
40             <asp:Label ID="lblBalance" runat="server" />
41         </p>
42         <p>
43             Amount
44         ¥<asp:TextBox ID="txtAmount" runat="server" Width="60px"></asp:TextBox>
45                 
46         &nbsp;<asp:Button ID="btnWithdrawal" runat="server" Text="Withdrawal" 
47                 onclick="btnWithdrawal_Click" />
48         &nbsp;<asp:Button ID="btnDeposit" runat="server" Text="Deposit" 
49                 onclick="btnDeposit_Click" />
50         </p>
51         <p>
52             Transfer
53         ¥<asp:TextBox ID="txtAmountToTransfer" runat="server" Width="60px"></asp:TextBox>
54                 
55         &nbsp;to
56         <asp:DropDownList AutoPostBack="true" ID="ddlBankAccountsToTransferTo" runat="server"></asp:DropDownList>
57         &nbsp;<asp:Button ID="btnTransfer" runat="server" Text="Commit" 
58                 onclick="btnTransfer_Click" />
59         </p>
60         <p>
61             Transactions</p>
62             <asp:Repeater ID="rptTransactions" runat="server">        
63                 <HeaderTemplate>
64                     <table>
65                     <tr>
66                         <td>deposit</td>
67                         <td>withdrawal</td>
68                         <td>reference</td>
69                     </tr>
70                 </HeaderTemplate> 
71                 <ItemTemplate>
72                     <tr>
73                         <td><%# Eval("Deposit")  %></td>
74                         <td><%# Eval("Withdrawal")  %></td>
75                         <td><%# Eval("Reference")  %></td>                
76                         <td><%# Eval("Date")  %></td>
77                     </tr>
78                 </ItemTemplate> 
79                 <FooterTemplate>
80                     </table>
81                 </FooterTemplate>   
82             </asp:Repeater>
83     </fieldset>    
84     </div> 
85     </form>
86 </body>
87 </html>
Default.aspx.cs代码
  1 using System;
  2 using System.Web.UI.WebControls;
  3 using Terminator.Practice.DomainModel.AppService;
  4 using Terminator.Practice.DomainModel.AppService.Messages;
  5 using Terminator.Practice.DomainModel.AppService.ViewModels;
  6 
  7 namespace Terminator.Practice.DomainModel.UI.Web
  8 {
  9     public partial class _Default : System.Web.UI.Page
 10     {
 11         protected void Page_Load(object sender, EventArgs e)
 12         {
 13             if (!Page.IsPostBack)
 14                 ShowAllAccounts();
 15         }
 16 
 17         private void ShowAllAccounts()
 18         {
 19             ddlBankAccounts.Items.Clear();  
 20 
 21             FindAllBankAccountResponse response = new ApplicationBankAccountService().GetAllBankAccounts();
 22             ddlBankAccounts.Items.Add(new ListItem("Select An Account", ""));
 23 
 24             foreach (BankAccountView accView in response.BankAccountView)
 25             {
 26                 ddlBankAccounts.Items.Add(new ListItem(accView.CustomerRef, accView.AccountNo.ToString()));
 27             }
 28         }
 29 
 30         protected void btCreateAccount_Click(object sender, EventArgs e)
 31         {
 32             BankAccountCreateRequest createAccountRequest = new BankAccountCreateRequest();
 33             createAccountRequest.CustomerName = txtCustomerRef.Text;           
 34             ApplicationBankAccountService service = new ApplicationBankAccountService();
 35 
 36             service.CreateBankAccount(createAccountRequest);   
 37          
 38             ShowAllAccounts();
 39         }
 40 
 41         protected void ddlBankAccounts_SelectedIndexChanged(object sender, EventArgs e)
 42         {
 43             DisplaySelectedAccount();
 44         }
 45 
 46         private void DisplaySelectedAccount()
 47         {
 48             if (ddlBankAccounts.SelectedValue != "")
 49             {
 50                 ApplicationBankAccountService service = new ApplicationBankAccountService();
 51                 FindBankAccountResponse response = service.GetBankAccountBy(new Guid(ddlBankAccounts.SelectedValue));
 52                 BankAccountView accView = response.BankAccount;
 53 
 54                 lblAccountNo.Text = accView.AccountNo.ToString();
 55                 lblBalance.Text = accView.Balance;
 56                 lblCustomerRef.Text = accView.CustomerRef;
 57 
 58                 rptTransactions.DataSource = accView.Transactions;
 59                 rptTransactions.DataBind();
 60 
 61                 FindAllBankAccountResponse allAccountsResponse = service.GetAllBankAccounts();
 62 
 63                 ddlBankAccountsToTransferTo.Items.Clear();
 64 
 65                 foreach (BankAccountView acc in allAccountsResponse.BankAccountView)
 66                 {
 67                     if (acc.AccountNo.ToString() != ddlBankAccounts.SelectedValue)
 68                         ddlBankAccountsToTransferTo.Items.Add(new ListItem(acc.CustomerRef, acc.AccountNo.ToString()));
 69                 }
 70             }
 71         }
 72 
 73         protected void btnWithdrawal_Click(object sender, EventArgs e)
 74         {
 75             ApplicationBankAccountService service = new ApplicationBankAccountService();
 76             WithdrawalRequest request = new WithdrawalRequest();
 77             Guid AccId = new Guid(ddlBankAccounts.SelectedValue);
 78             request.AccountId = AccId;
 79             request.Amount = Decimal.Parse(txtAmount.Text);
 80 
 81             service.Withdrawal(request);
 82             DisplaySelectedAccount();
 83         }
 84 
 85         protected void btnDeposit_Click(object sender, EventArgs e)
 86         {
 87             ApplicationBankAccountService service = new ApplicationBankAccountService();
 88             DepositRequest request = new DepositRequest(); 
 89             Guid AccId = new Guid(ddlBankAccounts.SelectedValue);
 90             request.AccountId = AccId;
 91             request.Amount = Decimal.Parse(txtAmount.Text);
 92 
 93             service.Deposit(request);
 94             DisplaySelectedAccount();
 95         }
 96 
 97         protected void btnTransfer_Click(object sender, EventArgs e)
 98         {
 99             ApplicationBankAccountService service = new ApplicationBankAccountService();
100             TransferRequest request = new TransferRequest();            
101             request.AccountIdFrom = new Guid(ddlBankAccounts.SelectedValue);
102             request.AccountIdTo = new Guid(ddlBankAccountsToTransferTo.SelectedValue);
103             request.Amount = Decimal.Parse(txtAmountToTransfer.Text);
104 
105             service.Transfer(request);
106             DisplaySelectedAccount();
107             
108         }
109     }
110 }
Default.aspx.designer.cs代码
  1 //------------------------------------------------------------------------------
  2 // <自动生成>
  3 //     此代码由工具生成。
  4 //
  5 //     对此文件的更改可能会导致不正确的行为,并且如果
  6 //     重新生成代码,这些更改将会丢失。 
  7 // </自动生成>
  8 //------------------------------------------------------------------------------
  9 
 10 namespace Terminator.Practice.DomainModel.UI.Web {
 11     
 12     
 13     public partial class _Default {
 14         
 15         /// <summary>
 16         /// form1 控件。
 17         /// </summary>
 18         /// <remarks>
 19         /// 自动生成的字段。
 20         /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。
 21         /// </remarks>
 22         protected global::System.Web.UI.HtmlControls.HtmlForm form1;
 23         
 24         /// <summary>
 25         /// txtCustomerRef 控件。
 26         /// </summary>
 27         /// <remarks>
 28         /// 自动生成的字段。
 29         /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。
 30         /// </remarks>
 31         protected global::System.Web.UI.WebControls.TextBox txtCustomerRef;
 32         
 33         /// <summary>
 34         /// btCreateAccount 控件。
 35         /// </summary>
 36         /// <remarks>
 37         /// 自动生成的字段。
 38         /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。
 39         /// </remarks>
 40         protected global::System.Web.UI.WebControls.Button btCreateAccount;
 41         
 42         /// <summary>
 43         /// ddlBankAccounts 控件。
 44         /// </summary>
 45         /// <remarks>
 46         /// 自动生成的字段。
 47         /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。
 48         /// </remarks>
 49         protected global::System.Web.UI.WebControls.DropDownList ddlBankAccounts;
 50         
 51         /// <summary>
 52         /// lblAccountNo 控件。
 53         /// </summary>
 54         /// <remarks>
 55         /// 自动生成的字段。
 56         /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。
 57         /// </remarks>
 58         protected global::System.Web.UI.WebControls.Label lblAccountNo;
 59         
 60         /// <summary>
 61         /// lblCustomerRef 控件。
 62         /// </summary>
 63         /// <remarks>
 64         /// 自动生成的字段。
 65         /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。
 66         /// </remarks>
 67         protected global::System.Web.UI.WebControls.Label lblCustomerRef;
 68         
 69         /// <summary>
 70         /// lblBalance 控件。
 71         /// </summary>
 72         /// <remarks>
 73         /// 自动生成的字段。
 74         /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。
 75         /// </remarks>
 76         protected global::System.Web.UI.WebControls.Label lblBalance;
 77         
 78         /// <summary>
 79         /// txtAmount 控件。
 80         /// </summary>
 81         /// <remarks>
 82         /// 自动生成的字段。
 83         /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。
 84         /// </remarks>
 85         protected global::System.Web.UI.WebControls.TextBox txtAmount;
 86         
 87         /// <summary>
 88         /// btnWithdrawal 控件。
 89         /// </summary>
 90         /// <remarks>
 91         /// 自动生成的字段。
 92         /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。
 93         /// </remarks>
 94         protected global::System.Web.UI.WebControls.Button btnWithdrawal;
 95         
 96         /// <summary>
 97         /// btnDeposit 控件。
 98         /// </summary>
 99         /// <remarks>
100         /// 自动生成的字段。
101         /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。
102         /// </remarks>
103         protected global::System.Web.UI.WebControls.Button btnDeposit;
104         
105         /// <summary>
106         /// txtAmountToTransfer 控件。
107         /// </summary>
108         /// <remarks>
109         /// 自动生成的字段。
110         /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。
111         /// </remarks>
112         protected global::System.Web.UI.WebControls.TextBox txtAmountToTransfer;
113         
114         /// <summary>
115         /// ddlBankAccountsToTransferTo 控件。
116         /// </summary>
117         /// <remarks>
118         /// 自动生成的字段。
119         /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。
120         /// </remarks>
121         protected global::System.Web.UI.WebControls.DropDownList ddlBankAccountsToTransferTo;
122         
123         /// <summary>
124         /// btnTransfer 控件。
125         /// </summary>
126         /// <remarks>
127         /// 自动生成的字段。
128         /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。
129         /// </remarks>
130         protected global::System.Web.UI.WebControls.Button btnTransfer;
131         
132         /// <summary>
133         /// rptTransactions 控件。
134         /// </summary>
135         /// <remarks>
136         /// 自动生成的字段。
137         /// 若要进行修改,请将字段声明从设计器文件移到代码隐藏文件。
138         /// </remarks>
139         protected global::System.Web.UI.WebControls.Repeater rptTransactions;
140     }
141 }

 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 on 2013-01-06 08:59  夜蹲寡妇门  阅读(1154)  评论(5编辑  收藏  举报