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

  1. 架构设计

    eShopOnWeb中应用了DDD和整洁架构的部分思想,值得了解一下。

3.1 架构原则

关注点分离:简称SOP。在分层架构设计中,关注点分离是核心设计思想,每一层独自负责不同的职责。从架构上讲,可以通过将核心业务与基础设施和用户界面逻辑分离来实现。该原则旨在避免紧耦合,又可确保各个模块独立发展。

封装:封装的是什么?是对象的状态和行为。外部对象无需关注其内部的实现机制。

在类中,通过使用访问修饰符来限制外部的访问来实现封装。 如果外部想要操纵对象的状态,它应该通过定义良好的函数(或属性设置器)来实现,而不是直接访问对象的私有状态。

而不同模块之间通过公开定义良好的接口进行方法调用,来实现封装。以隔离内部的实现机制。通过封装来确保应用程序间不同部分之间的隔离,正确使用封装有助于在应用程序设计中实现松耦合和模块化。

依赖倒置:简称DIP。高层模块不应该依赖低层模块,均应该依赖与抽象;抽象不应该依赖于细节;细节应该依赖于抽象。DIP是构建松耦合应用的关键部分,从而确保应用程序模块化,更易于测试和维护。 通过遵循DIP,可以应用依赖注入。

显式依赖:方法和类应明确指定所需的协作对象(依赖)以确保正常运行。简单来说,对于类而言,提供明确的构造函数(即在构造函数参数中指定该类需要正常工作所需的依赖对象),以便调用者正确传参以正确实例化对象。

单一职责:简称SRP。SRP作为面向对象设计的原则之一,也适用于架构原则。其与SOP类似。它强调对象应该只有一个责任,他们只应该仅有一个改变的理由。换言之,对象应该改变的唯一情况是它的职责需要被更新。遵守该原则,可以编写松耦合和模块化的应用。因为大量的新的行为都应该创建新类去实现,而不是添加到已经存在的类中。添加新类永远比修改一个类安全,因为尚无代码依赖于新类。

在复杂的大型应用中,可以将SRP应用到分层应用的各个层。展现职责应保留在UI项目中,而数据访问职责应保留在基础设施项目中, 业务逻辑应该保留在应用程序核心项目中。如此,即易于测试又可以独立于其他职责持续演化。

该原则的更高级应用,就是微服务了。每个微服务负责独立的职责。

摒弃重复:当出现重复时,应该实施重构。避免当功能改进时,需要同时修改多个部分。

透明持久化:要求可以轻松切换持久化技术,而实现持久化无感知(透明持久化)。

限界上下文:该概念是DDD战略设计的一部分,通过限界上下文来划分领域,作为领域的显式边界,为领域提供上下文语境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。

3.2. 传统分层架构和整洁架构

传统的分层架构是大家所熟知的三层架构。

传统三层架构

三层架构示例

这样的架构的缺点是:

依赖关系由上至下,不易解耦

不易测试,需要测试数据库

那如何解决三层架构的问题呢,借助【依赖倒置原则】。

DDD的分层架构思想和整洁架构中都是借助【依赖倒置原则】实现层与层之间强依赖关系的解耦。我们来看下整洁架构:

整洁架构——洋葱视图

从该洋葱视图中我们可以看到:

依赖关系由外而内。

处于核心的是实体和接口,不依赖任何其他项。其次是领域服务,仅依赖实体和接口,也相对独立。它们统称为应用程序内核。

应用程序内核之外是基础架构层和展现层,彼此也不一定依赖。

由于应用程序内核不依赖于基础设施层,所以可以很容易编写单元测试。

单元测试位置

由于UI层也不直接依赖于基础设施层,所以我们可以轻松置换基础设施层的实现(比如使用内存数据库),以进行集成测试。

集成测试位置

整洁架构——水平视图

下面我们就来看看eShopOnWeb是如何应用整洁架构的。

  1. 项目结构

    首先我们看下模板架构的项目结构。

    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. 应用层和展现层

如上面所阐述,在示例项目中应用层和展现层合二为一。应用层负责展现层与领域层之间的协调,协调业务对象来执行特定的应用程序。

  1. 面向切面编程(AOP)

    eShopOnWeb中也提到了AOP,介绍了在ASP.NET Core中如何应用过滤器来进行AOP,比如:身份验证、模型验证、输出缓存和错误处理等。

    通过过滤器和请求管道执行

执行顺序

  1. 简明DDD

    在eShopOnWeb中,也对DDD的概念,是否使用,何时使用,何时不用,都略有介绍。这里就摘录一二,当然也可以参考我之前的写的DDD理论学习系列。

结论

DDD首先是一个方法论,其注重于领域的合理建模,分为战略建模和战术建模。

如果你不知道你需要它,那么你可能不需要它。

如果你不知道到DDD用于解决什么问题,那么你可能没有遇到这些问题。

DDD倡导者也经常指出其仅适用于大型项目 (>6个月)。

相关概念

DDD是用来对真实世界系统或流程的建模。

使用DDD时,你需要和领域专家紧密合作,领域专家能够解释真实的系统该如何运行。在和领域专家的交流中确定通用语言,其主要用来描述系统中的一些概念。而之所以是通用,是因为不管是开发人员还是领域专家都应能够读懂。而通用语言描述的概念将构成面向对象设计的基础。其体现在代码中的理想状态是代码即设计。

战术

值对象:不可变。

实体:具有唯一标识符可变。

聚会根:在DDD中,用来表示整体与部分的关系,聚合是将相关联的领域对象进行显式分组,来表达整体的概念(也可以是单一的领域对象)。比如将表示订单与订单项的领域对象进行组合,来表达领域中订单这个整体概念。

仓储:一种持久化的模式,用于隔离具体持久化措施,实现透明持久化。

工厂:用于对象的创建。

服务:应用服务和领域服务。领域服务负责业务逻辑,应用服务用于表达业务用例和用户故事。

战略

限界上下文:来为领域提供上下文语境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。

上下文映射图:限界上下文之间的关联关系。

6. 应用测试

在eShopOnWeb中,还示例了三个测试项目,来指导我们合理的进行测试。

单元测试、集成测试和功能测试的区别

  1. 总结

    总体而言,示例项目简单容易理解,也主要是为了便于推广和演示。但里面涉及的知识点并没有想象的那么简单,从架构原则到设计和应用,每一个环节都包含不简单的知识体系。

所以等什么呢?结合示例项目和官方文档使用 ASP.NET Core 和 Azure 构建新式 Web 应用程序开始学习吧,相信你也会收获颇丰。

eShopOnWeb的更多相关文章

  1. eShopOnWeb 知多少

    1.引言 eShopOnWeb是基于ASP.NET Core构建,官方创建这样一个示例项目的目的,我想无非以下几点: 推广ASP.NET Core 指导利用ASP.NET Core如何进行架构设计 普 ...

  2. asp.net core系列 64 结合eShopOnWeb全面认识领域模型架构

    一.项目分析 在上篇中介绍了什么是"干净架构",DDD符合了这种干净架构的特点,重点描述了DDD架构遵循的依赖倒置原则,使软件达到了低藕合.eShopOnWeb项目是学习DDD领域 ...

  3. asp.net core系列 63 领域模型架构 eShopOnWeb项目分析 上

    一.概述 本篇继续探讨web应用架构,讲基于DDD风格下最初的领域模型架构,不同于DDD风格下CQRS架构,二者架构主要区别是领域层的变化. 架构的演变是从领域模型到CQRS,  一开始DDD是用领域 ...

  4. DDD领域驱动设计理论篇 - 学习笔记

    一.Why DDD? 在加入X公司后,开始了ASP.NET Core+Docker+Linux的技术实践,也开始了微服务架构的实践.在微服务的学习中,有一本微软官方出品的<.NET微服务:容器化 ...

  5. ABP框架入门踩坑-添加实体

    添加实体 ABP踩坑记录-目录 这里我以问答模块为例,记录一下我在创建实体类过程中碰到的一些坑. 审计属性 具体什么是审计属性我这里就不再介绍了,大家可以参考官方文档. 这里我是通过继承定义好的基类来 ...

  6. 一系列令人敬畏的.NET核心库,工具,框架和软件

    内容 一般 框架,库和工具 API 应用框架 应用模板 身份验证和授权 Blockchain 博特 构建自动化 捆绑和缩小 高速缓存 CMS 代码分析和指标 压缩 编译器,管道工和语言 加密 数据库 ...

  7. .NET Conf 2019日程(北京时间)

    一年一度的 .NET Conf马上就要开始了,我将日程简易的翻译了一下,并且时间全部转换为北京时间,以方便国内.NETer. 日程 第1天 (北京时间9月24日) .NET Conf 2019 基调 ...

  8. Unable to connect to web server 'IIS Express'(无法连接到Web服务器“IIS Express”)的解决方式-Jexus Manager

    在运行微软示例工程eShopOnWeb时候, 在经过一段时间再运行启动报Error "Unable to connect to web server 'IIS Express'"  ...

  9. 使用事件和 CQRS 重写 CRUD 系统

    使用事件和 CQRS 重写 CRUD 系统 https://msdn.microsoft.com/zh-cn/magazine/mt790196.aspx https://github.com/mem ...

随机推荐

  1. 流量分析系统--zookeeper集群部署

    安装zookeeper mkdir apps tar -zxvf zookeeper-3.4.5.tar.gz -C apps [root@mini1 zookeeper-3.4.5]# rm -rf ...

  2. UI组件之UIImage

    UIImageView:图像视图,用于在应用程序中显示图片 UIImage:是将图片文件转换为程序中的图片对象 UIImageView是UIImage的载体 方法一:用此方法创建图片对象,会将图片ca ...

  3. Understanding When to use RabbitMQ or Apache Kafka

    https://content.pivotal.io/rabbitmq/understanding-when-to-use-rabbitmq-or-apache-kafka How do humans ...

  4. 移植opencv2.4.9到android过程记录

    http://blog.csdn.net/brightming/article/details/50606463 在移植到arm开发板的时候已经说过,OpenCV已经为各平台准备了一套cmake交叉编 ...

  5. linux音频 DAPM之二:audio paths与dapm kcontrol

    转:https://blog.csdn.net/wh_19910525/article/details/12749293 在用alsa_amixer controls时,除了我们之前提到的snd_so ...

  6. Linux的压缩命令(tar,gzip,zip)

    打包和压缩.打包是指将一大堆文件或目录变成一个总的文件:压缩则是将一个大的文件通过一些压缩算法变成一个小文件. 这源于Linux中很多压缩程序只能针对一个文件进行压缩,这样当你想要压缩一大堆文件时,你 ...

  7. php数组函数-array_flip()

    array_flip()函数返回一个反转后的数组,如果同一个值出现多次,则最 后一个键名作为它的值,所有其他的键名将丢失. 如果原数组中的值得数据类型不是字符串或整数,函数将报错. array_fli ...

  8. React Native的生命周期解析

    在React Native中使用组件来封装界面模块时,整个界面就是一个大的组件,开发过程就是不断优化和拆分界面组件.构造整个组件树的过程. 上张图涵盖了一个组件从创建.运行到销毁的整个过程.大家可以看 ...

  9. 高通平台Bootloader启动流程【转】

    本文转载自:http://blog.csdn.net/fang_first/article/details/49615631 ====================基本知识============= ...

  10. Kubernetes pod网络解析

    在Kubernetes中,会为每一个pod分配一个IP地址,pod内的所有容器都共享这个pod的network namespace,彼此之间使用localhost通信. 那么pod内所有容器间的网络是 ...