将存储库模式与文档数据库一起使用是不是有意义?

Posted

技术标签:

【中文标题】将存储库模式与文档数据库一起使用是不是有意义?【英文标题】:Does it make sense to use the repository pattern with a document database?将存储库模式与文档数据库一起使用是否有意义? 【发布时间】:2011-11-21 19:33:19 【问题描述】:

我目前正在试验 MongoDB。我正在从 NHibernate/SQL 思维模式转变,因此最初我实现了一个 存储库模式 用于数据访问。

在我开始使用嵌套文档之前,这一切看起来都很好。现在它开始看起来有点不匹配。但是,我对存储库感到满意,并且喜欢它们提供的抽象、关注点分离和可测试性。

人们是否成功地将存储库模式与文档数据库一起使用?如果不是,您使用什么数据访问方法?那么抽象/SoC呢?

【问题讨论】:

感谢所有对赏金的回答 - 我本来想把它分成几个问题,但由于数量很少,所以不可能。 【参考方案1】:

这是一个有趣的问题。在我使用 MongoDB 时,我选择没有存储库。这是主要的,因为文档数据库被用作读取存储(因此简化了存储在那里的数据)。

我想您必须将考虑剥离回存储库是什么以及您从额外的抽象层中获得什么优势。一个好的设计将具有最少的层数可能

因此,存储库使您对持久性无知,并能够在通用数据上下文中使用工作单元。它还可以提高您针对数据单独测试查询的能力(因为这些通常被抽象为可查询对象或规范)。

另外,一些文档数据库已经提供了存储库模式(RavenDB 等),因此不需要再增加一层。

因此,在我看来,使用存储库与其说是您的数据是存储为关系表还是文档,不如说是您从抽象中获得了什么。

【讨论】:

【参考方案2】:

我不知道这是否会对您有所帮助,但不久前我正在听 pod-cast 与 Ayende Rahien 谈论 RavenDB。他建议一个文档实际上可以很好地映射到 DDD 哲学中的聚合。如果您使用嵌套文档来表示聚合并遇到设计问题,也许嵌套不是最好的方法?

【讨论】:

是的,好点。 Ayende 也认为存储库是一种反模式。他对文档匹配的聚合根可能是正确的,但并不是每个人都对 DDD 充满信心(即使在使用存储库时),而且我似乎经常遇到“只能通过聚合根访问”的灰色区域【参考方案3】:

思考“我应该使用 X 还是不使用”不如专注于“我应该使用什么”高效?

存储库模式有哪些替代方案,有哪些权衡,它们与您的领域和实现有何关系?

存储库非常适合在通用存储(例如 SQL)上实施一组预定义的模式。对于文档存储,我的印象是文档模式将比您通常在基于 SQL 的存储中看到的更大范围确定访问模式。在这种情况下实现存储库可能会导致非常泄漏的抽象,其中对底层文档结构的更改会对相关业务代码产生 1:1 的影响。在这种情况下,存储库提供的价值很小。对我来说,文档存储自然很适合工作单元 (UoW) 范式,其中工作单元是一个文档(或 doc+嵌套的子文档,或文档集)。

正如您所提到的,存储库模式的另一个优势是对存储机制的抽象。权衡通常是失去对 Mongo 的低级实现特定功能的访问权限。这对你来说是一个值得的权衡吗? NHibernate 与 SQL 紧密耦合,因此对 RDBMS 的所有重要特性都有丰富的功能抽象。我不知道 Mongo 有任何类似的框架,所以你真的会提高抽象级别。

您需要支持多个并发数据存储吗?例如,您是否会通过相同的数据层抽象将某些类型的数据写入 SQL 并将其他类型的数据写入 Mongo?如果是这样,那么存储库是一个不错的选择。

如果您可以提供有关您的领域和实施的更多详细信息,那么我们可以深入了解您可能想要考虑的具体权衡

【讨论】:

【参考方案4】:

我在生产代码中使用 MongoDB 和存储库模式已有 2 年多了,我可以说随着时间的推移它确实帮助了我。这些抽象非常适合测试(内存中)和生产(MongoDB)。

我使用 Java,代码看起来像这样(大致):

public interface MyStorage 

    boolean add(MyDoc doc);

    boolean update(MyDoc doc);

    boolean remove(String docId);

    boolean commit();

    MyDoc get(String docId);

    MyStorageQuery newQuery();

    List<MyDoc> query(MyStorageQuery q);


我为每个存储实现创建了 MyDoc 对象的新实例的工厂。 我在 MongoDb 和我自己的手动模拟之间交换实现以进行测试。

MongoDB 实现使用 MyDoc 类来扩展 BasicDBObject,如下所示:

public interface MyDoc 
  Data getData(); // let's assume this is a nested doc
  void setData(Data d);
  String getId();
  long getTimestamp();
  void setTimestamp(long time);


MongoDbMyDoc extends BasicDBObject implements MyDoc 
  MongoDbObject()  
  void setId(String id) 
    this.put("_id", id);
  
  String getId() 
    return super.get("_id");
  
  void setData(Data d) 
    dataObj = new BasicDBObject();
    dataObj.put("someField",d.someField);
    dataObj.put("someField2", d.someField2);
    super.put("data",dataObj);
  
  ...

然后我在实际的存储实现中使用 MongoDB Java 客户端从数据库返回我的实现实例。这是我的 MongoDB 存储实现的构造函数:

public MongoDbMyStorage(DB db, String collection) 
  //DB in a mongodb object (from the client library) which was instantiated elsewhere
  dbCollection = db.getCollection(collection);
  dbCollection.setObjectClass(MongoDbMyDoc.class);
  this.factory = new MongoDbMyDocFactory();

这里还有 2 个接口: MyStorageQuery 也被实现为 MongoDB 实现的 BasicDBObject,并使用存储接口的newQuery() 生成。 还有MyDocFactory,这里没有介绍,但它基本上是一个文档工厂,它知道存储实现是什么,并相应地生成MyDoc 实例。

注意事项: 抽象没有多大意义的一件事是定义 MongoDB 存储使用的索引。我将所有ensureIndex(...) 调用都放在构造函数中,不是很通用,但是为每个集合定义索引是 MongoDB 特定的优化,所以我可以接受它。 另一个是提交是使用getLastError() 命令实现的,根据我的经验,该命令效果不佳。这对我来说不是问题,因为我几乎从不明确提交更改。

【讨论】:

你好,commit() 函数对你有什么作用?如果您通常将文档视为 MongoDB 中的事务边界?【参考方案5】:

Eric Evens 在他的《领域驱动设计》一书中对存储库模式进行了非常复杂且非常好的解释。他的定义是存储库应该是什么以及应该如何使用它(在我个人看来)。你可以在这里找到一个简短的描述:Eric Evans on Repositories

基本上,如果您将存储库保留为客户端代码和工厂之间的中介,那么它们将非常适合我理解您的需求。存储库应提供查询/构造/验证接口并完成所有数据采集人员(如连接/查询数据库),并且您应该拥有一个或多个(根据需要)工厂来构建对象并将其传递回客户端通过存储库。

【讨论】:

我知道存储库是什么——问题是它是否适合将它们与文档数据库一起使用。【参考方案6】:

将存储库模式与 NoSQl 数据库一起使用比 RDS 数据库更有意义,因为在适当的 DDD 中,每个聚合根需要一个存储库,但是由于 RDS 数据存储的限制,开发人员通常为每个实体/表创建一个存储库对象。 NoSQL 数据库允许您正确实现 DDD 和存储库模式。

【讨论】:

以上是关于将存储库模式与文档数据库一起使用是不是有意义?的主要内容,如果未能解决你的问题,请参考以下文章

将 inline 关键字与模板一起使用是不是有意义?

将角度服务与 es6 模块一起使用是不是有意义?

将 Psyco 与 django 一起使用是不是有意义?

在微服务中,为数据库迁移脚本和API代码提供单独的代码存储库是否有意义?

模式对象依赖关系:减少无效的文档中提到的指南真的有意义吗?

MongoDB:单机分片。是不是有意义?