具有两个依赖服务的循环依赖 [英] Circular Dependency with two depending Services

查看:105
本文介绍了具有两个依赖服务的循环依赖的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对 C# 和依赖注入非常陌生.目前我正在从事一个新项目,并希望在技术上向前迈进.

I'm very new in C# and Dependency Injection. Currently I'm working on a new project and want to do a technology step forward.

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

In this Project, I've three situation causing circular dependency.

我已经阅读了很多关于此的内容并找到了诸如 Lazy<T>IServiceProvider 之类的解决方案,但我想为这个问题学习一个干净的解决方案,并且想要遵循最常见的建议来重构代码.

I've read a lot about this and found solutions like Lazy<T> and and IServiceProvider, but I want to learn a clean solution for this problem and want to follow the most common suggestion to refactor the code.

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

We have four services in this example:

AccountService ->登录、注销等

HttpService ->做 API 的事情

HttpService -> Do the API-Stuff

LogService ->做一些日志记录

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

LogRepository -> CRUD for the logging table / wrapper for EF

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

The AccountService authenticate via API using HttpService. Later, I want use the HttpService to get more data via API. HttpService now need the AccountService to get the Token for authenticate the request. This is causing a circular dependency error.

账户服务

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

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

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

How is the best practice to solve or proper redesign this?

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

I've more Circular Dependency, e.g. use the LogService in LogRepository or using LogService in HttpService (because the HttpService sends Log-Entrys to the Server).

非常感谢您的帮助!

推荐答案

虽然你的对象图是循环的 (AccountService -> HttpService-> AccountService) 你的调用图不是.电话可能是这样的:

Although your object graph is cyclic (AccountService -> HttpService -> AccountService) your call graph is not. The call likely is something as follows:

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

带有非循环调用图

循环对象图经常发生在违反单一责任原则.类获得的功能(方法)越多,它们的对象图变得循环的机会就越大.将类拆分为更小、更集中的部分,不仅可以解决循环依赖问题,而且通常还可以改进应用程序的设计.

Cyclic object graphs with non-cyclic call graphs often happen on components that violate the Single Responsibly Principle. The more functionality (methods) classes get, the bigger the chance their object graphs become cyclic. Splitting classes up in smaller, more focused pieces, not only fixes the cyclic-dependency problem, but often also improves the design of the application.

我认为您的情况实际上与我在 DIPP&P 的第 6.3 节.该部分专门讨论修复循环依赖.

I think your case is actually quite similar to the example that I discuss in section 6.3 of DIPP&P. That section specifically talks about fixing cyclic dependencies.

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

Long story short, I think your best bet is to split AccountService in (at least) two services:

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

这两个服务都有自己的接口,而且与 IAccountService 相比,这些新接口现在的范围更小.这提高了您遵守接口隔离原则的机会.

Both services get their own interface and those new interfaces are now less wide compared to IAccountService. This improves your chances of adhering to the Interface Segregation Principle.

这是一个示例:

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

Let's start with the new interface definitions:

// 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 实现开始:

Let's look at the implementations next, starting with the IAuthenticationService implementation:

// 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 逻辑的其余部分隐藏在新的 IIdentityProvider 后面> 抽象,注入到 AuthenticationService 中.这种重构非常类似于 Facade Service 重构(详细讨论关于 Facade Service 重构,请参阅 第 6.1 节 的 DIPP&P).

This "new" AuthenticationService contains part of the code of the AccountService and the rest of the old AccountService logic is hidden behind the new IIdentityProvider abstraction, which is injected into AuthenticationService. This refactoring is very similar to the Facade Service refactoring (for an elaborate discussion on the Facade Service refactoring, see section 6.1 of DIPP&P).

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

IdentityProvider implements the new IIdentityProvider interface and contains the old logic from AccountService:

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

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

And finally, HttpService that now depends on IIdentityProvider instead of 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);
    }
}

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

Using this new design, the object graph is no longer cyclic and can be constructed as follows:

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

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

这篇关于具有两个依赖服务的循环依赖的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆