IceRPC之依賴注入>快樂的RPC

xlgwr發表於2024-06-08

作者引言

很高興啊,我們來到了IceRPC之依賴注入>快樂的RPC,基礎引導,打好基礎,才能讓自已不在迷茫,快樂的暢遊世界。

依賴注入和IceRPC

瞭解 IceRPC (C#) 如何為依賴注入(DI)提供支援。

DI作為可選功能

DI的第一條規則是:不要引入對DI的依賴。

IceRPC (C#) 將此規則放首要位置上,即為 DI 提供全方位支援,同時使此支援完全可選。

IceRPC C# API 被設計為可選 DI 容器。此外,IceRPC 還提供支援程式碼來幫助大家,在 Microsoft's DI container中使用 IceRPC。.

DI APIs

IceRPC 提供的所有與 DI 相關的 API 都在同一個名稱空間中, IceRpc.Extensions.DependencyInjection, 但由多個元件實現:

  • IceRpc.dll 提供如IDispatcherBuilderIInvokerBuilder IceRpc.Deadline.dll之類的抽象以及其他攔截器/中介軟體元件,為IDispatcherBuilderIInvokerBuilder提供了擴充套件方法。這些擴充套件方法與 DI 容器無關。
  • IceRpc.Extensions.DependencyInjection.dll 為 Microsoft 的 DI 容器提供支援程式碼。這包括 IServiceCollection 的各種擴充套件方法,例如 AddIceRpcServerAddIceRpcClientConnection,以及 IDispatcherBuilderIInvokerBuilder 的實現。
--- title: 程式集依賴圖 --- flowchart BT di([Microsoft.Extensions.DependencyInjection.Abstractions.dll]) logging([Microsoft.Extensions.Logging.Abstractions.dll]) options([Microsoft.Extensions.Options.dll]) IceRpc.dll --> logging IceRpc.Logger.dll --> IceRpc.dll IceRpc.Logger.dll --> logging IceRpc.Deadline.dll --> IceRpc.dll IceRpc.Extensions.DependencyInjection.dll --> di IceRpc.Extensions.DependencyInjection.dll --> options IceRpc.Extensions.DependencyInjection.dll --> IceRpc.dll

排程管道與 DI

瞭解如何使用 DI 容器構建排程管道。

傳統的排程管道

傳統的排程管道相當靜態: 建立路由、新增一些中介軟體、在此路由中對映或安裝少量葉片排程程式,然後讓伺服器將傳入的請求排程到此路由。

葉片排程程式(通常是 Slice 服務)被對映或安裝在固定路徑(例如/greeter 或/admin/greeter-manager)。 這些排程程式是單例(或類似單例),其使用壽命與路由和伺服器相同。

排程管道中的中介軟體使用以下features相互通訊: 上游中介軟體設定下游中介軟體可以檢索的features。葉片排程程式還可以使用相同的features與這些中介軟體進行通訊。

這對於許多應用程式來說都很有效。然而,這並不是使用 DI 時的典型模型。

DI容器構建排程管道

DI,排程管道通常更具動態性: 一些基礎設施程式碼為每個排程建立唯一的 DI 範圍,並且葉片排程程式是由 DI 容器管理的服務。當該葉片排程器的生命週期是短暫的或範圍化的時,該葉片排程器是按需建立的(每個排程)。

DI 排程管道中的中介軟體可以像往常一樣使用features,與其他中介軟體和葉片排程器進行通訊。然而,更慣用的方法是使用注入服務進行通訊。例如:

  • 上游中介軟體接收範圍服務(透過注入),然後填寫此服務
  • 下游中介軟體接收相同的作用域服務(也透過注入)並讀取上游中介軟體填寫的資訊
  • 葉片排程器的建構函式(作用域或瞬態服務)與該先前填充的作用域服務自動連線

中介軟體鏈本身是靜態的: 每個排程不會建立新的中介軟體例項。中介軟體通常是由 DI 容器管理的單例。

使用 Microsoft 的 DI 容器構建排程管道

IceRpc.Extensions.DependencyInjection元件為 IServiceCollection 提供了多種接受 Action<IDispatcherBuilder> 引數的擴充套件方法。

所有這些方法都允許 Microsoft 的 DI 容器構建和配置排程管道。例如:

// Construct a dispatch pipeline using Microsoft's DI container.

using IceRpc.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
...

// Add a new IDispatcher singleton configured with an action.
services.AddIceRpcDispatcher(builder => builder.Map<IGreeterService>());

由此產生的排程程式(排程管道)為每個傳入請求建立一個新的 DI 範圍,並使用 IServiceProviderFeature 將此範圍傳輸到下游排程程式。

在 IDispatcherBuilder 中安裝標準中介軟體

標準中介軟體是可以與 DI 容器一起使用或不使用 DI 容器的中介軟體: 它不依賴DI容器注入服務來執行,並且實現了介面 IDispatcher

IceRPC 附帶的所有中介軟體都是標準中介軟體: 可以在有或沒有 DI 的情況下使用它們,並且它們在排程中使用功能進行通訊。

這些中介軟體可以安裝在路由或 IDispatcherBuilder 中。例如:

// Construct a dispatch pipeline using Microsoft's DI container.

using IceRpc.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
...

services.AddIceRpcDispatcher(
    builder => builder
        .UseLogger()
        .Map<IGreeterService>());

這裡,UseLoggerIceRpc.Logger 元件提供的擴充套件方法。此擴充套件方法適用於實現 IServiceProvider 的任何 DI 容器,例如 Microsoft 的 DI 容器和 Simple Injector 的容器。

UseLogger 的實現只是從 DI 容器中檢索一個記錄器例項, 然後用這個例項建立一個新的中介軟體:

public static IDispatcherBuilder UseLogger(this IDispatcherBuilder builder) =>
    builder.ServiceProvider.GetService(typeof(ILogger<LoggerMiddleware>)) is ILogger logger ?
        builder.Use(next => new LoggerMiddleware(next, logger)) :
        throw new InvalidOperationException(
            $"Could not find service of type '{nameof(ILogger<LoggerMiddleware>)}' in the service container.");

我們建議在建立自己的標準中介軟體時遵循相同的模式,併為 RouterIDispatcherBuilder 提供使用擴充套件方法。

通常不鼓勵在執行時呼叫 DI 容器—它是反模式的服務定位器。在這裡,應該將 UseLogger 擴充套件方法視為不受此規則約束的基礎設施程式碼。

具有注入服務的中介軟體

DI 容器注入的服務來建立與其他中介軟體和葉片排程器通訊的中介軟體,而不是提供標準的中介軟體。

DI 友好的中介軟體需要實現以下 IMiddleware 介面之一:

  • IMiddleware<TDep>
  • IMiddleware<TDep1, TDep2>
  • IMiddleware<TDep1, TDep2, TDep3>

例如,我們希望以更適合 DI 的方式,重新實施截止日期中介軟體。標準截止日期中介軟體讀取截止日期欄位,並建立截止日期功能,以將此截止日期傳達給,下游中介軟體和葉片排程程式。新的 DI 友好截止日期中介軟體,解碼截止日期並將此資訊,儲存在注入的 scoped 服務中:

// Configured as a scoped service in the composition root of the application.
public class DeadlineInformation
{
    public DateTime Value { get; set; } = DateTime.MinValue; // MinValue means no deadline.
}
...

// New DI-friendly deadline middleware. Note that it does not implement IDispatcher.
public class DIDeadlineMiddleware : IMiddleware<DeadlineInformation>
{
    private readonly IDispatcher _next;

    // A constructor with an IDispatcher parameter is required for auto-wiring.
    public DIDeadlineMiddleware(IDispatcher next) => _next = next;

    // deadlineInfo is a scope service provided by the DI container.
    public ValueTask<OutgoingResponse> DispatchAsync(
        IncomingRequest request,
        DeadlineInformation deadlineInfo,
        CancellationToken cancellationToken)
    {
        // Decode the deadline field as usual.
        DateTime deadline = request.Fields.DecodeValue(
            RequestFieldKey.Deadline,
            (ref SliceDecoder decoder) => decoder.DecodeTimeStamp());

        if (deadline != DateTime.MinValue)
        {
            // If deadline is not MinValue, store it in deadlineInfo
            deadlineInfo.Value = deadline;

            // TODO: enforce deadline while calling _next.DispatchAsync.
        }
        else
        {
            // Call _next.DispatchAsync as usual.
            return _next.DispatchAsync(request, cancellationToken);
        }
    }
}

如果使用 Microsoft 的 DI 容器,可以使用 IceRpc.Extensions.DependencyInjection 元件提供的 UseMiddleware 擴充套件方法安裝此中介軟體:

using IceRpc.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
...

// The DIDeadlineMiddleware is instantiated and managed by the DI container.
services.AddIceRpcDispatcher(
    builder => builder
        .UseLogger()
        .UseMiddleware<DIDeadlineMiddleware>()
        .Map<IGreeterService>());

// To be registered as a transient or scoped service in the DI container.
[SliceService]
internal partial class Chatbot : IGreeterService
{
    // DeadlineInformation is auto-wired by the DI container.
    internal Chatbot(DeadlineInformation deadlineInfo)
    {
        ...
    }
    ...
}

呼叫管道與DI

瞭解如何使用 DI 容器構建呼叫管道。

DI 容器構建呼叫管道

與排程管道不同,呼叫管道在有或沒有 DI 容器的情況下幾乎相同。 這是因為呼叫沒有自然的 DI 範圍: DI 作用域內執行呼叫,則該作用域來自另一個封閉活動,例如進行此呼叫的排程。

IceRPC [C#] 不會為 DI 範圍內的呼叫提供任何特殊支援。特別是 IMiddleware 沒有攔截器對應物。

使用 Microsoft 的 DI 容器構建呼叫管道

可以呼叫 AddIceRpcInvoker 將新的呼叫器(呼叫管道)單例新增到您的 DI 容器中。

例如:

// Construct an invocation pipeline using Microsoft's DI container.

using IceRpc.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
...

// Add a new IInvoker singleton configured with an action.
services
    .AddIceRpcClientConnection()
    .AddIceRpcInvoker(builder => builder.Into<ClientConnection>())

必須使用 Into 方法指定最終呼叫器。透過此示例,新呼叫器流入我們之前配置的 ClientConnection 單例。

IInvokerBuilder 中安裝攔截器

所有使用 IceRPC 傳送的攔截器都可以在有或沒有 DI 的情況下使用,並使用呼叫中的通訊 features 。 例如,重試攔截器使用 IServerAddressFeature 與連線快取通訊,以協調複製伺服器上的重試。

這些攔截器可以安裝在管道或 IInvokerBuilder 中。

例如:

// Construct an invocation pipeline using Microsoft's DI container.

using IceRpc.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
...

// Add a new IInvoker singleton configured with an action.
services
    .AddIceRpcClientConnection()
    .AddIceRpcInvoker(builder =>
        builder
            .UseLogger()
            .Into<ClientConnection>())

這裡,UseLoggerIceRpc.Logger 元件提供的擴充套件方法。 此擴充套件方法適用於實現 IServiceProvider 的任何 DI 容器,例如 Microsoft 的 DI 容器和 Simple Injector 的容器。

UseLogger 方法的實現只是從 DI 容器中檢索一個記錄器例項, 然後用這個例項建立一個新的攔截器:

public static IInvokerBuilder UseLogger(this IInvokerBuilder builder) =>
    builder.ServiceProvider.GetService(typeof(ILogger<LoggerInterceptor>)) is ILogger logger ?
        builder.Use(next => new LoggerInterceptor(next, logger)) :
        throw new InvalidOperationException(
            $"Could not find service of type '{nameof(ILogger<LoggerInterceptor>)}' in the service container.");

我們建議在建立自己的攔截器時遵循相同的模式,併為 PipelineIInvokerBuilder 提供使用擴充套件方法。

通常不鼓勵在執行時呼叫 DI 容器—它是反模式的服務定位器。在這裡,應該將 UseLogger 擴充套件方法視為不受此規則約束的基礎設施程式碼。

收尾

最近寫的都是基礎相關的概念,大家看看就行,以官方為主更為妙哉。

作者結語

  • 一直做,不停做,才能提升速度
  • 翻譯的不好,請手下留情,謝謝
  • 覺得還不錯的話,點個
  • 如果對我有點小興趣,如可加我哦,一起探討人生,探討道的世界
    image

相關文章