zoukankan      html  css  js  c++  java
  • 一起谈.NET技术,走向ASP.NET架构设计——第四章:业务层分层架构(后篇) 狼人:

      今天的内容比较简单,也是本章的一个收尾!

      Anemic Domain Model

      这种模式和之前讲述的Domain Model有很多的相似的地方。在之前的Domain Model中,每个业务类都包含了自己的业务逻辑和数据,以及对象之前的关系;但是在Anemic Domain Model,每个业务类仅仅只是包含了一些保存业务数据的属性,把相应的业务规则从原本的业务类中移到了另外的一个专门的业务规则类(Specification Pattern,我们后面的章节讲述),同时把相应的业务方法移到了service类中。之前在Domain Model中,service类包含了一些粗颗粒度的业务方法,现在service类中也包含了原本在业务类中的细粒度的业务方法。

      我们再来看看在这种组织方式下,我们之前的代码如何写:

    public class Transaction
    {
    public Guid Id{ get; set; }
    public decimal Deposit{ get; set; }
    public decimal Withdraw{ get; set; }
    public string Reference{ get; set; }
    public DateTime Date{ get; set; }
    public Guid BankAccountId{ get; set; }
    }

      大家看到上面的代码,里面已经没有了业务逻辑的方法和一些规则,这些东西都被移到了另外的一个类中:

    public class BankAccountHasEnoughFundsToWithdrawSpecification
    {
    private decimal _amountToWithdraw;
    public BankAccountHasEnoughFundsToWithdrawSpecification(decimal amountToWithdraw)
    {
    _amountToWithdraw
    = amountToWithdraw;
    }
    public bool IsSatisfiedBy(BankAccount bankAccount)
    {
    return bankAccount.Balance >= _amountToWithdraw;
    }
    }

      大家在看看service类,现在service类中方法就很多了:

    public class ApplicationBankAccountService
    {
    private BankAccountService _bankAccountService;
    private IBankAccountRepository _bankRepository;

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

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

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

    public BankAccountCreateResponse CreateBankAccount(BankAccountCreateRequest bankAccountCreateRequest)
    {
    BankAccountCreateResponse bankAccountCreateResponse
    = new BankAccountCreateResponse();
    BankAccount bankAccount
    = _bankAccountService.CreateBankAccount(bankAccountCreateRequest.CustomerName);
    bankAccountCreateResponse.BankAccountId
    = bankAccount.AccountNo;
    bankAccountCreateResponse.Success
    = true;

    return bankAccountCreateResponse;
    }

    public void Deposit(DepositRequest depositRequest)
    {
    _bankAccountService.Deposit(depositRequest.AccountId, depositRequest.Amount,
    "");
    }

    public void Withdrawal(WithdrawalRequest withdrawalRequest)
    {
    _bankAccountService.Withdraw(withdrawalRequest.AccountId, withdrawalRequest.Amount,
    "");
    }

    public TransferResponse Transfer(TransferRequest request)
    {
    TransferResponse response
    = new TransferResponse();

    try
    {
    _bankAccountService.Transfer(request.AccountIdTo, request.AccountIdFrom, request.Amount);
    response.Success
    = true;
    }
    catch (InsufficientFundsException)
    {
    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.BankAccountView
    = 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.Transactions)
    {
    bankAccountView.Transactions.Add(ViewMapper.CreateTransactionViewFrom(tran));
    }

    bankAccountResponse.BankAccount
    = bankAccountView;

    return bankAccountResponse;
    }

    }

      到这里为止,四种组织业务逻辑的模式就讲述完了,每一种都有自己的用途,无所谓“一定用,或者一定不用”。到底是用哪种,都是根据项目和经验而定。

      DDD

      下面我们就来进入DDD,这里只是讲述了一下DDD中的一些基本概念,至于具体的讲述DDD:

      1、后面的章节会陆续的介绍。

      2、阅读《领域驱动设计.软件核心复杂性应对之道》,如果朋友们有需要,留下自己的Email,我会发送给大家。

      下面的一些的文字都是摘自一些书籍。目的只是一个为了让大家快速的了解一下DDD。

      DDD几个概念:

      分层架构

      实体

      值对象

      服务

      模块

      聚合

      工厂

      分层架构

      当我们创建一个软件应用时,这个应用的很大一部分是不能直接跟领域关联的,但它们是基础设施的一部分或者是为软件服务的。最好能让应用中的领域部分尽可能少地和其他的部分掺杂在一起,因为一个典型的应用包含了很多和数据库访问,文件或网络访问以及用户界面等相关的代码。

      在一个面向对象的程序中,用户界面、数据库以及其他支持性代码经常被直接写到业务对象中。附加的业务逻辑被嵌入到UI 组件和数据库脚本的行为中。之所以这样做的某些原因是这样可以很容易地让事情快速工作起来。

      但是,当领域相关的代码被混入到其他层时,要阅读和思考它也变得极其困难。表面看上去是对UI 的修改,却变成了对业务逻辑的修改。对业务规则的变更可能需要谨慎跟踪用户界面层代码、数据库代码以及其他程序元素。实现粘连在了一起,模型驱动对象于是变得不再可行。也很难使用自动化测试。对于每个活动中涉及到的技术和逻辑,程序必须保持简单,否则就会变得很难理解。因此,将一个复杂的程序切分成层。开发每一个层中内聚的设计,让每个层仅依赖于它底下的那层。遵照标准的架构模式以提供层的低耦合。将领域模型相关的代码集中到一个层中,把它从用户界面、应用和基础设施代码中分隔开来。释放领域对象的显示自己、保存自己、管理应用任务等职责,让它专注于展现领域模型。这会让一个模型进一步富含知识,更清晰地捕获基础的业务知识,让它们正常工作。

      一个通用领域驱动设计的架构性解决方案包含4 个概念层:

      将应用划分成分离的层并建立层间的交换规则很重要。如果代码没有被清晰隔离到某层中,它会迅即混乱,因为它变得非常难以管理变更。在某处对代码的一个简单修改会对其他地方的代码造成不可估量的结果。领域层应该关注核心的领域问题。它应该不涉及基础设施类的活动。用户界面既不跟业务逻辑紧密捆绑也不包含通常属于基础设施层的任务。在很多情况下应用层是必要的。它会成为业务逻辑之上的管理者,用来监督和协调应用的整个活动。

      例如,对一个典型的交互型应用,领域和基础设施层看上去会这样:用户希望预定一个飞行路线,要求用一个应用层中的应用服务来完成。应用依次从基础设施中取得相关的领域对象,调用它们的相关方法,比如检查与另一个已经被预定的飞行线路的安全边界。当领域对象执行完所有的检查并修改了它们的状态决定后,应用服

    务将对象持久化到基础设施中。 

      实体

      有一类对象看上去好像拥有标识符,它的标识符在历经软件的各种状态后仍能保持一致。对这些对象来讲这已经不再是它们关心的属性,这意味着能够跨越系统的生命周期甚至能超越软件系统的一系列的延续性和标识符。我们把这样的对象称为实体。

      OOP 语言会把对象的实例放于内存,它们对每个对象会保持一个对像引用或者是记录一个对象地址。在给定的某个时刻,这种引用对每一个对象而言是唯一的,但是很难保证在不确定的某个时间段它也是如此。实际上恰恰相反。对象经常被移出或者移回内存,它被序列化后在网络上传输,然后在另一端被重新建立,或者它们都被消除。在程序的运行环境中,那个看起来像标识符的引用关系其实并不是我们在谈论的标识符。

      如果有一个存放了天气信息(如温度)的类,很容易产生同一个类的不同实例,这两个实例都包含了同样的值,这两个对象是完全相当的,可以用其中一个跟另一个交换,但它们拥有不同的引用,它们不是实体。如果我们要用软件程序实现一个“人”的概念,我们可能会创建一个Person 类,这个类会带有一系列的属性,如:名称,出生日期,出生地等。这些属性中有哪个可以作为Person 的标识符吗?名字不可以作为标识符,因为可能有很多人拥有同一个名字。如果我们只

    考虑两个人的名字的话,我们不能使用同一个名字来区分他们两个。我们也不能使用出生日期作为标识符,因为会有很多人出在同一天出生。同样也不能用出生地作为标识符。一个对象必须与其他的对象区分开来,即使是它们拥有着相同的属性。错误的标识符可能会导致数据混乱。

      考虑一下一个银行会计系统。每一个账户拥有它自己的数字码。每一个账户可以用它的数字码来精确标识。这个数字码在系统的生命周期中会保持不变,并保证延续性。账户码可以作为一个对象存在于内存中,也可以被在内存中销毁,发送到数据库中。当这个账户被关闭时,它还可以被归档,只要还有人对它感兴趣,它就依然在某处存在。不论它的表现形式如何,数字码会保持一致。因此,在软件中实现实体意味着创建标识符。对一个人而言,其标识符可能是属性的组合:名称,出生日期,出生地,父母名称、当前地址。在美国,社会保险号也会用来创建标识符。对一个银行账户来说,账号看上去已经足可以作为标识符了。通常标识符或是对

    象的一个属性(或属性的组合),一个专门为保存和表现标识符而创建的属性,也或是一种行为。对两个拥有不同标识符的对象来说,能用系统轻易地把它们区分开来,或者两个使用了相同标识符对象能被系统看成是相同的,这些都是非常重要的。如果不能满足这个条件,整个系统可能是有问题的。

      有很多不同的方式来为每一个对象创建一个唯一的标识符:可能由一个模型来自动产生ID,在软件中内部使用,不会让它对用户可见;它可能是数据库表的一个主键,会被保证在数据库中是唯一的。只要对象从数据库中被检索,它的ID 就会被检索出并在内存中被重建;ID 也可能由用户创建,例如每个机场会有一个关联的代

    码。每个机场拥有一个唯一的字符串ID,这个字符串是在世界范围内通用的,被世界上的每一个旅行代理使用以标识它们的旅行计划中涉及的机场。另一种解决方案是使用对象的属性来创建标识符,当这个属性不足以代表标识符时,另一个属性就会被加入以帮助确定每一个对象。

      当一个对象可以用其标识符而不是它的属性来区分时,可以将它作为模型中的主要定义。保证类定义简洁并关注生命周期的延续性和可标识性。对每个对象定义一个有意义的区分,而不管它的形式或者历史。警惕要求使用属性匹配对象的需求。定义一个可以保证对每一个对象产生一个唯一的结果的操作,这个过程可能需要某个符号以保证唯一性。这意味着标识可以来自外部,或者它可以是由系统产生、使用任意的标识符,但它必须符合模型中的身份差别。模型必须定义哪些被看作同一事物。

      实体是领域模型中非常重要的对象,并且它们应该在建模过程开始时就被考虑。决定一个对象是否需要成为一个实体也很重要,这会在下一个模型中被讨论。

      由于篇幅的原因,这里也不过多的写了,大家可以先下载DDD的精简版看看,需要的话,留下Email,我给出DDD的完整版!

      今天就到这里了,还是希望多多见谅,支持!谢谢啊!

      代码下载

  • 相关阅读:
    《Eric带您走近Office 2010》系列专题来啦!
    以人为本 体验至上(二)
    以人为本 体验至上(一)
    操作Static对象的多线程问题
    TroubleShoot:SilverLight中Wcf调用时出现Not Found等错误
    delegate与event的关系说明
    SharePoint对象模型性能考量
    SharePoint自带Silverlight WebPart中文输入问题处理
    关于Wcf事件多次执行的问题
    SharePoint中调试SilverLight程序
  • 原文地址:https://www.cnblogs.com/waw/p/2163182.html
Copyright © 2011-2022 走看看