使用 FastEndpoints 來垂直切換Web API的控制器方法

2023-12-15 15:01:17

在我們開發專案的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介面後端了。