学海无涯

导航

常用 Web 应用程序体系架构

参考:https://learn.microsoft.com/zh-cn/dotnet/architecture/modern-web-apps-azure/common-web-application-architectures

一体式应用程序

应用程序体系结构项目可能的最小数量是一。 在这种体系结构中,应用程序的完整逻辑包含在单一项目中,编译为单一程序集并作为单个单元进行部署。

它包含应用程序的所有行为,包括展现、业务和数据访问逻辑。

图 5-1 展示了单项目应用的文件结构。

 

 图  单项目 ASP.NET Core 应用

 

在单项目方案中,通过使用文件夹实现关注点分离。 默认模板包括单独的文件夹,对应于 MVC 模式中的模型、视图和控制器,以及其他数据和服务文件夹。 在这种结构安排中,应尽可能地将展现逻辑限制在“Views”文件夹,将数据访问逻辑限制在“Data”文件夹中保存的类。 业务逻辑应位于“Models”文件夹内的服务和类中。

尽管简单,但单项目整体解决方案也有一些缺点。 随着项目的大小和复杂性增加,文件和文件夹数量也会继续随之增加。 用户界面 (UI) 问题(模型、视图和控制器)驻留于多个文件夹中,这些文件夹未按字母顺序组合在一起。 将其他 UI 级别的构造(例如筛选器或 ModelBinder)添加到它们自己的文件夹时,问题只会变得更糟。 业务逻辑分散于“Models”和“Services”文件夹之间,没有明确地指示哪些文件中的哪些类应当依赖其他类。 这种项目级别缺少组织的情况通常会导致面条式代码

为解决这些问题,应用程序通常演变为多项目解决方案,其中将每个项目视为位于应用程序的特定层 。

传统“N 层”体系结构应用程序

图 5-2 展示了应用程序逻辑分层最常用的组织结构。

 

 

图 5-2. 典型的应用程序层次。

这些层经常简称为 UI、BLL(业务逻辑层)和 DAL(数据访问层)。 使用此体系结构,用户可通过 UI 层(仅与 BLL 交互)提出请求。 反过来,BLL 可为数据访问请求调用 DAL。 UI 层不应直接向 DAL 提出任何请求,也不得通过其他途径直接与持久性发生交互。 同样,BLL 应仅通过 DAL 与持久性发生交互。 通过这种方式,每层都有自己熟知的职责。

这种传统分层方法的缺点之一是编译时依赖关系由上而下运行。 即,UI 层依赖于 BLL,而 BLL 依赖于 DAL。 这意味着,通常保存应用程序中最重要的逻辑的 BLL层,必须依赖于数据访问的实现方式(且通常依赖于数据库的存在)。 在这样的体系结构中很难测试业务逻辑,需要一个测试数据库。 如下节中所述,依赖倒置原则可以用来解决此问题。

图 5-3 展示了一个示例解决方案,其中按职责(层次)将应用程序分解为三个项目。

 

 图 . 具有三个项目的简单整体式应用程序。

干净体系结构

遵循依赖倒置原则以及域驱动设计原则 (DDD) 的应用程序倾向于达到类似的体系结构。 多年来,这种体系结构有多种名称。 最初的名称之一是六边形体系结构,然后是端口 - 适配器。 最近,它被称为洋葱体系结构干净体系结构。 此电子书中将后一种名称“干净体系结构”用作此体系结构的名称。

eShopOnWeb 参考应用程序使用“干净体系结构”方法将其代码组织到项目中。 可以在 ardalis/cleanarchitecture GitHub 存储库中或通过从 NuGet 安装模板找到一个解决方案模板,用作自己的 ASP.NET Core 解决方案的起点。

干净体系结构将业务逻辑和应用程序模型置于应用程序的中心。 而不是让业务逻辑依赖于数据访问或其他基础设施,此依赖关系被倒置:基础结构和实现细节依赖于应用程序内核。 此功能是通过在应用程序核心中定义抽象或接口来实现的,然后通过基础设施层中定义的类型实现。 将此体系结构可视化的常用方法是使用一系列同心圆,类似于洋葱。 图 5-7 展示这种样式的体系结构表示形式的示例。

 

 图 . 干净体系结构,洋葱视图

图 5-7. 干净体系结构,洋葱视图

在此关系图中,依赖关系流向最里面的圆。 “应用程序内核”因其位于此关系图的核心位置而得名。 从关系图上可见,该应用程序内核在其他应用程序层上没有任何依赖项。 应用程序的实体和接口位于正中心。 在外圈但仍在应用程序核心中的是域服务,它通常实现内圈中定义的接口。 在应用程序内核外面,UI 和基础结构层均依赖于应用程序内核,但不一定彼此依赖。

 

 图 . 干净体系结构,水平层次视图

注意,实线箭头表示编译时依赖关系,而虚线箭头表示仅运行时依赖关系。 使用干净体系结构,UI 层可使用编译时应用程序内核中定义的接口,理想情况下不应知道体系结构层中定义的实现类型。 但是在运行时,这些实现类型是应用执行所必需的,因此它们需要存在并通过依赖关系注入接通应用程序内核接口。

图 5-9 展示了遵循这些建议生成 ASP.NET Core 应用程序体系结构时的更详细的视图。

 

 图 5-9. 遵循干净体系结构的 ASP.NET Core 体系结构关系图。

由于应用程序内核不依赖于基础结构,可轻松为此层次编写自动化单元测试。 图 5-10 和 5-11 展示了测试如何适应此体系结构。

 

 

 图 5-10. 隔离状态下的单元测试应用程序内核。

 

 图 5-11. 使用外部依赖关系的集成测试基础结构实现。

由于 UI 层对基础结构项目中定义的类型没有任何直接依赖关系,同样,可轻松交换实现,无论是为便于测试还是为应对不断变化的应用程序要求。 ASP.NET Core 对内置依赖关系注入的使用及相关支持使此体系结构最适合用于构造重要的整体式应用程序。

对于单片式应用程序,应用程序内核、基础结构和 UI 项目均作为单一应用程序运行。 运行时应用程序体系结构可能类似于图 5-12。

 

 

 图 5-12. 示例 ASP.NET Core 应用的运行时体系结构。

采用干净体系结构排列代码

在干净体系结构解决方案中,每个项目都有明确的职责。 在这种情况下,某些类型将属于每个项目,你会经常在相应的项目中找到与这些类型相应的文件夹。

应用程序核心

应用程序内核包含业务模型,后者包括实体、服务和接口。 这些接口包括使用基础结构执行的操作(如数据访问、文件系统访问和网络调用等)的抽象。有时,在此层定义的服务或接口需要使用与 UI 或基础结构没有任何依赖关系的非实体类型。 这些类型可定义为简单的数据传输对象 (DTO)。

应用程序内核类型
  • 实体(保存的业务模型类)
  • 聚合(实体组)
  • 接口
  • 域服务
  • 规范
  • 自定义异常和临界子句
  • 域事件和处理程序

基础结构

基础结构项目通常包括数据访问实现。 在典型的 ASP.NET Core Web 应用程序中,这些实现包括 Entity Framework (EF) DbContext、任何已定义的 EF Core Migration 对象以及数据访问实现类。 提取数据访问实现代码最常用的方式是通过使用存储库设计模式

除数据访问实现外,基础结构项目还应包含必须与基础结构问题交互的服务的实现。 这些服务应实现应用程序内核中定义的接口,因此基础结构应包含对应用程序内核项目的引用。

基础结构类型
  • EF Core 类型(DbContextMigration
  • 数据访问实现类型(存储库)
  • 特定于基础结构的服务(如 FileLogger 或 SmtpNotifier

UI 层

ASP.NET Core MVC 应用程序中的用户界面层是应用程序的入口点。 此项目应引用应用程序内核项目,且其类型应严格通过应用程序内核中定义的接口与基础结构进行交互。 UI 层中不允许基础结构层类型的直接实例化(或静态调用)。

UI 层类型
  • Controllers
  • 自定义筛选器
  • 自定义中间件
  • 视图
  • ViewModels
  • 启动

Startup 类或 Program.cs 文件负责配置应用程序,并将实现类型与接口接通。 执行此逻辑的位置称为应用程序的组合根,它允许依赖项注入在运行时正常工作。

 备注

为了在应用程序启动过程中接通依赖关系注入,UI 层项目可能需要引用 Infrastructure 项目。 此依赖项可以通过使用自定义 DI 容器来轻而易举地消除,此容器内置了对从程序集中加载类型的支持。 就本示例而言,最简单的方法是允许 UI 项目引用 Infrastructure 项目(但开发人员应将对 Infrastructure 项目中类型的实际引用限制为应用程序的组合根)。

 

posted on 2022-10-17 15:37  宁静致远.  阅读(538)  评论(0编辑  收藏  举报