練習題 - 設計模式 - 使用 PipelineNet 實作責任鍊模式 (職責鍊模式) Chain of Responsibility Pattern

前一篇文章「練習題 - 設計模式 - 責任鍊模式 (職責鍊模式) Chain of Responsibility Pattern」是不藉助任何套件的方式下完成實作,但我總會想應該是有人會做個 NuGet 套件來讓開發者可以直接套用就能夠完成責任鍊模式的實作,找了一輪後發現到還蠻少的,雖然少但也還是有一些,不過有的是已經很久沒有更新或是用的人不多,而有些是不適合真的拿來使用,有這種情況大概是責任鍊模式的實作其實並不複雜。

不過還是有找到一個還可以的套件,所以就拿來用在之前的練習題裡,看看是不是適合。

PipelineNet

PipelineNet 是一個微型框架,幫助你實現管道(Pipeline)和責任鍊(Chain of Responsibility)模式。使用 PipelineNet,你可以輕鬆地將業務邏輯分離,並擴展你的應用程式。管道可以用來依序執行一系列的中介軟體(middleware),而不期望有回傳值;而責任鍊則做相同的事,但期望有回傳值。此外,這些操作也都可以以非同步方式進行。

這邊先不說明 套件的 Pipeline 功能,直接看責任鍊(Chains of Responsibility)模式的功能支援。

責任鍊模式有兩種實作方式:ResponsibilityChain<TParameter, TReturn>AsyncResponsibilityChain<TParameter, TReturn>。這兩者具有相同的功能,都能聚合並執行一系列的 middleware,並回傳一個結果類型。

責任鍊與管道(pipeline)的差別是可以使用 Finally 方法來定義 fallback function。可以為責任鍊設置一個 fallback function,如果多次調用該方法,則會覆蓋之前定義的 function。

 

使用 PipelineNet

建立各個 Handler  類別並繼承實作 PipeneNet 的 IAsyncMiddleware<TParameter,TReturn> 介面,而這邊方法的回傳型別還是一樣使用 HandlerResult

/// <summary>
/// class TokenExistHandler
/// </summary>
public class TokenExistHandler : IAsyncMiddleware<string, HandlerResult>
{
    /// <summary>
    /// Runs the parameter
    /// </summary>
    /// <param name="parameter">The parameter</param>
    /// <param name="next">The next</param>
    /// <returns>A task containing the handler result</returns>
    public async Task<HandlerResult> Run(string parameter, Func<string, Task<HandlerResult>> next)
    {
        if (string.IsNullOrEmpty(parameter))
        {
            return new HandlerResult(
                isSuccessful: false,
                actionResult: new UnauthorizedObjectResult("Token is missing."));
        }

        return await next(parameter);
    }
}

上一篇的實作裡是要自己建立一個 IHandler  介面和 HandlerBase 抽象類別,然後各個 Handler 去繼承實作。而使用 PipelineNet 的話就看你的實作是否有用到非同步,如果有的話就繼承實作 IAsyncMiddleware<TParameter,TReturn> 介面,沒有非同步就繼承實作 IMiddleware<TParameter,TReturn> 介面。

這邊就不再一一展示各個 Handler 實作的程式碼。

接著就如同 PipelineNet 的範例,要建立 IResponsibilityHandlerFactory 介面與 ResponsibilityHandlerFactory 類別,主要的工作串起每個 Handler 形成執行責任鍊。

/// <summary>
/// interface IResponsibilityHandlerFactory
/// </summary>
public interface IResponsibilityHandlerFactory
{
    /// <summary>
    /// Creates the responsibility chain
    /// </summary>
    /// <returns>An async responsibility chain of string and handler result</returns>
    IAsyncResponsibilityChain<string, HandlerResult> CreateResponsibilityChain();
}

而我們這邊的 ResponsibilityHandlerFactory 類別會使用到 Finally 方法去定義 fallback function,這是定義當職責鍊裡每個 Handler 執行結果的 IsSuccessful 都為 true 時最後要回傳的結果,上一篇的這個處理是寫在 HandlerBase 抽象類別裡,

但這次是使用 PipelineNet 就不必用到 HandlerBase,直接使用 IAsyncResponsibilityChain<string, HandlerResult>  的 Finally 方法來設定這個處理,以下是 ResponsibilityHandlerFactory  類別的實作內容

using PipelineNet.ChainsOfResponsibility;
using PipelineNet.ServiceProvider.MiddlewareResolver;
using WebApplication1.Models;

namespace WebApplication1.PipelineNet;

/// <summary>
/// class ResponsibilityHandlerFactory
/// </summary>
public class ResponsibilityHandlerFactory : IResponsibilityHandlerFactory
{
    private readonly IServiceProvider _serviceProvider;

    /// <summary>
    /// Initializes a new instance of the <see cref="ResponsibilityHandlerFactory"/> class
    /// </summary>
    /// <param name="serviceProvider">The service provider</param>
    public ResponsibilityHandlerFactory(IServiceProvider serviceProvider)
    {
        this._serviceProvider = serviceProvider;
    }

    /// <summary>
    /// Creates the responsibility chain
    /// </summary>
    /// <returns>An async responsibility chain of string and handler result</returns>
    public IAsyncResponsibilityChain<string, HandlerResult> CreateResponsibilityChain()
    {
        return new AsyncResponsibilityChain<string, HandlerResult>(new ActivatorUtilitiesMiddlewareResolver(this._serviceProvider))
               .Chain<TokenExistHandler>()
               .Chain<TokenValidationHandler>()
               .Chain<TokenExpirationHandler>()
               .Chain<RoleCheckHandler>()
               .Chain<TransactionLimitHandler>()
               .Chain<ExternalServiceHandler>()
               .Finally(_ => Task.FromResult(new HandlerResult(true)));
    }
}

對了,這邊會使用到 ActivatorUtilitiesMiddlewareResolver 這個類別,這個類別是另外一個 NuGet  套件「PipelineNet.ServiceProvider」裡,所以要記得除了 PipelineNet  之外也需要安裝 PipelineNet.ServiceProvider

Program.cs 的註冊 IResponsibilityHandlerFactory 與 ResponsibilityHandlerFactory

接著是 Controller 裡相依注入使用 IResponsibilityHandlerFactory,在 Action 方法裡使用 _responsibilityHandlerFactory.CreateResponsibilityChain() 方法建立責任鍊,然後使用 chain.Execute 方法去執行責任鍊的處理

/// <summary>
/// class ExampleWithPipelineNetController
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class ExampleWithPipelineNetController : ControllerBase
{
    private readonly IResponsibilityHandlerFactory _responsibilityHandlerFactory;

    /// <summary>
    /// Initializes a new instance of the <see cref="ExampleWithPipelineNetController"/> class
    /// </summary>
    /// <param name="responsibilityHandlerFactory">The responsibilityHandlerFactory</param>
    public ExampleWithPipelineNetController(IResponsibilityHandlerFactory responsibilityHandlerFactory)
    {
        this._responsibilityHandlerFactory = responsibilityHandlerFactory;
    }
    
    [HttpGet("data")]
    public async Task<IActionResult> GetDataAsync([FromHeader] string token)
    {
        var chain = this._responsibilityHandlerFactory.CreateResponsibilityChain();

        var result = await chain.Execute(token);

        // 如果處理不成功,返回對應的錯誤結果
        if (!result.IsSuccessful)
        {
            return result.ActionResult;
        }

        // 所有處理成功,回傳最終成功結果
        return this.Ok("Success: All validations passed.");
    }
}

這樣就完成了使用 PipelineNet 實作責任鍊模式。

其實經過實作後覺得好像跟原本不用套件的方式並沒有差太多,使用 PipelineNet 的實作是可以少建立幾個介面與抽象類別,對於一個專案裡只會有一個責任鍊需求的情況來說,不用套件也沒有什麼差別,但如果一個專案裡會實作許多責任鍊的情況時,使用 PipelineNet 就會方便不少。

以上

純粹是在寫興趣的,用寫程式、寫文章來抒解工作壓力