具有两个依赖服务的循环依赖

And*_*bel 4 .net c# rest dependency-injection webassembly

我对 C# 和依赖注入非常陌生。目前我正在开发一个新项目,希望在技术上取得进步。

在这个项目中,我遇到了三种导致循环依赖的情况。

我读了很多关于这方面的内容并找到了诸如Lazy<T>和 之类的解决方案IServiceProvider的解决方案,但我想学习针对此问题的干净解决方案,并希望遵循最常见的建议来重构代码。

在这个例子中我们有四个服务:

AccountService-> 登录、注销等

HttpService-> 做 API 的事情

LogService-> 做一些日志记录

LogRepository-> 用于 EF 的日志表/包装器的 CRUD

AccountService使用 API 通过 API 进行身份验证HttpService。后来,我想使用HttpServiceAPI 通过 API 获取更多数据。HttpService现在需要AccountService获取令牌来验证请求。这导致循环依赖错误。

账户服务

public interface IAccountService
{
    Identity Identity { get; }
    Task Login(Credentials Credentials);
    Task Logout();
}

public class AccountService : IAccountService
{
    public Identity Identity { get; private set; }
    
    private readonly IHttpService _httpService;
    private readonly ILogService _logService;
    
    public AccountService(
        IHttpService HttpService, ILogService LogService)
    {
        _httpService = HttpService;
        _logService = LogService;
    }

    public async Task Login(Credentials Credentials)
    {
        Identity = await _httpService.Post<Identity>(
            "api/rest/v1/user/authenticate", Credentials);
    }
}
Run Code Online (Sandbox Code Playgroud)

Http服务

public interface IHttpService
{
    Task<T> Get<T>(string uri);
    Task Post(string uri, object value);
    Task<T> Post<T>(string uri, object value);
}

public class HttpService : IHttpService
{
    private readonly HttpClient _httpClient;
    private readonly IAccountService _accountService;
    private readonly ILogService _logService; 

    public HttpService(
        HttpClient HttpClient,
        IAccountService AccountService,
        ILogService ILogService)
    {
        _httpClient = HttpClient;
        _accountService = AccountService;
        _logService = LogService;
    }

    private async Task AddAuthentication(HttpRequestMessage Request)
    {
        Request.Headers.Authorization = new AuthenticationHeaderValue(
            "bearer", _accountService.Identity.SystemToken);
    }
}
Run Code Online (Sandbox Code Playgroud)

解决或正确重新设计此问题的最佳实践是什么?

我有更多的循环依赖,例如使用LogServiceinLogRepository或 using LogServicein HttpService(因为HttpService将日志条目发送到服务器)。

非常感谢你的帮助!

Ste*_*ven 6

DI 容器无法满足存在循环的对象图,就像您无法用普通的旧式 C# 来表达这一点一样:

new AccountService(
    new HttpService(
        new AccountService(
            new HttpService(
                new AccountService(
                    new HttpService(
                        new AccountService(
                            new HttpService(
                                ... this would go on for ever     
Run Code Online (Sandbox Code Playgroud)

尽管您的对象图是循环的(AccountService-> HttpService-> AccountService),但您的调用图不是。该调用可能如下所示:

AccountService.Login
    -> HttpService.Post
        -> HttpService.AddAuthentication
            -> AccountService.Identity
Run Code Online (Sandbox Code Playgroud)

具有非循环调用图的循环对象图通常发生在违反单一责任原则的组件上。类获得的功能越多(方法越多),它们的对象图变成循环的可能性就越大。将类分成更小、更集中的部分,不仅可以解决循环依赖问题,而且通常还可以提高应用程序的可维护性。

我认为您的案例实际上与我在DIPP&P第 6.3 节中讨论的示例非常相似。该部分专门讨论了修复循环依赖关系。

长话短说,我认为最好的选择是分成AccountService(至少)两个服务:

  • 一项服务负责登录和注销
  • 第二个服务负责获取用户的身份。

这两种服务都有自己的接口,并且与IAccountService. 这提高了您遵守接口隔离原则的机会。

下面是一个示例:

让我们从新的接口定义开始:

AccountService.Login
    -> HttpService.Post
        -> HttpService.AddAuthentication
            -> AccountService.Identity
Run Code Online (Sandbox Code Playgroud)

接下来让我们看一下实现,从IAuthenticationService实现开始:

// Contains Login and Logout methods of old IAccountService
public interface IAuthenticationService
{
    Task Login(Credentials Credentials);
    Task Logout();
}

// Contains Identity property of old IAccountService
public interface IIdentityProvider
{
    // For simplicity I added a setter to the interface, because that keeps
    // the example simple, but it is possible to keep Identity read-only if
    // required.
    Identity Identity { get; set; }
}

// This interface is kept unchanged.
public interface IHttpService
{
    Task<T> Get<T>(string uri);
    Task Post(string uri, object value);
    Task<T> Post<T>(string uri, object value);
}
Run Code Online (Sandbox Code Playgroud)

这个“新”AuthenticationService包含 的部分代码,AccountService其余旧AccountService逻辑隐藏在新IIdentityProvider抽象后面,注入到 中AuthenticationService这种重构与Facade Service 重构非常相似(有关 Facade Service 重构的详细讨论,请参阅DIPP&P 的6.1 节)。

IdentityProvider实现新IIdentityProvider接口并包含旧逻辑AccountService

// Old AccountService, now depending on IIdentityProvider
public class AuthenticationService : IAuthenticationService
{
    private readonly IHttpService _httpService;
    private readonly ILogService _logService;
    private readonly IIdentityProvider _identityProvider;
    
    public AccountService(
        IHttpService HttpService,
        ILogService LogService,
        IIdentityProvider IdentityProvider)
    {
        _httpService = HttpService;
        _logService = LogService;
        _identityProvider = IdentityProvider;
    }

    public async Task Login(Credentials Credentials)
    {
        _identityProvider.Identity = await _httpService.Post<Identity>(
            "api/rest/v1/user/authenticate", Credentials);
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,现在HttpService取决于:IIdentityProviderIAccountService

public class IdentityProvider : IIdentityProvider
{
    public Identity Identity { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

使用这种新设计,对象图不再是循环的,并且当用普通 C# 构建时,可以如下构建:

// Now depends on IIdentityProvider instead of IAccountService
public class HttpService : IHttpService
{
    private readonly HttpClient _httpClient;
    private readonly IIdentityProvider _identityProvider;
    private readonly ILogService _logService; 

    public HttpService(
        HttpClient HttpClient,
        IIdentityProvider IdentityProvider,
        ILogService ILogService)
    {
        _httpClient = HttpClient;
        _identityProvider = IdentityProvider;
        _logService = LogService;
    }

    private async Task AddAuthentication(HttpRequestMessage Request)
    {
        // Now uses the new IIdentityProvider dependency instead
        // of the old IAccountService, which caused the cycle.
        Request.Headers.Authorization = new AuthenticationHeaderValue(
            "bearer", _identityProvider.Identity.SystemToken);
    }
}
Run Code Online (Sandbox Code Playgroud)