eShopOnWeb
eShopOnWeb
https://www.cnblogs.com/sheng-jie/p/9616675.html
构建现代Web应用
1.引言
eShopOnWeb是基于ASP.NET Core构建,官方创建这样一个示例项目的目的,我想无非以下几点:
推广ASP.NET Core
指导利用ASP.NET Core如何进行架构设计
普及架构设计思想
eShopOnWeb 与另外一个eShopOnContainers互相补充。eShopOnContainers是基于微服务和容器技术的应用程序架构,支持多重部署。而eShopOnWeb相较于它就简单的多,其是基于传统Web应用开发,仅支持单一部署。
本文就简单梳理下自己的所学所得。
2.MPA Or SPA
eShopOnWeb的示例项目中包含两个Web项目,一个是基于MVC创建的MPA多页面应用,一个是基于Razor创建的SPA单页面应用。在此之间我该如何选择呢?
是否需要丰富的交互行为?
是否足够的前端技术积累?
是否主要通过API进行交互?
决策表:Mpa or Spa
- 架构设计
eShopOnWeb中应用了DDD和整洁架构的部分思想,值得了解一下。
3.1 架构原则
关注点分离:简称SOP。在分层架构设计中,关注点分离是核心设计思想,每一层独自负责不同的职责。从架构上讲,可以通过将核心业务与基础设施和用户界面逻辑分离来实现。该原则旨在避免紧耦合,又可确保各个模块独立发展。
封装:封装的是什么?是对象的状态和行为。外部对象无需关注其内部的实现机制。
在类中,通过使用访问修饰符来限制外部的访问来实现封装。 如果外部想要操纵对象的状态,它应该通过定义良好的函数(或属性设置器)来实现,而不是直接访问对象的私有状态。
而不同模块之间通过公开定义良好的接口进行方法调用,来实现封装。以隔离内部的实现机制。通过封装来确保应用程序间不同部分之间的隔离,正确使用封装有助于在应用程序设计中实现松耦合和模块化。
依赖倒置:简称DIP。高层模块不应该依赖低层模块,均应该依赖与抽象;抽象不应该依赖于细节;细节应该依赖于抽象。DIP是构建松耦合应用的关键部分,从而确保应用程序模块化,更易于测试和维护。 通过遵循DIP,可以应用依赖注入。
显式依赖:方法和类应明确指定所需的协作对象(依赖)以确保正常运行。简单来说,对于类而言,提供明确的构造函数(即在构造函数参数中指定该类需要正常工作所需的依赖对象),以便调用者正确传参以正确实例化对象。
单一职责:简称SRP。SRP作为面向对象设计的原则之一,也适用于架构原则。其与SOP类似。它强调对象应该只有一个责任,他们只应该仅有一个改变的理由。换言之,对象应该改变的唯一情况是它的职责需要被更新。遵守该原则,可以编写松耦合和模块化的应用。因为大量的新的行为都应该创建新类去实现,而不是添加到已经存在的类中。添加新类永远比修改一个类安全,因为尚无代码依赖于新类。
在复杂的大型应用中,可以将SRP应用到分层应用的各个层。展现职责应保留在UI项目中,而数据访问职责应保留在基础设施项目中, 业务逻辑应该保留在应用程序核心项目中。如此,即易于测试又可以独立于其他职责持续演化。
该原则的更高级应用,就是微服务了。每个微服务负责独立的职责。
摒弃重复:当出现重复时,应该实施重构。避免当功能改进时,需要同时修改多个部分。
透明持久化:要求可以轻松切换持久化技术,而实现持久化无感知(透明持久化)。
限界上下文:该概念是DDD战略设计的一部分,通过限界上下文来划分领域,作为领域的显式边界,为领域提供上下文语境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。
3.2. 传统分层架构和整洁架构
传统的分层架构是大家所熟知的三层架构。
传统三层架构
三层架构示例
这样的架构的缺点是:
依赖关系由上至下,不易解耦
不易测试,需要测试数据库
那如何解决三层架构的问题呢,借助【依赖倒置原则】。
DDD的分层架构思想和整洁架构中都是借助【依赖倒置原则】实现层与层之间强依赖关系的解耦。我们来看下整洁架构:
整洁架构——洋葱视图
从该洋葱视图中我们可以看到:
依赖关系由外而内。
处于核心的是实体和接口,不依赖任何其他项。其次是领域服务,仅依赖实体和接口,也相对独立。它们统称为应用程序内核。
应用程序内核之外是基础架构层和展现层,彼此也不一定依赖。
由于应用程序内核不依赖于基础设施层,所以可以很容易编写单元测试。
单元测试位置
由于UI层也不直接依赖于基础设施层,所以我们可以轻松置换基础设施层的实现(比如使用内存数据库),以进行集成测试。
集成测试位置
整洁架构——水平视图
下面我们就来看看eShopOnWeb是如何应用整洁架构的。
- 项目结构
首先我们看下模板架构的项目结构。
eShopOnWeb Solution
从上图来看其项目结构十分简单,简单的三层,加上三个测试项目。
三层对应:
ApplicationCore:领域层
Infrastructure:基础设施层
Web/WebRazorPages:展现层
DDD使用的传统分层架构
其实该项目架构是DDD经典四层架构,只不过其将应用层集成到展现层中去了。
Web应用服务
4.1 基础设施层
主要提供通用的基础服务和持久化。
Infrastructure
从上图的代码结构我们可以看出:
在Data文件夹下定义了用于持久化的商品目录数据库上下文CatalogContext和泛型仓储EfRepository。
Identity文件夹下定义了身份数据库上下文的。
Logging文件夹定义了一个日志适配器。
Services定义了一个通用的邮件发送基础服务。
4.2. 领域层
领域层是一个项目的核心,用来定义业务规则并实现。其主要用来实体、值对象、聚合、仓储、领域服务和领域事件等。
ApplicationCore
从上图来看:
Entities文件夹下定义了三个聚合根和相关的实体及值对象。
Exceptions文件夹定义了公共的异常。
Interfaces文件夹定义了系列接口。
Services文件夹定义了两个领域服务。
Specifications文件夹下是实现的规约模式。
4.2.1. 聚合根的相关实现
这里我们来看下聚合根的相关定义和实现。
///抽象的聚合根空接口
public interface IAggregateRoot
{ }
//所有的实体基类
public class BaseEntity
{
public int Id { get; set; }
}
//购物车聚会根
public class Basket : BaseEntity, IAggregateRoot
{
public string BuyerId { get; set; }
private readonly List _items = new List();
public IReadOnlyCollection Items => _items.AsReadOnly();
public void AddItem(int catalogItemId, decimal unitPrice, int quantity = 1)
{
if (!Items.Any(i => i.CatalogItemId == catalogItemId))
{
_items.Add(new BasketItem()
{
CatalogItemId = catalogItemId,
Quantity = quantity,
UnitPrice = unitPrice
});
return;
}
var existingItem = Items.FirstOrDefault(i => i.CatalogItemId == catalogItemId);
existingItem.Quantity += quantity;
}
}
从这个实现中我们可以学习到:
通过定义一个空的接口IAggregateRoot,要求所有的聚会根来实现它。
这样做的体现了什么思想:
面向接口编程
约定大于配置
依赖注入
通过定义一个BaseEntity,要求所有的实体继承它。
为什么这样做?
因为实体的特征是具有唯一的身份标识,所以通过在父类来定义Id属性来实现。这也就是层超类型的实现方式。
这样做有什么缺点?
因为所有实体的主键类型不一定都是int类型,所以这个基类型最好改成泛型。
Basket聚合根中将Items定位为Readonly,是为了封装集合,避免子项被其他地方更改。
4.2.2. 仓储的相关实现
仓储是用来透明持久化领域对象的。
public interface IRepository where T : BaseEntity
{
T GetById(int id);
T GetSingleBySpec(ISpecification spec);
IEnumerable ListAll();
IEnumerable List(ISpecification spec);
T Add(T entity);
void Update(T entity);
void Delete(T entity);
}
public interface IAsyncRepository where T : BaseEntity
{
Task GetByIdAsync(int id);
Task<List> ListAllAsync();
Task<List> ListAsync(ISpecification spec);
Task AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
}
从以上代码我们可以学到两点:
面向接口编程
职责分离,同步异步接口分离。
4.2.3. 领域服务相关实现
领域服务用来实现业务逻辑的。
public interface IOrderService
{
Task CreateOrderAsync(int basketId, Address shippingAddress);
}
public class OrderService : IOrderService
{
private readonly IAsyncRepository _orderRepository;
private readonly IAsyncRepository _basketRepository;
private readonly IAsyncRepository _itemRepository;
public OrderService(IAsyncRepository basketRepository,
IAsyncRepository itemRepository,
IAsyncRepository orderRepository)
{
_orderRepository = orderRepository;
_basketRepository = basketRepository;
_itemRepository = itemRepository;
}
public async Task CreateOrderAsync(int basketId, Address shippingAddress)
{
var basket = await _basketRepository.GetByIdAsync(basketId);
Guard.Against.NullBasket(basketId, basket);
var items = new List();
foreach (var item in basket.Items)
{
var catalogItem = await _itemRepository.GetByIdAsync(item.CatalogItemId);
var itemOrdered = new CatalogItemOrdered(catalogItem.Id, catalogItem.Name, catalogItem.PictureUri);
var orderItem = new OrderItem(itemOrdered, item.UnitPrice, item.Quantity);
items.Add(orderItem);
}
var order = new Order(basket.BuyerId, shippingAddress, items);
await _orderRepository.AddAsync(order);
}
从以上代码我们可以学习到:
依赖注入
领域服务负责实现真正的业务逻辑
4.3. 应用层和展现层
如上面所阐述,在示例项目中应用层和展现层合二为一。应用层负责展现层与领域层之间的协调,协调业务对象来执行特定的应用程序。
- 面向切面编程(AOP)
eShopOnWeb中也提到了AOP,介绍了在ASP.NET Core中如何应用过滤器来进行AOP,比如:身份验证、模型验证、输出缓存和错误处理等。
通过过滤器和请求管道执行
执行顺序
- 简明DDD
在eShopOnWeb中,也对DDD的概念,是否使用,何时使用,何时不用,都略有介绍。这里就摘录一二,当然也可以参考我之前的写的DDD理论学习系列。
结论
DDD首先是一个方法论,其注重于领域的合理建模,分为战略建模和战术建模。
如果你不知道你需要它,那么你可能不需要它。
如果你不知道到DDD用于解决什么问题,那么你可能没有遇到这些问题。
DDD倡导者也经常指出其仅适用于大型项目 (>6个月)。
相关概念
DDD是用来对真实世界系统或流程的建模。
使用DDD时,你需要和领域专家紧密合作,领域专家能够解释真实的系统该如何运行。在和领域专家的交流中确定通用语言,其主要用来描述系统中的一些概念。而之所以是通用,是因为不管是开发人员还是领域专家都应能够读懂。而通用语言描述的概念将构成面向对象设计的基础。其体现在代码中的理想状态是代码即设计。
战术
值对象:不可变。
实体:具有唯一标识符可变。
聚会根:在DDD中,用来表示整体与部分的关系,聚合是将相关联的领域对象进行显式分组,来表达整体的概念(也可以是单一的领域对象)。比如将表示订单与订单项的领域对象进行组合,来表达领域中订单这个整体概念。
仓储:一种持久化的模式,用于隔离具体持久化措施,实现透明持久化。
工厂:用于对象的创建。
服务:应用服务和领域服务。领域服务负责业务逻辑,应用服务用于表达业务用例和用户故事。
战略
限界上下文:来为领域提供上下文语境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。
上下文映射图:限界上下文之间的关联关系。
6. 应用测试
在eShopOnWeb中,还示例了三个测试项目,来指导我们合理的进行测试。
单元测试、集成测试和功能测试的区别
- 总结
总体而言,示例项目简单容易理解,也主要是为了便于推广和演示。但里面涉及的知识点并没有想象的那么简单,从架构原则到设计和应用,每一个环节都包含不简单的知识体系。
所以等什么呢?结合示例项目和官方文档使用 ASP.NET Core 和 Azure 构建新式 Web 应用程序开始学习吧,相信你也会收获颇丰。
eShopOnWeb的更多相关文章
- eShopOnWeb 知多少
1.引言 eShopOnWeb是基于ASP.NET Core构建,官方创建这样一个示例项目的目的,我想无非以下几点: 推广ASP.NET Core 指导利用ASP.NET Core如何进行架构设计 普 ...
- asp.net core系列 64 结合eShopOnWeb全面认识领域模型架构
一.项目分析 在上篇中介绍了什么是"干净架构",DDD符合了这种干净架构的特点,重点描述了DDD架构遵循的依赖倒置原则,使软件达到了低藕合.eShopOnWeb项目是学习DDD领域 ...
- asp.net core系列 63 领域模型架构 eShopOnWeb项目分析 上
一.概述 本篇继续探讨web应用架构,讲基于DDD风格下最初的领域模型架构,不同于DDD风格下CQRS架构,二者架构主要区别是领域层的变化. 架构的演变是从领域模型到CQRS, 一开始DDD是用领域 ...
- DDD领域驱动设计理论篇 - 学习笔记
一.Why DDD? 在加入X公司后,开始了ASP.NET Core+Docker+Linux的技术实践,也开始了微服务架构的实践.在微服务的学习中,有一本微软官方出品的<.NET微服务:容器化 ...
- ABP框架入门踩坑-添加实体
添加实体 ABP踩坑记录-目录 这里我以问答模块为例,记录一下我在创建实体类过程中碰到的一些坑. 审计属性 具体什么是审计属性我这里就不再介绍了,大家可以参考官方文档. 这里我是通过继承定义好的基类来 ...
- 一系列令人敬畏的.NET核心库,工具,框架和软件
内容 一般 框架,库和工具 API 应用框架 应用模板 身份验证和授权 Blockchain 博特 构建自动化 捆绑和缩小 高速缓存 CMS 代码分析和指标 压缩 编译器,管道工和语言 加密 数据库 ...
- .NET Conf 2019日程(北京时间)
一年一度的 .NET Conf马上就要开始了,我将日程简易的翻译了一下,并且时间全部转换为北京时间,以方便国内.NETer. 日程 第1天 (北京时间9月24日) .NET Conf 2019 基调 ...
- Unable to connect to web server 'IIS Express'(无法连接到Web服务器“IIS Express”)的解决方式-Jexus Manager
在运行微软示例工程eShopOnWeb时候, 在经过一段时间再运行启动报Error "Unable to connect to web server 'IIS Express'" ...
- 使用事件和 CQRS 重写 CRUD 系统
使用事件和 CQRS 重写 CRUD 系统 https://msdn.microsoft.com/zh-cn/magazine/mt790196.aspx https://github.com/mem ...
随机推荐
- eclipse(1)----ubuntu下的安装与配置
eclipse的安装与配置 1.eclipse官网下载,最新版本eclipse-jee-oxygen-3-linux-gtk-x86_64.tar.gz 2.tar包存在~/Download/下,解压 ...
- Linux基本命令 网络命令
概述 网络和监控命令类似于这些: hostname, ping, ifconfig, iwconfig, netstat, nslookup, traceroute, finger, telnet, ...
- qt的udp的初步使用(转)
该程序实现的功能是:局域网内,每个用户登录到聊天软件,则软件界面的右端可以显示在线用户列表,分别显示的是用户名,主机名,ip地址.软件左边那大块是聊天内容显示界面,这里局域网相当于qq中的qq群,即群 ...
- DNS 安装配置
DNS 安装配置 实验环境 一台主机:Linux Centos 6.5 32位 安装包: DNS服务:bind.i686 DNS测试工具:bind-utils DNS 服务安装 1.yum安装DNS服 ...
- eval 加密 js,把js代码重新编续成新的代码,然后eval运行
eval( function(p, a, c, k, e, r) { e = function(c) { return c.toString(a) //35 }; if (!''.replace(/^ ...
- spark学习2(hive0.13安装)
第一步:hive安装 通过WinSCP将apache-hive-0.13.1-bin.tar.gz上传到/usr/hive/目录下 [root@spark1 hive]# chmod u+x apac ...
- <转载>Win x86-64 - Download & execute (Generator)
#Title: Obfuscated Shellcode Windows x86/x64 Download And Execute [Use PowerShell] - Generator #leng ...
- JavaScript -- 操作input CheckBox 全选框
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- Virtio SCSI设备介绍
Qemu的存储栈 在KVM虚拟化环境中,当客户机的内核存储系统像在物理机上一样通过页缓存.文件系统.通用块设备层运行到实际设备驱动时,这时驱动对设备寄存器的访问会触发CPU从客户机代码切换到物理机内的 ...
- keystone cache
http://docs.openstack.org/juno/config-reference/content/section_keystone.conf.html http://docs.opens ...