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

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

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

  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的完整版!

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

  代码下载

目录
相关文章
|
13天前
|
人工智能 开发框架 量子技术
【专栏】.NET 技术:驱动创新的力量
【4月更文挑战第29天】.NET技术,作为微软的开发框架,以其跨平台、开源和语言多样性驱动软件创新。它在云计算、AI/ML、混合现实等领域发挥关键作用,通过Azure、ML.NET等工具促进新兴技术发展。未来,.NET将涉足量子计算、微服务和无服务器计算,持续拓宽软件开发边界,成为创新的重要推动力。掌握.NET技术,对于开发者而言,意味着握有开启创新的钥匙。
|
13天前
|
开发框架 .NET C#
【专栏】理解.NET 技术,提升开发水平
【4月更文挑战第29天】本文介绍了.NET技术的核心概念和应用,包括其跨平台能力、性能优化、现代编程语言支持及Web开发等特性。文章强调了深入学习.NET技术、关注社区动态、实践经验及学习现代编程理念对提升开发水平的重要性。通过这些,开发者能更好地利用.NET构建高效、可维护的多平台应用。
|
13天前
|
机器学习/深度学习 vr&ar 开发者
【专栏】.NET 技术:引领开发新方向
【4月更文挑战第29天】本文探讨了.NET技术如何引领软件开发新方向,主要体现在三方面:1) 作为跨平台开发的先锋,.NET Core支持多操作系统和移动设备,借助.NET MAUI创建统一UI,适应物联网需求;2) 提升性能和开发者生产力,采用先进技术和优化策略,同时更新C#语言特性,提高代码效率和可维护性;3) 支持现代化应用架构,包括微服务、容器化,集成Kubernetes和ASP.NET Core,保障安全性。此外,.NET还不断探索AI、ML和AR/VR技术,为软件开发带来更多创新可能。
|
13天前
|
开发框架 Cloud Native 开发者
【专栏】剖析.NET 技术的核心竞争力
【4月更文挑战第29天】本文探讨了.NET框架在软件开发中的核心竞争力:1) .NET Core实现跨平台与云原生技术的融合,支持多操作系统和容器化;2) 提升性能和开发者生产力,采用JIT、AOT优化,提供C#新特性和Roslyn编译器平台;3) 支持现代化应用架构,包括微服务和容器化,内置安全机制;4) 丰富的生态系统和社区支持,拥有庞大的开发者社区和微软的持续投入。这些优势使.NET在竞争激烈的市场中保持领先地位。
|
13天前
|
开发框架 .NET 开发者
【专栏】领略.NET 技术的创新力量
【4月更文挑战第29天】.NET技术自ASP.NET起历经创新,现以.NET Core为核心,展现跨平台能力,提升性能与生产力,支持现代化应用架构。.NET Core使开发者能用同一代码库在不同操作系统上构建应用,扩展至移动和物联网领域。性能提升,C#新特性简化编程,Roslyn编译器优化代码。拥抱微服务、容器化,内置安全机制,支持OAuth等标准。未来.NET 6将引入更快性能、Hot Reload等功能,预示着.NET将持续引领软件开发潮流,为开发者创造更多机会。
|
4月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
47 0
|
18天前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
22 0
|
2月前
|
开发框架 前端开发 .NET
进入ASP .net mvc的世界
进入ASP .net mvc的世界
32 0
|
2月前
|
开发框架 前端开发 .NET
C# .NET面试系列六:ASP.NET MVC
<h2>ASP.NET MVC #### 1. MVC 中的 TempData\ViewBag\ViewData 区别? 在ASP.NET MVC中,TempData、ViewBag 和 ViewData 都是用于在控制器和视图之间传递数据的机制,但它们有一些区别。 <b>TempData:</b> 1、生命周期 ```c# TempData 的生命周期是短暂的,数据只在当前请求和下一次请求之间有效。一旦数据被读取,它就会被标记为已读,下一次请求时就会被清除。 ``` 2、用途 ```c# 主要用于在两个动作之间传递数据,例如在一个动作中设置 TempData,然后在重定向到另
113 5
|
9月前
|
存储 开发框架 前端开发
[回馈]ASP.NET Core MVC开发实战之商城系统(五)
经过一段时间的准备,新的一期【ASP.NET Core MVC开发实战之商城系统】已经开始,在之前的文章中,讲解了商城系统的整体功能设计,页面布局设计,环境搭建,系统配置,及首页【商品类型,banner条,友情链接,降价促销,新品爆款】,商品列表页面,商品详情等功能的开发,今天继续讲解购物车功能开发,仅供学习分享使用,如有不足之处,还请指正。
124 0