如何以正确的方式实现存储库模式?

How to implement the repository pattern the right way?

在为我的 ASP.NET 项目实现存储库模式时,我遇到了一些无法解决的问题。所以我有几个关于如何正确实现存储库模式的问题。

根据我的经验,我认为 classes/models 仅在我的应用程序中没有行为,紧挨着他们的存储库不是好的 OOP。但是,这就是我实现存储库模式的方式。我只是在任何需要存储库实例的地方制作,以执行一些操作。这种方法的结果是我的所有域 classes 都没有行为。

它们只是没有方法的保存数据的对象。我的老师对我说,我用的是瘦模型,我应该努力做胖模型。 作为对该反馈的回应,我在 classes 中实现了一些业务逻辑,但我 运行 遇到了一些问题:

场景:

我的 User class 有一个朋友列表,里面有用户对象,代表某个用户的朋友。向用户添加新朋友时,域 class 方法会检查 "friend" 是否已存在于朋友列表中。如果没有,他将被添加到列表中。这些更改需要发送到数据库以进行持久化。

我知道这必须在每个域的存储库中完成 class,但是对存储库方法的调用属于应用程序架构中的什么地方?

现在我在域 class 方法本身中调用存储库方法,以持久保存对数据库的更改:

public void AddFriend(User friend)
    {
        foreach(User f in Friends)
        {
            if(f.Username == friend.Username)
            {
                throw new Exception(String.Format("{0} is already a friend.", friend.Username));
            }
        }

        Friends.Add(friend);
        userRepo.AddFriend(this.Id, friend.Id);
    }

这是一个好方法吗,出于某种原因我认为它不是。关于单元测试,我们需要使用这种方法进行一些依赖注入,这对我来说意味着它不是一个独立的 class(良好的可测试单元)。我读过一些人的帖子,说他们使用额外的服务层或其他东西。我认为这里需要这种抽象,但是某个服务层是什么样的?里面有什么,有哪些方法等?

我看到其他一些学生在域中使用静态方法class,它提供了添加新对象、更新对象、删除对象和获取所有对象等功能。

示例:

public class Tram
{
    private static TramRepository Repo = new TramRepository(new DBTram());

    public static void AddTram(int tramID, TramType type, int lineNr)
    {
        Tram tram = new Tram(tramID, type, TramStatus.depot, lineNr, true, null);
        Repo.AddTram(tram);
    }

    public static List<Tram> GetAll()
    { 
        Repo.GetAll();
    }
}

我发现在域 class 中向数据库添加新实体的方法很奇怪,这就是该实体本身。同样对于 GetAll() 方法,我认为在 class 本身中有一个获取所有电车的方法很奇怪。所以一个电车对象可以得到所有的电车。我认为这是实现存储库模式的一种奇怪方式。我说得对吗?

那么,这里需要什么样的抽象?是否必须有一个额外的层?如果是这样,这一层是什么样子的? (示例代码)或者我搜索的方向错误,是否有另一种解决方案可以解决存储库模式的单元测试问题?

这个架构问题我每次都遇到,一定要回答。

这个问题很难解释清楚,希望大家理解。

The result of this approach was that all my domain classes literally had no behaviour. They were just objects which holds data with no methods.

  1. Is it good OOP to have an application that relies on an architecture that, next to their repositories, only has models/classes that hold values with no behaviour?

不,当然不是,但这就是当今网络开发人员的教育方式。

它回到了 1970 年代的旧模块化编程风格(静态 类 + 对象中的数据)。
模块化编程导致了很多问题,所以他们发明了封装和 object-oriented 语言来解决这些问题。

制作所有数据public只要你做的是简单的网站就可以了:

  • 加载数据
  • 显示数据
  • 保存数据

如果您需要复杂的行为,问题就开始了 - 那么您的数据实际上是软件实现的一部分,而您做到了 public!当有多个开发人员在同一个项目上工作时,这会导致严重的维护问题。

没有教 Web 开发人员 OOP。他们正在学习一种简单的编程方式 - 将他们引导到简单的网站(这是政府需要的)。

你的问题是绝对正常的,但不要指望找到绝对的答案。欢迎来到软件行业!

这是我的看法:

  1. Is it good OOP to have an application that relies on an architecture that, next to their repositories, only has models/classes that hold values with no behaviour?

我认为您尝试实现存储库模式,但您错过了更高的架构视图。大多数应用程序至少在 3 层中解耦:视图(演示)、业务和数据访问。 存储库模式发生在 DataAccess 中,这是您可以找到纯数据对象的地方。 但是这个数据访问层被业务层使用,你会在其中找到一个域模型,classes 具有业务行为和数据。 单元测试工作必须在业务层的域模型上进行。 这些测试不应该关心数据是如何存储的。

  1. Where do I call repository methods in the architecture of the application?

同样没有绝对的答案,但通常使用像业务服务这样的东西是有意义的。这些服务可以安排不同域对象之间的流,并将它们加载并保存在存储库中。 这基本上就是您在 AddFriend class 中所做的,它属于业务层。

Regarding to unit testing, we need with this approach some dependancy injection, which says to me that it is not a independant class

业务服务通常依赖于存储库,这是单元测试的一个非常常见的情况。 Tram class 可以持有业务行为,仍然是独立的。 AddTram 业务服务需要一个存储库,依赖注入允许对其进行测试。

  1. Methods that insert, update and delete new entities in a database, are they supposed to be in a class itself?

对于这一点,我可以大声说清楚:请不要那样做,是的,CRUD 操作属于 Tram Repository。这当然不是业务逻辑。这就是为什么在你的例子中你需要两个 classes 在两个不同的层:

  • Tram可以是业务层的业务对象(这里没有Crud操作)
  • TramRepository 是需要存储电车数据的对象(您将在其中找到 CRUD 操作)

"Because i have seen some other students making use of static methods in a domain class which provides these functionality"

使用静态方法显然不是一个好主意,这意味着任何人都可以通过您的对象存储数据,即使它应该处理业务案例。再说一次,数据存储不是商业案例,而是技术必需品。

现在公平地说,所有这些概念都需要在上下文中进行讨论才能有意义,并且需要根据每个新项目进行调整。这就是为什么你的工作既艰巨又令人兴奋:背景为王。

我也写了一个blog article以MVVM为中心,但我认为它可以帮助理解我的答案。