【问题标题】:Circular Dependency with two depending Services具有两个依赖服务的循环依赖
【发布时间】:2021-08-15 21:14:26
【问题描述】:

我是 C# 和依赖注入的新手。目前我正在开展一个新项目,并希望在技术方面向前迈出一步。

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

我已经阅读了很多这方面的内容,并找到了像 Lazy<T> 和 IServiceProvider 这样的解决方案,但我想学习一个干净的解决方案来解决这个问题,并希望遵循最常见的建议来重构代码。

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

AccountService -> 登录、注销等

HttpService -> 做 API-Stuff

LogService -> 做一些日志记录

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

AccountService 使用HttpService 通过 API 进行身份验证。稍后,我想使用HttpService 通过 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);
    }
}

HttpService

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);
    }
}

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

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

非常感谢您的帮助!

【问题讨论】:

  • 您能否以如下方式更新您的代码示例:1. 删除与问题无关的所有依赖项,以及 2. 精简与循环依赖项相关的方法版本,即显示调用HttpService 的AccountService 的方法,并显示调用IAccountService 的HttpService 的方法。
  • 也就是说,在不知道细节的情况下,我敢打赌你的AccountService 或HttpService 做得太多,换句话说,违反了Single Responsibility Principle。将此类拆分为多个较小的类通常会解决 SRP 违规和循环依赖问题。我在我的书的section 6.3 中详细讨论了这一点。
  • 我已经编辑了代码。您现在可以看到呼叫。服务做的不多。 HttpService 仅处理 api 调用,AccountService 仅登录、注销并保存有关当前用户的信息。
  • 在不了解您的系统的情况下,HttpService 似乎没有那么有价值,并且可能被过度设计。我建议完全删除这个类,并在您需要进行 HTTP 调用的每个服务中创建 HttpClient。

标签: c# .net rest dependency-injection webassembly


【解决方案1】:

虽然你的对象图是循环的(AccountService -> HttpService -> AccountService)你的调用图不是。调用可能如下:

AccountService.Login
    -> HttpService.Post
        -> HttpService.AddAuthentication
            -> AccountService.Identity

循环对象图和非循环调用图经常发生在违反Single Responsibly Principle的组件上。功能(方法)类获得的越多,它们的对象图变得循环的机会就越大。将类拆分成更小、更集中的部分,不仅可以解决循环依赖问题,而且通常还可以改进应用程序的设计。

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

长话短说,我认为最好的办法是将AccountService 拆分为(至少)两个服务:

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

这两个服务都有自己的接口,与IAccountService 相比,这些新接口现在更宽。这可以提高您遵守Interface Segregation Principle 的机会。

下面是一个例子:

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

// 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);
}

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

// 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);
    }
}

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

IdentityProvider 实现了新的IIdentityProvider 接口并包含来自AccountService 的旧逻辑:

public class IdentityProvider : IIdentityProvider
{
    public Identity Identity { get; set; }
}

最后,HttpService 现在依赖于IIdentityProvider 而不是IAccountService:

// 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);
    }
}

使用这种新设计,对象图不再是循环的,可以如下构造:

var identity = new IdentityProvider();
var logger = new LogService();

new AccountService(
    new HttpService(
        new HttpClient(...),
        identity,
        logger),
    logger,
    identity);

【讨论】:

  • 太棒了,谢谢。我会买你的书来了解更多关于现代建筑的知识:-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-12
相关资源
最近更新 更多