带有运行时实现解析器的.net DI



我有一个涉及DI的奇怪案例,特别是在运行时从同一服务中解析实现时。我知道我可以注入一个服务提供者,但这似乎违反了依赖倒置原则。

另外,如果这最终成为一个架构/设计问题,我很抱歉;我最近刚从。net框架开发转过来,仍然在熟悉DI的局限性。注意我已经简化了&出于明显的原因更改了业务上下文,因此请记住层次结构/结构是重要的部分……对于这个问题,我决定用一个经典的在线零售商的例子。

项目概述/例子:

core libraryNET类库)- IRetailerService:客户端应用所使用的公共服务├IOrderService:facade/aggregate services注入到^├IInventoryManager:内部组件注入到facade/aggregate services以及其他组件p>Aggregate/farade Services
public class RetailerService : IRetailerService
{
private readonly IOrderService _orderService;
public OrderService( IOrderService orderService, ... ) { //... set injected components }

async Task IRetailerService.Execute( Guid id )
{
await _orderService.Get( id );
}
async Task IRetailerService.Execute( Guid id, User user )
{
await _orderService.Get( id, user );
}
}
internal class OrderService : IOrderService
{
public OrderService( IInventoryManager inventoryManager, IProductRespository productRepo, ... ) { }
async Task<object> IOrderService.Get( Guid id )
{
//... do stuff with the injected components
await _inventoryManager.Execute( ...args );
await _productRepo.Execute( ...args );
}
async Task<object> IOrderService.Get( Guid id, User user ) { }
}

问题:

让我们说我想记录IOrderService.Get( Guid id, User user ),但只有当这个覆盖与User提供-这包括日志内部注入的组件(InventoryManager, iproductrerepository等)以及

目前我能想到的唯一解决办法是:

  1. 给这个层次添加一个额外的层&使用命名注册和作用域生命周期来确定null和日志实现是否被传递。
  2. 将服务提供商注入到面向公众的服务IRetailerService中,并以某种方式传递正确的实现。

我认为我理想的解决方案是某种类型的装饰器/中间件来控制这个…我只给出了核心库代码;但是在解决方案中也有一个WebApi项目引用了这个库。如有任何意见或指导,我将不胜感激。

我建议使用工厂来创建订单服务,以及任何需要日志记录器的下游依赖项。下面是一个完整的示例:

void Main()
{
var serviceProvider = new ServiceCollection()
.AddScoped<IRetailerService, RetailerService>()
.AddScoped<IInventoryManager, InventoryManager>()
.AddScoped<IOrderServiceFactory, OrderServiceFactory>()
.BuildServiceProvider();
var retailerService = serviceProvider.GetRequiredService<IRetailerService>();
Console.WriteLine("Running without user");
retailerService.Execute(Guid.NewGuid());

Console.WriteLine("Running with user");
retailerService.Execute(Guid.NewGuid(), new User());
}
public enum OrderMode
{
WithUser,
WithoutUser
}
public interface IOrderServiceFactory
{
IOrderService Get(OrderMode mode);
}
public class OrderServiceFactory : IOrderServiceFactory
{
private readonly IServiceProvider _provider;
public OrderServiceFactory(IServiceProvider provider)
{
_provider = provider;
}
public IOrderService Get(OrderMode mode)
{
// Create the right sort of order service - resolve dependencies either by new-ing them up (if they need the 
// logger) or by asking the service provider (if they don't need the logger).
return mode switch
{
OrderMode.WithUser => new OrderService(new UserLogger(), _provider.GetRequiredService<IInventoryManager>()),
OrderMode.WithoutUser => new OrderService(new NullLogger(), _provider.GetRequiredService<IInventoryManager>())
};
}
}
public interface IRetailerService
{
Task Execute(Guid id);
Task Execute(Guid id, User user);
}
public interface IOrderService
{
Task Get(Guid id);
Task Get(Guid id, User user);
}
public class User { }
public class RetailerService : IRetailerService
{
private readonly IOrderServiceFactory _orderServiceFactory;
public RetailerService(
IOrderServiceFactory orderServiceFactory)
{
_orderServiceFactory = orderServiceFactory;
}
async Task IRetailerService.Execute(Guid id)
{
var orderService = _orderServiceFactory.Get(OrderMode.WithoutUser);
await orderService.Get(id);
}
async Task IRetailerService.Execute(Guid id, User user)
{
var orderService = _orderServiceFactory.Get(OrderMode.WithUser);
await orderService.Get(id, user);
}
}
public interface ISpecialLogger
{
public void Log(string message);
}
public class UserLogger : ISpecialLogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
public class NullLogger : ISpecialLogger
{
public void Log(string message)
{
// Do nothing.
}
}
public interface IInventoryManager { }
public class InventoryManager : IInventoryManager { }
internal class OrderService : IOrderService
{
private readonly ISpecialLogger _logger;
public OrderService(ISpecialLogger logger, IInventoryManager inventoryManager)
{
_logger = logger;
}
public async Task Get(Guid id)
{
_logger.Log("This is the 'id-only' method");
}
public async Task Get(Guid id, User user)
{
_logger.Log("This is the 'id-and-user' method");
}
}

使用它,您将得到以下输出:

Running without user
Running with user
This is the 'id-and-user' method

工厂允许您完全控制下游组件的生成方式,因此您可以想要多复杂就有多复杂。

您可以在运行时解析IOrderService.Get方法中的依赖项,以便每个方法都有自己的依赖项。然而,这并不能完全解决你的问题。嵌套依赖IInventoryManager inventoryManager, IProductRespository productRepo, ...也应该能够启用日志记录。

所以你可以用:

internal class OrderService : IOrderService
{
public OrderService( IServiceProvider serviceProvider) { }
async Task<object> IOrderService.Get( Guid id )
{
var inventoryManager = (IInventoryManager)serviceProvider.GetService(typeof(IInventoryManager));
inventoryManager.Logging = false;
var productRepo = (IProductRespository)serviceProvider.GetService(typeof(IProductRespository));
productRepo.Logging = false;
//... do stuff with the injected components
await inventoryManager.Execute( ...args );
await productRepo.Execute( ...args );
}
async Task<object> IOrderService.Get( Guid id, User user ) {
var inventoryManager = (IInventoryManager)serviceProvider.GetService(typeof(IInventoryManager));
inventoryManager.Logging = false;
var productRepo = (IProductRespository)serviceProvider.GetService(typeof(IProductRespository));
productRepo.Logging = true;
//... do stuff with the injected components
await inventoryManager.Execute( ...args );
await productRepo.Execute( ...args );
}
}

你也可以提供一个带有参数的Factory/Builder来启用日志记录。但是在任何情况下,因为您希望从同一个根类开始的嵌套类具有不同的行为,这可能会很复杂。

另一个选项是提供IOrderService的两个实现,一个包含日志记录,另一个不包含。但我不确定这是否对您有帮助,因为您可能有很好的理由为方法提供重载,而不是将它们拆分为单独的服务。这并不能解决嵌套注入的问题。

最后一个选项可能是使用单例LoggingOptions类。每个依赖都有一个依赖于这个类,因为这是一个单例,每次你进入你的重载,你设置它为true,所以所有的类都被告知你的意图记录。然而,这在很大程度上取决于您的架构。如果这两个方法可能几乎同时被调用,这可能会破坏嵌套的依赖项日志记录行为或随时中断日志记录。

看看这个问题,这可能会有帮助。通过考虑这个问题,您可以为每个依赖项(包括嵌套的依赖项)提供一个工厂,该工厂将在每次调用重载方法时设置日志记录行为。

相关内容

  • 没有找到相关文章

最新更新