4

使用 FastEndpoints 来垂直切割Web API的控制器方法

 4 months ago
source link: https://www.cnblogs.com/wuhuacong/p/17903114.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在我们开发项目的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的相关引用加入项目中,如下所示。

8867-20231215122541932-958393883.png

 然后在项目中的启动类代码中,我们添加相关的代码使用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接口。

8867-20231215125128553-447595395.png

对于以前的控制器接口来说,一般可能一个控制(如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统一封装后返回的结果对象。

8867-20220707103738771-1192268724.png

如果需要了解我的《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 配置信息

8867-20231215130657485-2140276440.png

确保打开的时候就启动Swagger页面即可。

8867-20231215130758046-1943806407.png

启动Swagger页面,我们来看看具体的效果,可以看到有两个Test的接口,如下所示。

8867-20231215131133959-617438510.png

我们来调试Swagger,并测试下结果返回。

8867-20231215131344364-1043655524.png

测试返回的结果如下所示,由于采用了统一返回结果的处理,这里返回的TestResponse的对象序列化信息,放在了result的里面了,如下所示。

8867-20231215131449763-2085962134.png

而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接口展示界面效果。

8867-20231215131854717-2050091155.png

正常执行返回结果如下所示。

8867-20231215132321049-2044204311.png

 如果处理过程中有异常,由于我们采用了统一返回结果处理,因此异常信息也需要统一在对象里面,返回结果如下所示。

8867-20231215132003050-570661113.png

 以上就是简单类型的一些处理例子,结合了统一返回结果的处理,我们可以很好的定义一个通用的结果返回。

3、对我们SqlSugar框架常规CRUD等基类接口进行垂直切割的处理

上面我们为了更好理解FastEndpoints的碎片化接口的处理,我们做了两个简单的方法来测试。

下面我们通过对我们《SqlSugar开发框架》中的基类接口进行功能上的拆分,并结合实际业务的需要接口,进行扩展的处理,从而也实现了常规CRUD的操作接口,并实现特殊业务类的API接口处理。

关于Web API的常规接口处理 ,我们为了简化代码,往往抽象一些常规的CRUD方法在控制器基类中,这样可以极大的减少了继承子类的接口代码,通过继承基类,子类自动具备了CRUD的处理接口,只需要根据业务的需要,增加一些特殊的业务接口即可。

以前的处理方法,我们是根据项目的需要,我们定义了一些控制器的基类,用于实现不同的功能,如下界面所示。

8867-20220514125921134-1233794491.png

其中ControllerBase是.net core Web API中的标准控制器基类,我们由此派生一个LoginController用于登录授权,而BaseApiController则处理常规接口用户身份信息,而BusinessController则是对标准的增删改查等基础接口进行的封装,我们实际开发的时候,只需要开发编写类似CustomerController基类即可。

而现在采用FastEndpoints ,需要垂直切割整个控制器,这种架构称为 “垂直切片架构", 系统的每个组件都是单独的一块,彼此并不影响,就像微服务那样。

8867-20231215153659749-1240401604.png

我们需要把基类的接口实现放到具体的业务API类里面,为了方便,可以给它们不同的名称一个接口,或者组合在一个文件里面,如下所示。

8867-20231215133656663-1369350699.png

我们来看看其中给一个简单的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界面类似的效果。

8867-20231215134248685-593482470.png

 这样,我们可以在保持接口一致性的情况下,无缝的对接新的Web API接口后端了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK