以工作单元模式重用服务调用

时间:2015-04-13 20:55:28

标签: c# .net design-patterns dependency-injection unit-of-work

我有一个使用WebApi,Generic Repository,EF6和工作单元模式的场景 (为了将几次调用的所有更改都包装到同一个上下文中。)

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);
    }
}
  

此代码无法编译,仅供参考。

像这样的方法

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

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

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

此代码https://stackoverflow.com/a/15527444/310107

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();
}

正在使用类似我需要的东西,但我也希望能够将服务注入单元测试它们(而不是在我的经理/服务方法体中创建实例)

这样做的最佳方法是什么?

由于

1 个答案:

答案 0 :(得分:2)

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

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

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

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

执行此操作的一种非常有效的方法是将服务层置于通用抽象之后。虽然这个抽象的名称是无关紧要的,但我通常称之为抽象'命令处理程序:

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

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

  1. 抽象描述了一个服务操作或用例。
  2. 操作可能包含的任何参数都包含在单个消息(命令)中。
  3. 每个操作都有自己独特的命令类。
  4. 例如,您的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();
        }
    }
    

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

    也可以在这里创建一个新的工作单元,但最简单的方法是让工作单元在(web)请求期间保持有效。

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

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

    可以在文章hereherehere中找到更多信息。