具有两个依赖服务的循环依赖
Circular Dependency with two depending Services
我是 C# 和依赖注入的新手。目前我正在做一个新项目,想在技术上取得进步。
在这个项目中,我遇到了三种导致循环依赖的情况。
我已经阅读了很多关于此的内容并找到了 Lazy<T>
和 IServiceProvider
等解决方案,但我想学习针对此问题的干净解决方案,并希望遵循最常见的建议重构代码。
我们在这个例子中有四个服务:
AccountService
-> 登录、注销等
HttpService
-> 做 API-Stuff
LogService
-> 做一些日志记录
LogRepository
-> 用于日志记录的 CRUD table / EF 的包装器
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
将日志条目发送到服务器)。
非常感谢您的帮助!
虽然你的对象图是循环的(AccountService
-> HttpService
-> AccountService
)你的调用图 不是。调用可能如下所示:
AccountService.Login
-> HttpService.Post
-> HttpService.AddAuthentication
-> AccountService.Identity
具有非循环调用图的循环对象图经常发生在违反Single Responsibly Principle的组件上。获得的功能(方法)越多 类,它们的对象图变得循环的可能性就越大。将 类 拆分为更小、更集中的部分,不仅可以解决循环依赖问题,而且通常还可以改进应用程序的设计。
我认为您的情况实际上与我在 section 6.3 of 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
逻辑隐藏在新的 IIdentityProvider
抽象之后,它被注入 AuthenticationService
。这个重构和DIPP&P的Facade Service refactoring (for an elaborate discussion on the Facade Service refactoring, see 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);
我是 C# 和依赖注入的新手。目前我正在做一个新项目,想在技术上取得进步。
在这个项目中,我遇到了三种导致循环依赖的情况。
我已经阅读了很多关于此的内容并找到了 Lazy<T>
和 IServiceProvider
等解决方案,但我想学习针对此问题的干净解决方案,并希望遵循最常见的建议重构代码。
我们在这个例子中有四个服务:
AccountService
-> 登录、注销等
HttpService
-> 做 API-Stuff
LogService
-> 做一些日志记录
LogRepository
-> 用于日志记录的 CRUD table / EF 的包装器
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
将日志条目发送到服务器)。
非常感谢您的帮助!
虽然你的对象图是循环的(AccountService
-> HttpService
-> AccountService
)你的调用图 不是。调用可能如下所示:
AccountService.Login
-> HttpService.Post
-> HttpService.AddAuthentication
-> AccountService.Identity
具有非循环调用图的循环对象图经常发生在违反Single Responsibly Principle的组件上。获得的功能(方法)越多 类,它们的对象图变得循环的可能性就越大。将 类 拆分为更小、更集中的部分,不仅可以解决循环依赖问题,而且通常还可以改进应用程序的设计。
我认为您的情况实际上与我在 section 6.3 of 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
逻辑隐藏在新的 IIdentityProvider
抽象之后,它被注入 AuthenticationService
。这个重构和DIPP&P的Facade Service refactoring (for an elaborate discussion on the Facade Service refactoring, see 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);