存储库模式是如何真正完成的?

how is repository pattern really done?

我在 asp.net 核心网站 api 中使用弹性搜索。当涉及到存储库责任时,我不太明白界限在哪里。

下面是我定义我的实现的方式:

public SearchRespository: ISearchRespository<Product>
{
   private ElasticClient _client

   public async Task<ISearchResponse<Product>> SearchAsync(ISearchRequest request)
    {
        var response = _client.SearchAsync<Product>(request);
        return await products;
    }        

    . . . // others
}        

在我的控制器中:

public SearchController : Controller
{
    private ISearchRespository _repo;

    public SearchController(ISearchRespository repo)
    {
       _repo = repo;
    }

    public async Task<IActionResult> Search()
    {
       // build my search request from Request.Query

       var response = await _client.SearchAsync(request);
       var model = new SearchModel
       {
           Products = response.Documents;
           Aggregations = response.Aggregations;
       }

       return Ok(model)
}

就目前情况而言,回购正在按原样传递弹性响应。我的问题是我画的线对吗?如果我只是将 _client 移动到我的控制器或将构建请求和构建 model 移动到 _repo 会怎么样?你们如何使您的存储库正确?

您使用 Elastic Search 的事实应该是一个实现细节,尤其是控制器不应该知道,因此您将其从控制器中抽象出来是绝对正确的。我经常查看 SOLID 原则以了解我是否在正确的轨道上。如果我们查看连接到该第三方的 Dependency Inversion Principle, you'll see that it guides us towards a style that is also known as Ports and Adapters, which basically means that the use of an external tool is abstracted away (the port), and on the boundary of the application you implement an Adapter

所以从依赖倒置原则的意义上来说,你是在正确的轨道上。

然而,对于 Martin Fowler's Repository Pattern 试图解决的问题存在很多误解。定义如下:

Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.

这里需要注意的重要一点是,存储库旨在供域层使用。

然而,存储库模式存在很多误用,因为许多开发人员开始将其用作查询的分组结构。正如我所见,该存储库并不适用于系统中的所有查询;但仅适用于 domain 需要的查询。这些查询支持域为系统上的突变做出决策。

然而,您的系统需要的大多数查询都不是这种查询。您的代码就是一个很好的例子,因为在这种情况下,您完全跳过域,只执行读取操作。

这是不适合存储库的东西。我们可以通过再次与 SOLID 原则进行比较来验证这一点。

假设我们有以下存储库接口:

public interface IUserRepository
{
    User[] FindUsersBySearchText(string searchText, bool includeInactiveUsers);
    User[] GetUsersByRoles(string[] roles);
    UserInfo[] GetHighUsageUsers(int reqsPerDayThreshold);
    // More methods here
}

这是您将看到开发人员编写的典型存储库抽象。从 SOLID 原则的角度来看,这种抽象是有问题的,因为:

  • 违反了接口隔离原则,因为接口范围很广(有很多方法),这些接口的使用者被迫依赖于他们不使用的方法。
  • 违反了单一职责原则,因为存储库实现中的方法不是高度内聚的。唯一与这些方法相关的是它们属于同一个概念或实体。
  • 该设计违反了Open/Closed原则,因为几乎每次向系统添加查询时,都需要更改现有接口及其实现。每个接口至少有两种实现:一种是真实实现,一种是测试实现。

像这样的设计也会带来很多麻烦,因为很难应用 cross-cutting 问题(如安全、审计、日志记录、缓存等)。

所以这不是 Repository 模式旨在解决的问题;这样的设计只是严重违反 SOLID。

这里的解决方案是在您的系统中单独建模查询,而不使用存储库根本。有很多关于此的文章,您可以阅读我对此的看法 here

如果我看一下您的设计,它实际上与我在这里推广的设计有一些相似之处,因为您似乎有一个通用的查询方法可以处理多种类型的查询。然而,查询消息(您的 ISearchRequest)似乎特定于 Elastic Search。正如依赖倒置原则所述,这是您应该努力避免的事情。