仓库层和服务层有什么区别?

230

在面向对象设计模式中,仓储模式(Repository Pattern)和服务层(Service Layer)有何区别?

我正在开发一个ASP.NET MVC 3应用程序,并尝试理解这些设计模式,但我的大脑还没有完全理解它们!

5个回答

374

仓库层为数据访问提供了额外的抽象级别。而不是编写

var context = new DatabaseContext();
return CreateObjectQuery<Type>().Where(t => t.ID == param).First();

要从数据库中获取单个项目,您可以使用存储库接口

public interface IRepository<T>
{
    IQueryable<T> List();
    bool Create(T item);
    bool Delete(int id);
    T Get(int id);
    bool SaveChanges();
}

并调用 Get(id)。仓储层暴露基本的CRUD操作。

服务层暴露业务逻辑,使用仓储层。例如,服务可能如下所示:

public interface IUserService
{
    User GetByUserName(string userName);
    string GetUserNameByEmail(string email);
    bool EditBasicUserData(User user);
    User GetUserByID(int id);
    bool DeleteUser(int id);
    IQueryable<User> ListUsers();
    bool ChangePassword(string userName, string newPassword);
    bool SendPasswordReminder(string userName);
    bool RegisterNewUser(RegisterNewUserModel model);
}

虽然 repository 的 List() 方法返回所有用户,但 IUserService 的 ListUsers() 可能仅返回该用户可以访问的用户。

在 ASP.NET MVC + EF + SQL SERVER 中,我有这样的通信流程:

Views <- Controllers -> Service 层 -> Repository 层 -> EF -> SQL Server

Service 层 -> Repository 层 -> EF 这一部分操作模型。

Views <- Controllers -> Service 层 这一部分操作视图模型。

编辑:

以 /Orders/ByClient/5 为例(我们想查看特定客户的订单),其流程如下:

public class OrderController
{
    private IOrderService _orderService;

    public OrderController(IOrderService orderService)
    {
        _orderService = orderService; // injected by IOC container
    }

    public ActionResult ByClient(int id)
    {
        var model = _orderService.GetByClient(id);
        return View(model); 
    }
}

这是订单服务的界面:
public interface IOrderService
{
    OrdersByClientViewModel GetByClient(int id);
}

这个接口返回视图模型:

public class OrdersByClientViewModel
{
     CientViewModel Client { get; set; } //instead of ClientView, in simple project EF Client class could be used
     IEnumerable<OrderViewModel> Orders { get; set; }
}

这是接口实现。它使用模型类和仓库来创建视图模型:

public class OrderService : IOrderService
{
     IRepository<Client> _clientRepository;
     public OrderService(IRepository<Client> clientRepository)
     {
         _clientRepository = clientRepository; //injected
     }

     public OrdersByClientViewModel GetByClient(int id)
     {
         return _clientRepository.Get(id).Select(c => 
             new OrdersByClientViewModel 
             {
                 Cient = new ClientViewModel { ...init with values from c...}
                 Orders = c.Orders.Select(o => new OrderViewModel { ...init with values from o...}     
             }
         );
     }
}

2
@Sam Striano:正如您在上面所看到的那样,我的IRepository返回IQueryable。这允许在服务层中添加其他where条件和延迟执行,而不是在以后执行。是的,我使用一个程序集,但所有这些类都放置在不同的命名空间中。在小项目中没有创建多个程序集的理由。命名空间和文件夹分离效果很好。 - LukLed
94
为什么在服务中返回视图模型?难道服务不应该模拟多个客户端(移动/网页)的情况吗?如果是这样,那么视图模型可能因不同平台而异。 - Ryan
15
同意 @Ryan 的观点,服务层应该返回实体对象或实体对象集合(而不是 IQueryable)。然后在用户界面上,可以使用 Automapper 对实体进行映射到 SomeViewModel 等视图模型。 - Eldar
6
您不需要为每个实体创建存储库。您可以使用通用实现,并在您的IOC库中将IRepository<>绑定到GenericRepository<>。该答案非常古老。我认为最好的解决方案是将所有存储库组合成一个名为“UnitOfWork”的类。它应包含每种类型的存储库和一个名为“SaveChanges”的方法。所有存储库都应共享一个EF上下文。 - LukLed
2
@LukLed 我认为最好的解决方案是将所有存储库合并到一个名为UnitOfWork的类中。为什么这样做?EF的DbContext已经是一个UOW了。 - Halter
显示剩余35条评论

54
如Carnotaurus所说,仓库负责将您的数据从存储格式映射到业务对象。它应该处理从存储读取和写入数据(包括删除、更新)的方式。
另一方面,服务层的目的是将业务逻辑封装在一个地方以促进代码重用和关注点分离。在实践中,当构建Asp.net MVC站点时,我通常使用以下结构:[控制器]调用[服务],服务再调用[仓库]。
我发现的一个有用原则是尽量减少控制器和仓库中的逻辑。控制器中之所以这样做是因为它有助于让我的代码更干净。很常见的情况是,我需要在其他地方使用相同的过滤或逻辑,如果我将其放在控制器中,我就无法重用它。
而在仓库中这么做是因为我想在更好的解决方案出现时能够替换我的存储(或ORM)。如果在仓库中有逻辑,我需要在更改存储库时重新编写此逻辑。但如果我的存储库仅返回IQueryable,则服务可以执行过滤,并且我只需要替换映射即可。
例如,最近我用EF4替换了我的Linq-To-Sql存储库,对于那些遵循这个原则的存储库,可以在几分钟内替换。而对于其他存储库,则需要花费数小时的时间进行更改。

我同意你的观点,Mikael。事实上,我在我的技术博客http://www.freecodebase.com中应用了相同的场景,并在这个实现中使用了代码优先的方法。源代码也可以在这里下载。 - Toffee
我一直在研究如何在现有的MVC应用程序中应用存储库模式。这是一个定制的框架,具有类似Active Record的ORM和其他Rails / Laravel约定,并且对我现在正在进行的工作存在一些架构问题。我发现的一件事是,存储库“不应返回ViewModels、DTO或查询对象”,而应该返回存储库对象。我正在思考服务通过onBeforeBuildBrowseQuery等方法与存储库对象交互,并可以使用查询构建器来更改查询的位置。 - webstackdev
@Toffee,您的链接已经失效了,能否请您更新一下?我需要这个实现的源代码。 - Hamza Khanzada

46

被接受的答案(并得到数百个点赞)存在一个主要缺陷。我想在评论中指出这一点,但它只会在30多条评论中被埋没,所以我在这里指出。

我接手了一个企业应用程序,它就是这样构建的,我的初始反应是“什么鬼”?ViewModel放在服务层里?我不想改变约定,因为已经进行了多年的开发,所以我继续返回ViewModel。但当我们开始使用WPF后,它变成了噩梦。我们(团队开发人员)总是说:“哪个ViewModel?真正的那个(我们为WPF编写的那个),还是服务层的那个?”它们是为Web应用程序编写的,甚至有一个IsReadOnly标志来禁用UI中的编辑。由于一个词,即ViewModel,造成了重大的缺陷和问题!!

在您犯同样的错误之前,以下是除了我上面的故事之外的一些更多原因:

从服务层返回ViewModel是一件非常不好的事情。这就像说:

  1. 如果你想使用这些服务,你最好使用MVVM,并且这是你需要使用的ViewModel。糟糕!

  2. 服务正在假设它们将在某个UI中显示。如果它被非UI应用程序(例如Web服务或Windows服务)使用会怎样呢?

  • 那甚至不是一个真正的视图模型。一个真正的视图模型具有可观察性、命令等功能。那只是一个名字不好的POCO。(请看上文中我提到名字的原因。)

  • 消费应用程序最好是一个表示层(视图模型由该层使用),并且最好理解C#。又是一次痛苦!

  • 请不要这样做!


    7
    虽然我知道这并没有对讨论有所帮助,但我还是想评论一下:“那只是一个取名不好的POCO。”<<这句话印在T恤上会很不错!:) :) - Mephisztoe

    13

    仓储层用于访问数据库,并帮助扩展对数据库的CRUD操作。而服务层包含应用程序的业务逻辑,可以使用仓储层来实现涉及数据库的某些逻辑。在应用程序中,最好有单独的仓储层和服务层。拥有独立的仓储层和服务层使代码更具模块化,将数据库与业务逻辑解耦。


    9

    通常,一个仓库被用作搭建框架来填充实体 - 服务层将会发起请求。很可能你会把仓库放在你的服务层之下。


    在使用EF4的ASP.NET MVC应用程序中,可能是这样的:SQL Server --> EF4 --> 存储库 --> 服务层 --> 模型 --> 控制器和反之亦然? - Sam
    1
    是的,您的存储库可以用于从EF4获取轻量级实体;您的服务层可以用于将它们发送回专门的模型管理器(在您的场景中为Model)。控制器将调用您的专门模型管理器来执行此操作...快速查看我的Mvc 2/3博客。我有图表。 - Phil C
    仅供澄清:在您的情境中,EF4代表我的图表中的模型位置,而在您的情境中,Model是我图表中专门的模型管理器。 - Phil C

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接