项目模板说明

Ater.Web.template 是基于ASP.NET CoreEntityFramework Core技术栈的项目模板,提供了开箱即用的项目结构和基础功能,帮助开发者快速搭建一个现代化的Web应用程序。

本着以结果为导向,实用为目标,在架构设计上,我们主要追求通用、简洁和灵活三个原则,致力于为开发者提供高效且易用的开发体验,三个原则的具体含义:

  • 通用:指尽可能使用主流的,官方的,已被大量采用或认可的技术栈和实现方式,以避免不必要的学习成本。
  • 简捷:简单和快捷。指保持代码和结构的简单,在使用上快捷。尽可能降低心智负担,避免过度设计和不必要的抽象。专注于业务逻辑实现,减少重复性代码内容。
  • 灵活:我们在通用、简捷的基础上提供约定和最佳实践,这是非侵入式的,由于我们并不以某些设计模式或开发习惯为目标,开发者不需要受其限制。

Important

文档中最佳实践是一个技术表达用语,指经过实践之后推荐的措施,并不是说是最好的实践。每个人或团队都可以有自己的最佳实践。

为了达成这样的目标,我们将项目开发分成两个部分:框架层项目层

框架层(Framework)

在框架层指的是经过大量项目的经验积累,总结出来的最佳实践和约定。这些约定符合通用、简捷、灵活的原则,旨在帮助开发者快速上手和理解项目结构,并能够基于此,快速实现业务逻辑代码。

我们将通过提供以下程序集来实现框架层的功能:

  • Ater.Common:基础类库,包含一些常用的扩展方法和工具帮助类。
  • Ater.Web.Convention:Web开发相关的约定,通过接口定义。
  • Ater.Web.Extension:在Web开发中常用的三方类库集成,如发邮件,图形验证码,Excel导出等。
  • Ater.Web.Analyzer:源代码生成器和代码分析器相关功能。

由于框架层与项目和业务无关,它们可以被打包成类库,为此我们提供了打包后的Nuget包,包括:

  • Ater.Common
  • Ater.Web.Convention
  • Ater.Web.Extension
  • Ater.Web.Analyzer

实际使用中,你可以直接安装Ater.Web.Extension,它包含了Ater.CommonAter.Web.Convention

项目层

项目层其实是为了与框架层区分,指与本项目相关的层级结构,主要包括:

  • Definition,定义层,用来定义业务模型
  • Implement,实现层,用来实现业务逻辑,通过拆解成不同的Module来实现
  • Services,服务层,对外提供接口服务

这是一个通用的流程,非常容易理解,对于各个行业的流程,我们都可以抽象出这三个步骤:定义->实现->服务

  • 定义 是要搞清楚我们要做什么,需要什么,产出什么。
  • 实现 是指具体的实现过程,将我们的有的资源,转换成产出结果的过程。
  • 服务 就像一个用户代理,接收用户需求,根据需求将对应的产出结果交给用户。

定义层(Definition)

定义层,定义业务模型,决定了业务的数据模型和行为,是业务实现的基础和前提,通常包括:

  • Entity:实体定义,决定核心业务模型的存储结构。
  • EntityFramework:ORM映射定义,决定实体与数据库表的映射关系。
  • Share:一些可共享的模型内容,如DTO,Options等。
  • ServiceDefaults:通用服务注入的定义,如健康检查,重试机制,日志等。

实现层(Implement)

实现层,主要是实现业务逻辑,通过拆解成不同的Module来实现。

在实际需要中,业务需求常常涉及多个领域或多个业务模块,我们可以按照业务模块进行拆分,框架默认提供CommonMod共享模块,被其他模块引用。

典型结构如下:

  • CommonMod:共享模块,供各个业务模块使用。
  • CustomerModule:客户模块,包含客户相关的业务逻辑。
  • OrderModule:订单模块,包含订单相关的业务逻辑。

Manager

在实际开发中,主要通过Manager实现业务逻辑。框架提供了ManagerBase实现类,该类主要提供了默认的数据访问的能力。

EntityManager

最常见的用法是继承ManagerBase<TEntity>,其中TEntity对应一个业务实体模型,用来对该实体进行操作,称之为EntityManager

通常EntityManager之间不会直接通过依赖注入进行互相调用,避免产生循环依赖。

典型的代码如下:

public class UserManager(DataAccessContext<User> dataContext,ILogger<UserManager> logger) 
  : ManagerBase<User>(dataContext, logger)
{
}

请注意,这里注入了DataAccessContext<>,它是一个数据访问上下文,只是对CommandDbContextQueryDbContext的简单封装。

ManagerBase

默认的ManagerBase<TEntity>使用CommandDbContextQueryDbContext,分别用于数据的读和写操作。如果你需要指定数据库上下文,或者不需要数据访问的能力,可以继承使用不同参数的ManagerBase,以实现自己的逻辑。

可以在Definition/Share程序集中的Implement目录下查看ManagerBase.cs

ManagerBase提供了对数据访问的支持,如果你的业务逻辑不涉及到数据状态的变化,建议将其封装成一个Service,而不是一个Manager

Tip

关于使用ManagerBase提供数据访问能力的内容,详情请查看数据访问文档,包括如何使用工厂模式支持多租户的数据库上下文创建。

Modules

在实现层中,我们将业务逻辑拆分成多个模块,每个模块实现自己的Manager,模块的划分有助于将业务逻辑进行拆分和解耦,有助于多人的协作开发。

其中CommonMod是一个通用共享模块,其他各个模块都会引用它。

模块的命名约定以Mod结尾。

Module具有一定的独立性,无论是在业务上还是代码上,一个Module通常包括:

  • Models,DTO等模型类
  • Managers,业务实现类
  • ModuleExtensions.cs,用来添加该模块需要的服务。

你也可以在Module中实现Controller,以便可以在多个服务中复用,这也是一种实践。

服务层

服务层是面向实际调用者的,通常我们通过Restful APIGrpc来提供接口的调用。不同的服务通常使用不同的镜像去部署。

对于接口服务,我们需要关注的是:

  • 用户请求:请求的解析和验证
  • 业务逻辑:通过调用业务层实现业务逻辑,而不直接在服务层去实现。
  • 返回结果:响应的格式化和返回

在该层级,模板默认提供了以下服务

  • Http.API:使用Restful API提供接口服务。
  • IdentityServer:使用IdentityServer提供身份认证服务。
  • MirgrationService:使用EF Core提供数据库迁移服务。

接口服务

模板默认提供Http.API项目以实现Restful API接口服务,该程序集通常会依赖以下项目:

  • Ater.Web.Analyzers:提供源代码生成器,如自动生成注入ManagerModule的扩展方法。
  • XXXMod:即你需要使用的业务模块。
  • ServiceDefaults:即.NET Aspire提供的默认服务发现,遥测和健康检查等服务。

Program.cs中,你会看到AddManagersAddModules,这两个方法是用来添加业务模块的服务,它们由源代码生成器自动生成,无法手动修改,可以按F12查看生成的代码。

基础控制器类

框架提供了RestControllerBase基类,该类定义了路由,注入了语言本地化服务,以及实现了常见的返回方法。

此外,提供了ClientControllerBaseAdminControllerBase,分别用于客户端和管理端的控制器基类,它们拥有不同的路由、授权和OpenAPI的分组。通常我们自己编写的控制器要继承其中的一个。

Tip

可在Definition/Share程序集的Implement查看RestControllerBase.cs的实现。

.NET Aspire

在现代化应用开发中,越来越多的应用使用微服务方式来开发,然后使用容器来部署和运行,这是现代化开发中的主流方式。

微软提供.NET Aspire来解决微服务开发中的一些痛点。在V10版本,我们将默认使用.NET Aspire来作为本地开发的默认集成。

模板中关于Aspire的内容,一个是AspireHost项目,一个是Definition目录下的ServiceDefaults项目。