我目前正在试验MongoDB。我正在从NHibernate/SQL思维模式转变,因此最初我实现了一个用于数据访问的存储库模式。
在我开始使用嵌套文档之前,这一切看起来都很好。现在它开始看起来有点不匹配。但是,我对存储库感到满意,并且喜欢它们提供的抽象、关注点分离和可测试性。
人们是否成功地将存储库模式与文档数据库一起使用?如果不是,您使用什么数据访问方法?抽象/SoC呢?
我目前正在试验MongoDB。我正在从NHibernate/SQL思维模式转变,因此最初我实现了一个用于数据访问的存储库模式。
在我开始使用嵌套文档之前,这一切看起来都很好。现在它开始看起来有点不匹配。但是,我对存储库感到满意,并且喜欢它们提供的抽象、关注点分离和可测试性。
人们是否成功地将存储库模式与文档数据库一起使用?如果不是,您使用什么数据访问方法?抽象/SoC呢?
这是一个有趣的问题。在我使用 MongoDB 时,我选择没有存储库。这是主要的,因为文档数据库被用作读取存储(因此简化了存储在那里的数据)。
我想您必须将考虑剥离回什么是存储库以及从额外的抽象层中获得什么优势。一个好的设计将有尽可能少的层数。
因此,存储库给了您一些持久性无知以及在通用数据上下文中使用工作单元的能力。它还可以提高您针对数据单独测试查询的能力(因为这些通常被抽象为可查询或规范)。
此外,一些文档数据库已经提供了存储库模式(RavenDB 等),因此不需要再增加一层。
因此,在我看来,使用存储库与其说是您的数据是存储为关系表还是文档,不如说是您从抽象中获得了什么。
我在具有存储库模式的生产代码中使用 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()
命令实现的,根据我的经验,该命令效果不佳。这对我来说不是问题,因为我几乎从不明确提交更改。
思考“我应该使用 X 还是不使用”不如专注于“我应该使用什么”高效?
存储库模式的替代方案是什么,权衡是什么,它们与您的领域和实现有什么关系?
存储库非常适合在通用存储(例如 SQL)上执行一组预定义的模式。对于文档存储,我的印象是文档模式将比您通常在基于 SQL 的存储中看到的更大范围确定访问模式。在这种情况下实现存储库可能会导致非常泄漏的抽象,其中对底层文档结构的更改会对相关业务代码产生 1:1 的影响。在这种情况下,存储库提供的价值很小。对我来说,文档存储自然很适合工作单元 (UoW) 范式,其中工作单元是文档(或文档+嵌套子文档,或文档集)。
正如您所提到的,存储库模式的另一个优势是对存储机制的抽象。权衡通常是失去对 Mongo 的低级实现特定功能的访问权限。这对你来说是一个值得的权衡吗?NHibernate 与 SQL 非常紧密耦合,因此对 RDBMS 的所有重要特性都有丰富的功能抽象。我不知道 Mongo 有任何类似的框架,所以你真的会大大提高抽象级别。
您是否需要支持多个并发数据存储?例如,您是否会通过相同的数据层抽象将某些类型的数据写入 SQL 并将其他类型的数据写入 Mongo?如果是这样,那么存储库是一个不错的选择。
如果您可以提供有关您的域和实施的更多详细信息,那么我们可以深入了解您可能要考虑的具体权衡
我不知道这是否会对您有所帮助,但不久前我正在听Ayende Rahien 谈论 RavenDB的播客。他建议一个文档实际上可以很好地映射到 DDD 哲学中的聚合。如果您使用嵌套文档来表示聚合并遇到设计问题,那么嵌套可能不是最好的方法吗?
Eric Evens 在他的《领域驱动设计》一书中对存储库模式进行了非常复杂且非常好的解释。他的定义是存储库应该是什么以及应该如何使用它(在我个人看来)。你可以在这里找到一个简短的描述:Eric Evans on Repositories
基本上,如果您将存储库保留为客户端代码和工厂之间的中介,那么它们将非常适合我理解您的需求。存储库应提供查询/构造/验证接口并完成所有数据采集人员(如连接/查询数据库),并且您应该拥有一个或多个(根据需要)工厂来构建对象并将其传递回客户端通过存储库。
将存储库模式与 NoSQl 数据库一起使用比 RDS 数据库更有意义,因为在适当的 DDD 中,每个聚合根需要一个存储库,但是由于 RDS 数据存储的限制,开发人员通常为每个实体/表创建一个存储库对象。NoSQL 数据库允许您正确实施 DDD 和存储库模式。