C# 设计模式:使用IoC设置控制器、服务、存储库和UnitOfWork

C# 设计模式:使用IoC设置控制器、服务、存储库和UnitOfWork,c#,design-patterns,dependency-injection,inversion-of-control,unit-of-work,C#,Design Patterns,Dependency Injection,Inversion Of Control,Unit Of Work,想象一下,我有一个汽车租赁店的服务 我让CarsController在其唯一的构造函数中接受ICarService,让CarService在其唯一的构造函数中接受IUnitOfWork。IUnitOfWork具有ICarsRepository、IUsersRepository和ILogsRepository的3个单例只读属性以及一个提交方法。依赖关系通过任何合适的依赖注入容器(ninject、unity等)来解决,EF是底层ORM 我在几乎所有的应用程序中都使用这种体系结构。我不时遇到一个挑战:

想象一下,我有一个汽车租赁店的服务

我让CarsController在其唯一的构造函数中接受ICarService,让CarService在其唯一的构造函数中接受IUnitOfWork。IUnitOfWork具有ICarsRepository、IUsersRepository和ILogsRepository的3个单例只读属性以及一个提交方法。依赖关系通过任何合适的依赖注入容器(ninject、unity等)来解决,EF是底层ORM

我在几乎所有的应用程序中都使用这种体系结构。我不时遇到一个挑战:

在我的CarsController中有一个名为
RentCar(int carId,int userId)
的方法。CarsController在CarsService上调用RentCar(int carId,int userId)。CarsService需要在调用CarRepository方法之前和之后执行一些业务逻辑。例如,这种“某些业务逻辑”可以是验证用户是否有效,以及保存一些日志。由于我的IUnitOfWork允许我访问IUsersRepository和ILogsRepository,因此我可以非常轻松地与所有存储库交互,并在完成IUnitOfWork后调用commit

但是,我将编写的代码用于获取用户,然后对其进行验证,并将事件记录在DB中,可能已经存在于IUserSerive和ILogsService中。此时,我觉得应该在CarsService中使用这些服务,以避免任何逻辑重复

问题:

从服务中访问其他服务是一个好主意吗?如果是:

是否应该通过构造函数将所有依赖服务单独传递到服务中,或者是否存在类似UnitOfWork的模式,其中所有服务都可以通过只读单例属性访问

所有的服务方法最终都会在IUnitOfWork上调用Commit。因此,如果我确实访问了服务中的服务,我可能会在原始调用服务完成其工作之前调用Commit


如果我不应该在服务中调用服务,那么上面的逻辑复制场景如何?

您在这里描述的是交叉关注点的使用。验证、授权和日志记录不是业务问题,它们关心交叉问题。因此,您不想在添加此代码时污染您的业务层,并且希望避免到处重复大量代码

这个问题的解决方案是将横切关注点转移到装饰器,并将它们应用到业务逻辑服务中。现在的问题当然是,您不希望必须为每个服务定义一个装饰器,因为这将再次导致大量代码重复

因此,解决这个问题的办法是转移到。换句话说,为系统中的任何业务事务定义一个通用抽象,例如:

public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}
public class RentCarCommand
{
    public int CarId { get; set; }
    public int UserId { get; set; }
}
public class RentCarCommandHandler : ICommandHandler<RentCarCommand>
{
    private readonly IUnitOfWork uow;

    public RentCarCommandHandler(IUnitOfWork uow) 
    {
        this.uow = uow;
    }

    public void Handle(RentCarCommand command)
    {
        // Business logic of your old CarsService.RentCar method here.
    }
}
对于每个命令,您需要编写一个特定的
ICommandHandler
实现。例如:

public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}
public class RentCarCommand
{
    public int CarId { get; set; }
    public int UserId { get; set; }
}
public class RentCarCommandHandler : ICommandHandler<RentCarCommand>
{
    private readonly IUnitOfWork uow;

    public RentCarCommandHandler(IUnitOfWork uow) 
    {
        this.uow = uow;
    }

    public void Handle(RentCarCommand command)
    {
        // Business logic of your old CarsService.RentCar method here.
    }
}
现在,您可能开始思考为什么我们需要所有这些额外的“复杂性”,但我认为我们降低了系统的复杂性,因为我们现在只剩下一个抽象
ICommandHandler
。此外,考虑添加横切关注点的问题。这一点现在已经完全消失了,因为我们可以为交叉关注点(如验证)创建装饰器:

public class ValidationCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> 
{
    private readonly IValidator validator;
    private readonly ICommandHandler<TCommand> handler;

    public ValidationCommandHandlerDecorator(IValidator validator, 
        ICommandHandler<TCommand> handler)
    {
        this.validator = validator;
        this.handler = handler;
    }

    void ICommandHandler<TCommand>.Handle(TCommand command) 
    {
        // validate the supplied command (throws when invalid).
        this.validator.ValidateObject(command);

        // forward the (valid) command to the real
        // command handler.
        this.handler.Handle(command);
    }
}

当然,手动将此应用于系统中的每个命令处理程序会变得相当麻烦,但这正是DI库可以派上用场的地方。如何做到这一点取决于您使用的库。

谢谢Steven,非常感谢您的详细回复。我想我今天学到了一些非常有用的东西,这些东西将伴随我很长时间。难道我永远都不应该这样做,总是寻找其他的方法吗?我的意思是,我给出的示例是关于日志记录和验证的,但它可能是其他的东西。或者如果我遇到这种情况,这是不是意味着我做错了什么?@aDev:命令处理程序实现可能仍然会将工作委托给系统的其他部分,所以这很好。您可以将服务注入到处理程序的构造函数中。我所阻止的是让处理程序直接或间接地依赖于其他命令处理程序,因为这会使系统复杂化。在这种情况下,您应该简单地将公共逻辑从处理程序提取到服务中,并将该服务注入到所有需要它的处理程序中。谢谢我现在就试一下。我仍然认为我会被在每个服务的方法末尾调用的commit方法所困扰。例如,如果ServiceA有MethodA,在方法B完成时调用ServiceB的MethodB,它将对uow执行Commit()。但这不应该发生,因为MethodA还有几件事要完成,一旦完成,它将再次尝试在uow上调用Commit()。(如果你能解决这个问题,那就太好了,否则我会再次单独问:))。非常感谢您的帮助。@aDev:您应该做的是让一个装饰程序调用Submit()。这使您的服务无需调用它,并允许整个操作成为单个事务。