您在这里描述的是横切关注点的使用。验证、授权和日志记录不是业务问题,它们关心横切问题。所以你不想在添加这个的时候污染你的业务层,并且你想避免在整个地方做大量的代码重复。
解决这个问题的方法是将横切关注点转移到装饰器,并将它们应用到您的业务逻辑服务中。现在的问题当然是您不想为每个服务定义一个装饰器,因为这会再次导致大量代码重复。
因此,解决方案是移至command/handler pattern。换句话说,为系统中的任何业务事务定义一个通用抽象,例如:
public interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
并为每个操作定义一个“命令”DTO/消息对象,例如:
public class RentCarCommand
{
public int CarId { get; set; }
public int UserId { get; set; }
}
对于每个命令,您需要编写特定的ICommandHandler<T> 实现。例如:
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.
}
}
这个RentCarCommandHandler 替换了CarsService.RentCar 方法。如果CarsService 有多个方法,则每个方法将有 1 个命令 + 1 个命令处理程序。
现在您的控制器可以依赖ICommandHandler<RentCarCommand> 而不是ICarsService:
public class CarsController : Controller
{
private readonly ICommandHandler<RentCarCommand> rentCarHandler;
public CarsController(ICommandHandler<RentCarCommand> rentCarHandler)
{
this.rentCarHandler = rentCarHandler;
}
public ActionResult Index(int carId, int userId)
{
if (this.ModelState.IsValid)
{
var command = new RentCarCommand { CarId = carId, UserId = userId };
this.rentCarHandler.Handle(command);
}
// etc.
}
}
现在您可能开始思考为什么我们需要所有这些额外的“复杂性”,但我认为我们降低了系统的复杂性,因为我们现在只剩下一个抽象 ICommandHandler<T>。此外,考虑添加横切关注点的问题。现在完全没有了,因为我们可以为诸如验证之类的横切关注点创建装饰器:
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);
}
}
现在您可以将 DataAnnotation 的属性应用于命令属性,此验证器将确保任何命令都经过验证。
或者一些做一些审计跟踪的装饰器:
public class AuditTrailingCommandHandlerDecorator<TCommand>: ICommandHandler<TCommand>
{
private readonly IAuditTrailRepository repository;
private readonly ICommandHandler<TCommand> handler;
public LoggingCommandHandlerDecorator(
IAuditTrailRepository repository,
ICommandHandler<TCommand> handler)
{
this.logger = logger;
this.handler = handler;
}
void ICommandHandler<TCommand>.Handle(TCommand command)
{
string json = JsonConverter.Serialize(command);
this.repository.AppendToTrail(typeof(TCommand), json);
this.handler.Handle(command);
}
}
由于命令是简单的数据包,我们现在将它们序列化为 JSON,这通常足以用于审计跟踪。你当然可以对日志做同样的事情。
您可以按如下方式装饰您的RentCarCommandHandler:
ICommandHandler<RentCarCommand> handler =
new AuditTrailingCommandHandlerDecorator<RentCarCommand>(
new AuditTrailRepository(uow),
new ValidationCommandHandlerDecorator<RentCarCommand>(
new Validator(),
RentCarCommandHandler(uow)));
当然,手动将其应用于系统中的每个命令处理程序会变得非常麻烦,但这是 DI 库可以派上用场的地方。如何执行此操作取决于您使用的库。