在工作单元模式中重用服务调用

reusing services calls in unit of work pattern

我有一个使用 WebApi、通用存储库、EF6 和工作单元模式的场景 (为了将来自多次调用的所有更改包装到同一上下文。)

管理层用于执行对不同存储库以及其他管理器的调用。

目前 Customer Manager 确实注入了回购和其他经理,例如:

public class CustomerManager  {
    public CustomerManager(IRepository<Customer> _customerRepository, IRepository<Order> orderRepository, IManager itemManager) {
        _orderReporsitory = orderReporsitory;
        _itemManager = itemManager;
        _customerRepository = customerRepository;
}

    public bool Save(Customer customer) {
        _orderReporsitory.Find...
        _itemManager.IsItemUnique(ItemId)
        _customerRepository.Save(customer);
    }
}

This code does not compile, for reference only.

像这样的方法

http://blog.longle.net/2013/05/11/genericizing-the-unit-of-work-pattern-repository-pattern-with-entity-framework-in-mvc/

将几个存储库包装在一个工作单元下,并将所有更改一起刷新。

我的问题还涉及添加另一个管理层,也被包装在工作单元中,并且允许调用存储库和其他管理器 (因为我想重用一些管理器逻辑。就像在示例中一样,我正在重用一些 ItemManager 逻辑)

此代码

using (var uow = new UnitOfWork<CompanyContext>())
{
  var catService = new Services.CategoryService(uow);
  var custService = new Services.CustomerService(uow);

  var cat = new Model.Category { Name = catName };
  catService.Add(dep);

  custService.Add(new Model.Customer { Name = custName, Category = cat });

  uow.Save();
}

正在使用与我需要的类似的东西,但我也希望能够注入服务以对它们进行单元测试(而不是在我的 manager/service 方法的主体中创建实例)

执行此操作的最佳方法是什么?

谢谢

您的带有工作单元的代码片段有几个问题,例如:

  • 您在该方法中显式创建和处理工作单元,迫使您将该工作单元从一个方法传递到另一个方法,并从 class 传递到 class。
  • 这会导致您违反 Dependency Inversion Principle,因为您现在依赖于具体类型(CategoryServiceCustomerService),这会使您的代码复杂化并使您的代码更难测试。
  • 如果您需要更改工作单元的创建、管理或处置方式,则必须对整个应用程序进行彻底的更改;违反了 Open/Closed Principle.

我在this answer中更详细地表达了这些问题。

相反,我建议有一个 DbContext,通过完整的请求共享它,并控制它在应用程序基础结构中的生命周期,而不是在整个代码库中显式地控制它。

一种非常有效的方法是将您的服务层置于通用抽象之后。虽然这个抽象的名字无关紧要,但我通常称这个抽象为'command handler:

public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}

关于这个抽象有一些有趣的事情:

  1. 抽象描述了一种服务操作或用例。
  2. 操作可能具有的任何参数都包含在单个消息(命令)中。
  3. 每个操作都有自己独特的命令class。

例如,您的 CustomerManager 可能如下所示:

[Permission(Permissions.ManageCustomerDetails)]
public class UpdateCustomerDetailsCommand {
    public Guid CustomerId { get; set; }
    [Required] public string FirstName { get; set; }
    [Required] public string LastName { get; set; }
    [ValidBirthDate] public DateTime DateOfBirth { get; set; }
}

public class UpdateCustomerDetailsCommandHandler
    : ICommandHandler<UpdateCustomerDetailsCommand> {

    public UpdateCustomerDetailsCommandHandler(
        IRepository<Customer> _customerRepository, 
        IRepository<Order> orderRepository, 
        IManager itemManager) {
        _orderReporsitory = orderReporsitory;
        _itemManager = itemManager;
        _customerRepository = customerRepository;
    }

    public void Handle(UpdateCustomerDetailsCommand command) {
        var customer = _customerRepository.GetById(command.CustomerId);
        customer.FirstName = command.FirstName;
        customer.LastName = command.LastName;
        customer.DateOfBirth = command.DateOfBirth;
    }
}

这可能看起来只是一堆额外的代码,但是有了这个消息和这个通用抽象,我们就可以轻松地应用横切关注点,例如处理工作单元:

public class CommitUnitOfWorkCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand> {

    private readonly IUnitOfWork unitOfWork;
    private readonly ICommandHandler<TCommand> decoratee;

    public CommitUnitOfWorkCommandHandlerDecorator(
        IUnitOfWork unitOfWork,
        ICommandHandler<TCommand> decoratee) {
        this.unitOfWork = unitOfWork;
        this.decoratee = decoratee;
    }

    public void Handle(TCommand command) {
        this.decoratee.Handle(command);
        this.unitOfWork.SaveChanges();
    }
}

上面的 class 是一个装饰器:它既实现了 ICommandHandler<TCommand> 又包装了 ICommandHandler<TCommand>。这允许您围绕每个命令处理程序实现包装此装饰器的实例,并允许系统透明地保存在工作单元中所做的更改,而无需任何代码段明确地执行此操作。

也可以在这里创建一个新的工作单元,但最简单的开始是让工作单元在(网络)请求期间一直存在。

然而,这个装饰器只是您可以使用装饰器做的事情的开始。例如,这将是微不足道的:

  • 应用安全检查
  • 执行用户输入验证
  • 运行事务中的操作
  • 应用死锁重试机制。
  • 通过重复数据删除防止重新发布。
  • 在审计跟踪中注册每个操作。
  • 存储用于排队或后台处理的命令。

可以在文章中找到更多信息,here, here and here