Domain Model

Domain Model案例,项目结构图

 ASPPatterns.Chap4.DomainModel.Model:Domain Model项目将包含应用程序内所有的业务逻辑。领域对象将存放在此处,并于其他对象建立关系,从而表示应用程序正在构建的银行领域。该项目还将以接口的形式为领域对象持久化和检索定义契约,将采用Repository模式来实现所有的持久化管理需求。Model项目不会引用其他任何项目,从而确保:让它与任何基础设施关注点保持隔离,只关注业务领域。

ASPPatterns.Chap4.DomainModel.Repository:Repository项目将包含Model项目中定义的资源库接口的具体实现。Repository引用了Model项目,从而从数据库提取并持久化领域对象。Repository项目只关注领域对象持久化和检索的责任。  

ASPPatterns.Chap4.DomainModel.AppService:AppService项目充当应用程序的网关(API如果愿意的话)。表示层将通过消息(简单的数据传输对象)与AppService通信。AppService层还将定义试图模型,这些是领域模型的展开试图,只用于数据显示。

ASPPatterns.Chap4.DomainModel.UI.Web:UI.Web项目负责应用程序的表示和用户体验需求。该项目只与AppService交互,并接收专门为用户体验视图创建的强类型视图模型。

 


 

在一个项目中,业务逻辑最为重要,先看存放业务逻辑的Model项目

在Model项目下创建Transaction.cs文件,Transaction对象是一个值对象。

using System;

namespace ASPPatterns.Chap4.DomainModel.Model
{
    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.cs

再在Model项目下创建BankAccount对象。

using System;
using System.Collections.Generic;

namespace ASPPatterns.Chap4.DomainModel.Model
{
    /// <summary>
    /// Withdraw CanWithdraw
    /// </summary>
    public class BankAccount
    {
        decimal _balance;
        Guid _accountNo;
        string _customerRef;
        IList<Transaction> _transactions;
        public BankAccount()
            : this(Guid.NewGuid(), 0, new List<Transaction>(), "")
        {
            _transactions.Add(new Transaction(0m, 0m, "account Created", DateTime.Now));
        }

        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 BankAccount(Guid id, decimal balance, IList<Transaction> transaction, string customerRef)
        {
            _accountNo = id;
            _balance = balance;
            _transactions = transaction;
            _customerRef = customerRef;
        }

        /// <summary>
        /// 当用户尝试从某个账号取回现金的时候,先使用CanWithdraw方法来判断该用户是否可以取钱。(Test-Doer模式,先测试,再执行)
        /// </summary>
        /// <param name="amount"></param>
        /// <returns></returns>
        public bool CanWithdraw(decimal amount)
        {
            return (Balance >= amount);
        }

        /// <summary>
        /// 取款
        /// </summary>
        /// <param name="amount"></param>
        /// <param name="reference"></param>
        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();//当用户没有足够的现金金额去取款的时候,就要抛出一个异常。
            }
        }
        /// <summary>
        /// 用户存款
        /// </summary>
        /// <param name="amount"></param>
        /// <param name="reference"></param>
        public void Deposit(decimal amount, string reference)
        {
            Balance += amount;
            _transactions.Add(new Transaction(0m, amount, reference, DateTime.Now));
        }

        public IEnumerable<Transaction> GetTransactions()
        {
            return _transactions;
        }
    }
}
BankAccount.cs

当用户没有足够的现金金额去取款的时候,就要抛出一个异常。所以需要创建一个类来负责抛出异常。该类仍然处于Model下。

using System;

namespace ASPPatterns.Chap4.DomainModel.Model
{
    public class InsufficientFundsException:ApplicationException
    {
        
    }
}
InsufficientFundsException.cs

现在需要某种方法来持久化BankAccount和Transactions。Model项目之定义接口,具体实现由Repository项目来实现。

using System;
using System.Collections.Generic;

namespace ASPPatterns.Chap4.DomainModel.Model
{
    public interface IBankAccountRepository
    {
        void Add(BankAccount bankAccount);
        void Save(BankAccount bankAccount);
        IEnumerable<BankAccount> FindAll();
        BankAccount FindBy(Guid AccountId);
    }
}
IBankAccountRepository.cs

有些动作没有很好地映射到领域实体的方法。对于这类情况,可以使用领域服务,在两个账号之间转账的动作就是一种属于服务类的责任。仍需要将BankAccountService定义在Model项目中。

using System;

namespace ASPPatterns.Chap4.DomainModel.Model
{
    public class BankAccountService
    {
        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();
            }
        }

    }
}
BankAccountService.cs

 在BankAccountService的当前实现中,在保存两个银行账号之间发生的任何错误均会让数据处于非法状态。(使用unit of work来解决这个问题)


Demo中的转账的功能大致如此,移步至Repository项目。

编写用来持久化BankAccount和Transaction业务对象的方法。BankAccountRepository需要继承先前在Model中定义的接口IBankAccountRepository,并实现接口中定义的方法。

using ASPPatterns.Chap4.DomainModel.Model;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;

namespace ASPPatterns.Chap4.DomainModel.Repository
{
    public class BankAccountRepository : IBankAccountRepository
    {
        private string _conStr;
        public BankAccountRepository()
        {
            _conStr = "";//可以从配置文件中获取数据库连接字符串
        }
        public void Add(BankAccount bankAccount)
        {
            string insertSql = string.Format("Insert into BankAccounts (bankAccountId,balance,customerRef) values('{0}','{1}','{2}')", bankAccount.AccountNo, bankAccount.Balance, bankAccount.CustomerRef);
            using (SqlConnection connection = new SqlConnection(_conStr))
            {
                SqlCommand command = connection.CreateCommand();
                command.CommandText = insertSql;
                connection.Open();
                command.ExecuteNonQuery();
            }
        }



        public void Save(BankAccount bankAccount)
        {
            string bankAccountUpdateSql = string.Format("update bankAccounts set balance='{0}',customerRef='{1}' where bankAccountID='{2}'", bankAccount.Balance, bankAccount.CustomerRef, bankAccount.AccountNo);
            using (SqlConnection connection = new SqlConnection(_conStr))
            {
                SqlCommand command = connection.CreateCommand();
                command.CommandText = bankAccountUpdateSql;
                connection.Open();
                command.ExecuteNonQuery();
            }
        }

        public IEnumerable<BankAccount> FindAll()
        {
            IList<BankAccount> accounts = new List<BankAccount>();
            string queryStr = "select * from transactions t inner join bankaccounts b on t.bankaccountID=b.bankAccountid order by b.bankaccountid";
            using (SqlConnection connection = new SqlConnection(_conStr))
            {
                SqlCommand command = connection.CreateCommand();
                command.CommandText = queryStr;
                connection.Open();
                using (SqlDataReader reader = command.ExecuteReader())
                {
                    //做映射,将查询到的数据赋值给accounts
                }
            }
            return accounts;
        }

        public BankAccount FindBy(Guid AccountId)
        {
            BankAccount account = null;
            string sqlCmd = string.Format("select * from transaction t inner join bankaccount b on t.bankaccountid=b.bankaccountid where b.bankaccountid='{0}'", AccountId);
            using (SqlConnection connection = new SqlConnection(_conStr))
            {
                SqlCommand command = connection.CreateCommand();
                command.CommandText = sqlCmd;
                //

            }
            return account;
        }
    }
}
BankAccountRepository.cs

做好持久化方面的工作后,再看Service层

在AppService层内创建文件夹ViewModel,并在该文件夹中添加两个视图模型BankAccountView和TransactionView。这两个类的作用是用于界面显示,当BankAccount和Transaction对象中有数据,并要显示在界面上的时候,会使用映射器将BankAccount和Transaction对象中的数据映射到BankAccountView和TransactionView对象上,Demo中是手写了一个映射器,项目中可以使用AutoMapper在ASP.NET MVC中,结合razor语言,将数据显示在界面上。

using System;
using System.Collections.Generic;

namespace ASPPatterns.Chap4.DomainModel.AppService.ViewModel
{
    public class BankAccountView
    {
        public Guid AccountNo { get; set; }
        public string Balance { get; set; }
        public string CustomerRef { get; set; }
        public IList<TransactionView> Transactions { get; set; }
    }
}
BankAccountView.cs
using System;

namespace ASPPatterns.Chap4.DomainModel.AppService.ViewModel
{
    public class TransactionView
    {
        public string Deposit { get; set; }
        public string Withdrawal { get; set; }
        public string Reference { get; set; }
        public DateTime Date { get; set; }
    }
}
TransactionView.cs

映射器ViewMapper

using ASPPatterns.Chap4.DomainModel.AppService.ViewModel;
using ASPPatterns.Chap4.DomainModel.Model;
using System.Collections.Generic;

namespace ASPPatterns.Chap4.DomainModel.AppService
{
    public static class ViewMapper
    {
        public static TransactionView CreateTransactionViewFrom(Transaction tran)
        {
            return new TransactionView
            {
                Deposit = tran.Deposit.ToString(),
                Withdrawal = tran.Withdrawal.ToString(),
                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>()
            };
        }
    }
}
ViewMapper

 

再在Appservice层下创建一个Messages文件夹,该文件夹中包含了所有用来与服务层通信的请求-应答对象。使用Messaging(消息传送)模式为了使让所有的API对外返回的信息格式统一。

先创建基类ResponseBase

namespace ASPPatterns.Chap4.DomainModel.AppService.Messages
{
    public abstract class ResponseBase
    {
        public bool Success { get; set; }
        public string Message { get; set; }
    }
}
ResponseBase.cs

 

创建所有的,需要实现请求和应答的对象

namespace ASPPatterns.Chap4.DomainModel.AppService.Messages
{
    public class BankAccountCreateRequest
    {
        public string CustomerName { get; set; }
    }
}
BankAccountCreateRequest.cs
using System;

namespace ASPPatterns.Chap4.DomainModel.AppService.Messages
{
    public class BankAccountCreateReponse : ResponseBase
    {
        public Guid BankAccountId { get; set; }
    }
}
BankAccountCreateReponse
using System;

namespace ASPPatterns.Chap4.DomainModel.AppService.Messages
{
    public class DepositRequest
    {
        public Guid AccountId { get; set; }
        public decimal Amount { get; set; }
    }
}
DepositRequest.cs
using ASPPatterns.Chap4.DomainModel.AppService.ViewModel;
using System.Collections.Generic;

namespace ASPPatterns.Chap4.DomainModel.AppService.Messages
{
    public class FindAllBankAccountResponse : ResponseBase
    {
        public IList<BankAccountView> BankAccounView { get; set; }
    }
}
FindAllBankAccountResponse.cs
using ASPPatterns.Chap4.DomainModel.AppService.ViewModel;

namespace ASPPatterns.Chap4.DomainModel.AppService.Messages
{
    public class FindBankAccountResponse
    {
        public BankAccountView BankAccount { get; set; }
    }
}
FindBankAccountResponse.cs
using System;

namespace ASPPatterns.Chap4.DomainModel.AppService.Messages
{
    public class TransferRequest
    {
        public Guid AccountIdTo { get; set; }
        public Guid AccountIdFrom { get; set; }
        public decimal Amount { get; set; }

    }
}
TransferRequest.cs
namespace ASPPatterns.Chap4.DomainModel.AppService.Messages
{
    public class TransferResponse : ResponseBase
    {

    }
}
TransferResponse.cs
using System;

namespace ASPPatterns.Chap4.DomainModel.AppService.Messages
{
    public class WithdrawalRequest
    {
        public Guid AccountId { get; set; }
        public decimal Amount { get; set; }
    }
}
WithdrawalRequest.cs

 ApplicationBankAccountService类协调应用程序活动并将所有的业务任务委托给领域模型。该层并不包含任何业务逻辑,有助于防止任何与业务无关的代码污染领域模型项目。

该层还将领域实体转换成数据传输对象,从而保护领域的内部操作,并为一起工作的表示层提供了一个易于使用的API。

using ASPPatterns.Chap4.DomainModel.AppService.Messages;
using ASPPatterns.Chap4.DomainModel.AppService.ViewModel;
using ASPPatterns.Chap4.DomainModel.Model;
using ASPPatterns.Chap4.DomainModel.Repository;
using System;
using System.Collections.Generic;

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

        public ApplicationBankAccountService() : this(new BankAccountRepository(), new BankAccountService(new BankAccountRepository()))
        {

        }
        public ApplicationBankAccountService(IBankAccountRepository bankRepository, BankAccountService bankAccountService)
        {
            _bankRepository = bankRepository;
            _bankAccountService = bankAccountService;
        }

        public BankAccountCreateReponse CreateBankAccount(BankAccountCreateRequest bankAccountCreateRequest)
        {
            BankAccountCreateReponse bankAccountCreateReponse = new BankAccountCreateReponse();
            BankAccount bankAccount = new BankAccount(new Guid(), 12.12M, new List<Transaction>(), "customerRef");
            bankAccount.CustomerRef = bankAccountCreateRequest.CustomerName;
            _bankRepository.Add(bankAccount);
            return bankAccountCreateReponse;
        }

        public void Deposit(DepositRequest depositRequest)
        {
            BankAccount bankAccount = _bankRepository.FindBy(depositRequest.AccountId);
            bankAccount.Deposit(depositRequest.Amount, "");
            _bankRepository.Save(bankAccount);
        }

        public void Withdrawal(WithdrawalRequest withdrawalRequest)
        {
            BankAccount bankAccount = _bankRepository.FindBy(withdrawalRequest.AccountId);
            bankAccount.Withdraw(withdrawalRequest.Amount, "");
            _bankRepository.Save(bankAccount);
        }

        public TransferResponse Transfer(TransferRequest request)
        {
            TransferResponse response = new TransferResponse();
            try
            {
                _bankAccountService.Transfer(request.AccountIdTo, request.AccountIdFrom, request.Amount);
                response.Success = true;
            }
            catch (Exception)
            {
                response.Message = "There is not enough funds in account no:" + request.AccountIdFrom.ToString();
                response.Success = false;
            }
            return response;
        }

        public FindAllBankAccountResponse GetAllBankAccounts()
        {
            FindAllBankAccountResponse findAllBankAccountResponse = new FindAllBankAccountResponse();
            IList<BankAccountView> bankAccountViews = new List<BankAccountView>();
            findAllBankAccountResponse.BankAccounView = bankAccountViews;
            foreach (BankAccount acc in _bankRepository.FindAll())
            {
                bankAccountViews.Add(ViewMapper.CreateBankAccountViewFrom(acc));
            }
            return findAllBankAccountResponse;
        }

        public FindBankAccountResponse GetBankAccountBy(Guid Id)
        {
            FindBankAccountResponse bankAccountResponse = new FindBankAccountResponse();
            BankAccount acc = _bankRepository.FindBy(Id);
            BankAccountView bankAccountView = ViewMapper.CreateBankAccountViewFrom(acc);
            foreach (Transaction tran in acc.GetTransactions())
            {
                bankAccountView.Transactions.Add(ViewMapper.CreateTransactionViewFrom(tran));
            }
            bankAccountResponse.BankAccount = bankAccountView;
            return bankAccountResponse;
        }
    }
}
ApplicationBankAccountService.cs

 

在UI层调用Service。UI曾的界面没有展示,那不是重点。重点考察如何在UI层中调用Service。

using ASPPatterns.Chap4.DomainModel.AppService;
using ASPPatterns.Chap4.DomainModel.AppService.Messages;
using ASPPatterns.Chap4.DomainModel.AppService.ViewModel;
using System;
using System.Web.UI;

namespace ASPPatterns.Chap4.DomainModel.UI.Web
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                ShowAllAccounts();
            }
        }

        private void ShowAllAccounts()
        {
            ddlBankAccounts.Items.Clear();
            FindAllBankAccountResponse response = new ApplicationBankAccountService().GetAllBankAccounts();
            ddlBankAccounts.Items.Add(new ListItem("Select An Account", ""));
            foreach (BankAccountView accView in response.BankAccounView)
            {
                ddlBankAccounts.Items.Add(new ListItem(accView.CustomerRef, accView.AccountNo.ToString()));
            }
        }

        protected void btCreateAccount_Click(object sender, EventArgs e)
        {
            BankAccountCreateRequest createAccountRequest = new BankAccountCreateRequest();
            ApplicationBankAccountService service = new ApplicationBankAccountService();
            service.CreateBankAccount(createAccountRequest);
            ShowAllAccounts();
        }

        protected void ddlBankAccounts_SelectedIndexChanged(object sender, EventArgs e)
        {
            DisplaySelectedAccount();
        }

        private void DisplaySelectedAccount()
        {
            if (ddlBankAccounts.SelectedValue.ToString() != "")
            {
                ApplicationBankAccountService service = new ApplicationBankAccountService();
                FindBankAccountResponse response = service.GetBankAccountBy(new Guid());
                BankAccountView accView = response.BankAccount;

                lblAccount.Text = accView.Balance.ToString();
                lblBalance.Text = accView.Balance.ToString();
                lblCustomerRef.Text = accView.CustomerRef;

                rptTransactions.DataSource = accView.Transactions;
                rptTransactions.DataBind();

                FindAllBankAccountResponse allAccountResponse = service.GetAllBankAccounts();
                ddlBankAccountsToTransferTo.Items.Clear();

                foreach (var acc in allAccountResponse.BankAccounView)
                {
                    if (acc.AccountNo.ToString() != ddlBankAccounts.SelectedValue.ToString())
                        ddlBankAccountsToTransferTo.Items.Add(new ListItem(acc.CustomerRef, acc.AccountNo.ToString()));
                }
            }
        }

        protected void btnWithdrawal_Click(object sender, EventArgs e)
        {
            ApplicationBankAccountService service = new ApplicationBankAccountService();
            WithdrawalRequest request = new WithdrawalRequest();
            Guid guid = new Guid();
            request.AccountId = guid;
            request.Amount = decimal.Parse(txtAmount.Text);
            service.Withdrawal(request);
            DisplaySelectedAccount();
        }

        protected void btnDeposit_Click(object sender, EventArgs e)
        {
            ApplicationBankAccountService service = new ApplicationBankAccountService();
            DepositRequest request = new DepositRequest();
            Guid guid = new Guid();
            request.AccountId = guid;
            request.Amount = decimal.Parse(txtAmount.Text);
            service.Deposit(request);
            DisplaySelectedAccount();
        }
    }
}
Default.cs

 

posted @ 2020-06-05 10:58  水墨晨诗  阅读(443)  评论(0编辑  收藏  举报