使用 FastEndpoints 来垂直切割Web API的控制器方法
在我们开发项目的Web API的时候,随着项目功能要求越来越多,可能我们会为控制器基类增加越来越多的基础功能,有些功能有一定的适应性,但可能在一般的子类中用不到,而随着对控制器控制要求越来越精细,那么需要为基类或者子类增加更多的控制功能,这样随着迭代的进行,有些控制器的功能会显得越来越笨重。这个时候,一种更加灵活、轻便的Web API处理方式,对每个控制器方法的垂直切割的API框架应运而生,本篇随笔介绍的FastEndpoints 就是其中这样的一款框架,本篇随笔介绍一些FastEndpoints的基础处理方法,并通过一些基础的案例,把我们《 SqlSugar 开发框架》的一些模块进行迁移性测试,对比相关后端Web API的处理,一起分享给大家。
1、FastEndpoints介绍
FastEndpoints 是Minimal API和MVC的开发人员友好替代品,它是基于REPR设计模式(请求-端点-响应),以便创建方便且可维护的端点,几乎没有样板文件。
FastEndpoints 的性能与Minimal API 相当,甚至它更快,使用更少的内存并且每秒请求数比基准测试中的MVC控制器更高。对于比如:中间件、认证、授权、日志,依赖注入这些常用功能都支持,甚至有些还进行了加强。
设计主要是分为两种模式
分层模式:mvc、mvp、mvvm等
垂直模式:REPR设计模式
REPR设计模式就是垂直模式,系统的每个组件都是单独的一块,彼此并不影响,就像微服务那样。
MVC - 模型-视图-控制器旨在与用户界面配合使用。显然,视图是一个 UI 组件。如果您正在构建 API,则没有视图,因此您充其量使用的是 MC 模式,或者您可以将其称为模型-操作-控制器并获取 MAC 模式。关键是,你已经没有将MVC用于你的API,所以考虑一个更合适的模式应该不是一个很大的问题。
API 端点是非常独立的,每个端点都可以使用三个组件来描述:
请求(Request):终结点所需的数据形状
终结点(Endpoint):终结点在给定请求时执行的逻辑
响应(Response):终结点返回给调用方的响应
结合这三个元素,你会得到请求-端点-响应或 REPR 模式。
并非所有终结点都需要其请求或响应的实际数据,在某些情况下,不接收任何输入或仅返回 HTTP 状态代码。但是,在此模式中,空请求或响应仍然是有效的请求或响应,就像某些 MVC 操作不需要模型一样。
使用 API 端点库时,您可以将请求、终端节点和响应类型分组在一起,这样就无需在某些“视图模型”或“dtos”文件夹中四处寻找合适的类型。它减少了摩擦,使使用单个端点变得更加容易。
FastEndPoint GitHub库:https://github.com/FastEndpoints/FastEndpoints
FastEndpoints 在线文档:https://fast-endpoints.com
2、简单例子入门
参考官方的文档介绍,我们可以很容易的创建出一个简单的类似Hello开篇的API应用。
我创建一个基于.net core的Web API项目,先把FastEndPoint的相关引用加入项目中,如下所示。
然后在项目中的启动类代码中,我们添加相关的代码使用FastEndpoints,如下所示。
using FastEndpoints; using FastEndpoints.Swagger; var bld = WebApplication.CreateBuilder(); bld.Services .AddFastEndpoints() .SwaggerDocument(); var app = bld.Build(); app.UseFastEndpoints() .UseSwaggerGen(); app.Run();
如果需要对Swagger进行一些定制修改,可以改动如下,这里先忽略。
.SwaggerDocument(o => { o.DocumentSettings = s => { s.Title = "SqlSugar框架接口API文档"; s.Version = "v1"; }; o.TagDescriptions = t => { t["Test"] = "测试接口"; t["User"] = "用户相关接口"; }; })
为了简便,我们以命名控件不同,以及目录,来区分不同的Web API分组,如下所示,我们创建一个基于Test的相关API接口。
对于以前的控制器接口来说,一般可能一个控制(如TestController)会包含多个方法,如上面的Create、List方法,这里使用的是FastEndpoints,它们是把一个大型的控制器切换为一个方法一个类来处理,碎片化意味着类的增加,不过我们不需要做太多的工作,可以通过它们的一些基类来简化这个过程。
我们把WebAPI中请求的Request和Response的对象,放在一个Model类文件里面,如下代码所示。
namespace FastWebApi.Controllers.Test { /// <summary> /// 测试请求信息 /// </summary> public class TestRequest { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } } /// <summary> /// 测试返回信息 /// </summary> public class TestResponse { public string FullName { get; set; } public bool IsOver18 { get; set; } } }
我们来看看基于FastEndPoints方式 生成一个Create的请求Web API方法,如下代码所示
namespace FastWebApi.Controllers.Test { //客户使用标识,不用覆盖 Configure 函数 //[HttpPost("/api/user/create")] //[AllowAnonymous] /// <summary> /// 创建记录 /// </summary> public class Create : Endpoint<TestRequest, AjaxResponse> { public override void Configure() { Post("/test/create"); AllowAnonymous(); } public override async Task HandleAsync(TestRequest req, CancellationToken ct) { var result = new TestResponse() { FullName = req.FirstName + " " + req.LastName, IsOver18 = req.Age > 18 }; await SendAsync(result.ToAjaxResponse()); } } }
我们配置Web API方法的路由,可以通过在Configure函数中指定: Post("/test/create")
也可以通过Attribute属性标识的方式,来声明,上面的注释代码所示。
[HttpPost("/api/user/create")]
这两者是等同的,任何一种方式都可以,默认的接口是需要授权才能访问的,如果我们标识了
[AllowAnonymous]
就可以匿名访问Web API 的方法了,Web API的方法处理逻辑,都是统一通过重写 HandleAsync 方法进行实现的,如上面代码所示。
其中AjaxResponse 是我定义的一个统一返回结果,这样我们的接口模型就一致了。
如下是Web API统一封装后返回的结果对象。
如果需要了解我的《SqlSugar开发框架》的统一结果返回处理,可以参考《基于SqlSugar的数据库访问处理的封装,在.net6框架的Web API上开发应用 》中的 【统一结果封装和异常处理】 部分内容即可。
如果不需要统一返回模型,则可以自定义为任何的返回类型,如下是官方的案例所示。
public class MyEndpoint : Endpoint<MyRequest, MyResponse> { public override void Configure() { Post("/api/user/create"); AllowAnonymous(); } public override async Task HandleAsync(MyRequest req, CancellationToken ct) { await SendAsync(new() { FullName = req.FirstName + " " + req.LastName, IsOver18 = req.Age > 18 }); } }
接下来,我们检查下.netcore项目的launchSettings.json 配置信息
确保打开的时候就启动Swagger页面即可。
启动Swagger页面,我们来看看具体的效果,可以看到有两个Test的接口,如下所示。
我们来调试Swagger,并测试下结果返回。
测试返回的结果如下所示,由于采用了统一返回结果的处理,这里返回的TestResponse的对象序列化信息,放在了result的里面了,如下所示。
而List的控制器方法,这里没有请求输入的对象信息,因此参数为空。具体的API方法定义如下所示。
namespace FastWebApi.Controllers.Test { /// <summary> /// 获取所有记录 /// </summary> [HttpGet("/test/list")] [AllowAnonymous] public class List : EndpointWithoutRequest<AjaxResponse> { /// <summary> /// 处理返回 /// </summary> public override async Task HandleAsync(CancellationToken ct) { var result = new List<TestResponse>() { new TestResponse { FullName= "test", IsOver18 = true, }, new TestResponse { FullName= "test 2", IsOver18 = false, } }; await SendAsync(result.ToAjaxResponse()); } } }
Swagger接口展示界面效果。
正常执行返回结果如下所示。
如果处理过程中有异常,由于我们采用了统一返回结果处理,因此异常信息也需要统一在对象里面,返回结果如下所示。
以上就是简单类型的一些处理例子,结合了统一返回结果的处理,我们可以很好的定义一个通用的结果返回。
3、对我们SqlSugar框架常规CRUD等基类接口进行垂直切割的处理
上面我们为了更好理解FastEndpoints的碎片化接口的处理,我们做了两个简单的方法来测试。
下面我们通过对我们《SqlSugar开发框架》中的基类接口进行功能上的拆分,并结合实际业务的需要接口,进行扩展的处理,从而也实现了常规CRUD的操作接口,并实现特殊业务类的API接口处理。
关于Web API的常规接口处理 ,我们为了简化代码,往往抽象一些常规的CRUD方法在控制器基类中,这样可以极大的减少了继承子类的接口代码,通过继承基类,子类自动具备了CRUD的处理接口,只需要根据业务的需要,增加一些特殊的业务接口即可。
以前的处理方法,我们是根据项目的需要,我们定义了一些控制器的基类,用于实现不同的功能,如下界面所示。
其中ControllerBase是.net core Web API中的标准控制器基类,我们由此派生一个LoginController用于登录授权,而BaseApiController则处理常规接口用户身份信息,而BusinessController则是对标准的增删改查等基础接口进行的封装,我们实际开发的时候,只需要开发编写类似CustomerController基类即可。
而现在采用FastEndpoints ,需要垂直切割整个控制器,这种架构称为 “垂直切片架构", 系统的每个组件都是单独的一块,彼此并不影响,就像微服务那样。
我们需要把基类的接口实现放到具体的业务API类里面,为了方便,可以给它们不同的名称一个接口,或者组合在一个文件里面,如下所示。
我们来看看其中给一个简单的Count方法接口实现。
namespace FastWebApi.Controllers.User { /// <summary> /// 根据条件计算记录数量 /// </summary> [HttpGet("/user/count")] public class Count : Endpoint<UserPagedDto, AjaxResponse> { /// <summary> /// 处理请求 /// </summary> public override async Task HandleAsync(UserPagedDto req, CancellationToken ct) { var result = await BLLFactory<IUserService>.Instance.CountAsync(req); await SendAsync(result.ToAjaxResponse()); } } }
这里可以采用接口注入的方式,也可以采用我们辅助类BLLFactory<IUserService>.Instance方法调用接口,一样的实现。
这样结合了业务的具体Service来处理,只需要简单的处理下即可,也算比较方便,由于这些基础的CRUD的方法,主要路由、分页对象,业务对象,主键类型的不同,这些可以通过我们的代码生成工具的处理快速生成即可,因此可以实现批量化的业务类的API接口方法生成。
至于具体的业务接口API,我们就需要手工处理了,如对于用户的登陆获取token的方法,我们这里需要模仿来生成一个EndPiont类,如下所示。
/// <summary> /// 根据用户名、密码验证用户身份有效性 /// </summary> [HttpPost("/user/verify-user")] [AllowAnonymous] public class VerifyUser : Endpoint<VerifyUserDto, AjaxResponse> { /// <summary> /// 处理请求 /// </summary> public override async Task HandleAsync(VerifyUserDto input, CancellationToken ct) { var result = await BLLFactory<IUserService>.Instance.VerifyUser(input.UserName, input.UserPassword, input.SystemType, input.IP, input.MacAddr); await SendAsync(result.ToAjaxResponse()); } }
其他业务方法也是类似的处理,这里的FastEndPoints的处理类,只是增加了一个简单的包装层就可以了,最后看看这些方法在SwaggerUI中的展示,和我们普通模式的Web API中的Swagger UI界面类似的效果。
这样,我们可以在保持接口一致性的情况下,无缝的对接新的Web API接口后端了。
转载请注明出处:撰写人:伍华聪 http://www.iqidi.com