走進WebApiClientCore的設計

WebApiClient

WebApiClientNCC開源社區的一個項目,是目前微服務裏http接口調用的一把鋒利尖刀,項目早期設計與開發的時候,是基於.netframework的,然後慢慢加入netstandard和netcoreapp多個框架的支持,設計能力出衆,AOP能力唾手可得易如反掌。

WebApiClientCore

WebApiClient很優秀,它將不同框架不同平臺都實現了統一的api;WebApiClient不夠優秀,它在.netcore下完全可以更好,但它不得不兼容.net45開始所有框架而有所犧牲。所以WebApiClientCore橫空出世,它是WebApiClient.JIT的.netcore替代版本,目前尚屬於alpha階段,計劃只支持.netcore平臺,並緊密與.netcore新特性緊密結合。

WebApiClientCore的變化

  • 使用System.Text.Json替換Json.net,提升序列化性能
  • 移除HttpApiFactory和HttApiConfig功能,使用Microsoft.Extensions.Http的HttpClientFactory
  • 移除AOT功能,僅保留依賴於Emit的運行時代理
  • 高效的ActionInvoker,對返回Task<>和ITask<>作不同處理
  • 所有特性都都變成中間件,基於管道編排各個特性並生成Action執行委託
  • 良好設計的HttpContext、ApiRequestContext、ApiParameterContext和ApiResponseContext

WebApiClientCore執行流程設計

1 接口代理類生成設計

Cretate<THttpApi>() -> BuildProxyType() -> CreateProxyInstance(ActionInterceptor)

1.1 HttpApiProxyTypeBuilder

在HttpApi.Create()時,先調用HttpApiProxyTypeBuilder來生成THttpApi接口的代理類,HttpApiProxyTypeBuilder是基於Emit方案,Build出來的代理類在每個方法調用時觸發一次攔截器ActionInterceptor的Intercept()方法,將調用參數傳給攔截器。

1.2 HttpApiProxyBuilder

給定一個代理類的類型(Type),快速生成代理類的實例,這個Builder實際是生成並保存了代理類構造器的高效調用委託,屬於反射優化。

2 ActionInterceptor的設計

ActionInterceptor.Intercept(MethodInfo) -> CreateActionInvoker() -> ActionInvoker.Invoke()

ActionInterceptor在攔截到方法調用時,根據方法的MethodInfo信息,創建ActionInvoker,然後調用ActionInvoker.Invoke()執行。當然,ActionInvoker並不是總是創建的,因爲它的創建是有成本的,ActionInterceptor使用了緩存ActionInvoker的方案。

2.1 MultiplexedActionInvoker

WebApiClientCore支持加Task<>和ITask<>兩種異步聲明,MultiplexedActionInvoker實際上包裝了ActionInvoker和ActionTask兩個字段,當聲明爲Task<>時,調用ActionInvoker執行,當聲明爲ITask<>是,返回創建實現了ITask<>接口的ActionTask實例。

2.2 ActionInvoker

ActionInvoker是一個ApiActionDescriptor的執行器,其實現了IActionInvoker.Invoke(ServiceContext context, object[] arguments)接口。關於Descriptor的設計模式,我們在asp.netcore的各種AtionContext裏可以發現,有了ApiActionDescriptor,再給它各個參數值,Action就很容易執行起來了。

3 RequestDelegate生成設計

ActionInvoker在拿到各個參數值之後,並不是直接從ApiActionDescriptor查找各個特性來執行,而是在執行前就把執行流程編譯好,得到一個執行委託,這個委託叫RequestDelegate,其原型爲Func<ApiRequestContext, Task<ApiResponseContext>> Build(ApiActionDescriptor apiAction)。抽象成傳入請求上下文件,返回響應上下文,當真正執行時,調用這個委託即可。如果你熟悉asp.netcore,那麼應該很容易理解下面代碼的思路:

/// <summary>
/// 提供Action的調用鏈委託創建
/// </summary>
static class RequestDelegateBuilder
{
    /// <summary>
    /// 創建執行委託
    /// </summary>
    /// <returns></returns>
    public static Func<ApiRequestContext, Task<ApiResponseContext>> Build(ApiActionDescriptor apiAction)
    {
        var requestHandler = BuildRequestHandler(apiAction);
        var responseHandler = BuildResponseHandler(apiAction);

        return async request =>
        {
            await requestHandler(request).ConfigureAwait(false);
            var response = await SendRequestAsync(request).ConfigureAwait(false);
            await responseHandler(response).ConfigureAwait(false);
            return response;
        };
    }


    /// <summary>
    /// 創建請求委託
    /// </summary>
    /// <param name="apiAction"></param>
    /// <returns></returns>
    private static InvokeDelegate<ApiRequestContext> BuildRequestHandler(ApiActionDescriptor apiAction)
    {
        var builder = new PipelineBuilder<ApiRequestContext>();

        // 參數驗證特性驗證和參數模型屬性特性驗證
        builder.Use(next => context =>
        {
            var validateProperty = context.HttpContext.Options.UseParameterPropertyValidate;
            foreach (var parameter in context.ApiAction.Parameters)
            {
                var parameterValue = context.Arguments[parameter.Index];
                ApiValidator.ValidateParameter(parameter, parameterValue, validateProperty);
            }
            return next(context);
        });

        // action特性請求前執行
        foreach (var attr in apiAction.Attributes)
        {
            builder.Use(attr.OnRequestAsync);
        }

        // 參數特性請求前執行
        foreach (var parameter in apiAction.Parameters)
        {
            var index = parameter.Index;
            foreach (var attr in parameter.Attributes)
            {
                builder.Use(async (context, next) =>
                {
                    var ctx = new ApiParameterContext(context, index);
                    await attr.OnRequestAsync(ctx, next).ConfigureAwait(false);
                });
            }
        }

        // Return特性請求前執行
        foreach (var @return in apiAction.Return.Attributes)
        {
            if (@return.Enable == true)
            {
                builder.Use(@return.OnRequestAsync);
            }
        }

        // Filter請求前執行            
        foreach (var filter in apiAction.FilterAttributes)
        {
            if (filter.Enable == true)
            {
                builder.Use(filter.OnRequestAsync);
            }
        }

        return builder.Build();
    }

    /// <summary>
    /// 創建響應委託
    /// </summary>
    /// <param name="apiAction"></param>
    /// <returns></returns>
    private static InvokeDelegate<ApiResponseContext> BuildResponseHandler(ApiActionDescriptor apiAction)
    {
        var builder = new PipelineBuilder<ApiResponseContext>();

        // Return特性請求後執行
        foreach (var @return in apiAction.Return.Attributes)
        {
            if (@return.Enable == false)
            {
                continue;
            }

            builder.Use(async (context, next) =>
            {
                if (context.ResultStatus == ResultStatus.None)
                {
                    await @return.OnResponseAsync(context, next).ConfigureAwait(false);
                }
                else
                {
                    await next().ConfigureAwait(false);
                }
            });
        }

        // 驗證Result是否ok
        builder.Use(next => context =>
        {
            try
            {
                ApiValidator.ValidateReturnValue(context.Result, context.HttpContext.Options.UseReturnValuePropertyValidate);
            }
            catch (Exception ex)
            {
                context.Exception = ex;
            }
            return next(context);
        });

        // Filter請求後執行
        foreach (var filter in apiAction.FilterAttributes)
        {
            if (filter.Enable == true)
            {
                builder.Use(filter.OnResponseAsync);
            }
        }

        return builder.Build();
    }


    /// <summary>
    /// 執行http請求
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    private static async Task<ApiResponseContext> SendRequestAsync(ApiRequestContext context)
    {
        try
        {
            var apiCache = new ApiCache(context);
            var cacheValue = await apiCache.GetAsync().ConfigureAwait(false);

            if (cacheValue != null && cacheValue.Value != null)
            {
                context.HttpContext.ResponseMessage = cacheValue.Value;
            }
            else
            {
                using var cancellation = CreateLinkedTokenSource(context);
                var response = await context.HttpContext.Client.SendAsync(context.HttpContext.RequestMessage, cancellation.Token).ConfigureAwait(false);

                context.HttpContext.ResponseMessage = response;
                await apiCache.SetAsync(cacheValue?.Key, response).ConfigureAwait(false);
            }
            return new ApiResponseContext(context);
        }
        catch (Exception ex)
        {
            return new ApiResponseContext(context) { Exception = ex };
        }
    }

    /// <summary>
    /// 創建取消令牌源
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    private static CancellationTokenSource CreateLinkedTokenSource(ApiRequestContext context)
    {
        if (context.CancellationTokens.Count == 0)
        {
            return CancellationTokenSource.CreateLinkedTokenSource(CancellationToken.None);
        }
        else
        {
            var tokens = context.CancellationTokens.ToArray();
            return CancellationTokenSource.CreateLinkedTokenSource(tokens);
        }
    }
}

WebApiClientCore的特性設計

WebApiClientCore的核心特性爲以下4種,每種功能各不一樣,在設計上使用了中間件的思想,每一步執行都可以獲取到context對象和下一個中間件next對象,開發者在實現自定義Attribute時,可以選擇性的進行短路設計。

1 IApiActionAttribute

表示Action執行前會調用,調用時接收到ApiRequestContext

/// <summary>
/// 定義ApiAction修飾特性的行爲
/// </summary>
public interface IApiActionAttribute : IAttributeMultiplable
{
    /// <summary>
    /// 請求前
    /// </summary>
    /// <param name="context">上下文</param>
    /// <param name="next">下一個執行委託</param>
    /// <returns></returns>
    Task OnRequestAsync(ApiRequestContext context, Func<Task> next);
}

2 IApiParameterAttribute

表示參數執行前會調用,調用時接收到ApiParameterContext

/// <summary>
/// 定義Api參數修飾特性的行爲
/// </summary>
public interface IApiParameterAttribute
{
    /// <summary>
    /// 請求前
    /// </summary>
    /// <param name="context">上下文</param>
    /// <param name="next">下一個執行委託</param>
    /// <returns></returns>
    Task OnRequestAsync(ApiParameterContext context, Func<Task> next); 
}

3 IApiReturnAttribute

執行前和執行後都會收到,設置爲上下文的Result或Exception,會短路執行

/// <summary>
/// 定義回覆內容處理特性的行爲
/// </summary>
public interface IApiReturnAttribute : IAttributeMultiplable, IAttributeEnable
{
    /// <summary>
    /// 請求前
    /// </summary>
    /// <param name="context">上下文</param>
    /// <param name="next">下一個執行委託</param>
    /// <returns></returns>
    Task OnRequestAsync(ApiRequestContext context, Func<Task> next);

    /// <summary>
    /// 響應後
    /// </summary>
    /// <param name="context">上下文</param>
    /// <param name="next">下一個執行委託</param>
    /// <returns></returns>
    Task OnResponseAsync(ApiResponseContext context, Func<Task> next);
}

5 IApiFilterAttribute

執行前和執行後都會收到,在IApiReturnAttribute之後執行

/// <summary>
/// 定義ApiAction過濾器修飾特性的行爲
/// </summary>
public interface IApiFilterAttribute : IAttributeMultiplable, IAttributeEnable
{
    /// <summary>
    /// 請求前
    /// </summary>
    /// <param name="context">上下文</param>
    /// <param name="next">下一個執行委託</param>
    /// <returns></returns>
    Task OnRequestAsync(ApiRequestContext context, Func<Task> next);

    /// <summary>
    /// 響應後
    /// </summary>
    /// <param name="context">上下文</param>
    /// <param name="next">下一個執行委託</param>
    /// <returns></returns>
    Task OnResponseAsync(ApiResponseContext context, Func<Task> next);
}

結束語

代碼可以寫得很爛,但設計必須高大上,希望WebApiClientCore可以在聲明式客戶端領域繼續引領其它開源庫,同時讓使用它的開發者爲之讚歎。

如果你希望爲望WebApiClientCore出力,可以Fork它然後pull request,和我一起完善單元測試,或編寫多語言資源文件,或者加入一些更好的代碼設計。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章