用Mongoose反规范化:如何同步更改

有非规格化架构时传播更新的最佳方式是什么? 应该全部在同一个function上完成吗?

我有这样的模式:

var Authors = new Schema({ ... name: {type: String, required:true}, period: {type: Schema.Types.ObjectId, ref:'Periods'}, quotes: [{type: Schema.Types.ObjectId, ref: 'Quotes'}] active: Boolean, ... }) 

然后:

 var Periods = new Schema({ ... name: {type: String, required:true}, authors: [{type: Schema.Types.ObjectId, ref:'Authors'}], active: Boolean, ... }) 

现在说我想对作者进行非规范化处理,因为period字段总是使用句号的名称(这是唯一的,不能有两个同名的句号)。 然后说,我把我的模式变成这样:

 var Authors = new Schema({ ... name: {type: String, required:true}, period: String, //no longer a ref active: Boolean, ... }) 

现在mongoose不知道周期字段连接到周期模式。 所以,当某个时期的名称发生变化时,由我来更新该字段。 我创build了一个服务模块,提供这样一个接口:

 exports.updatePeriod = function(id, changes) {...} 

在这个函数中,我会通过更改来更新需要更新的期间文档。 所以这是我的问题。 那么我应该在这个方法中更新所有的作者吗? 因为那么这个方法就必须知道Author模式和任何其他使用句点的模式,从而在这些实体之间产生很多耦合。 有没有更好的办法?

也许我可以发出一段时间已经更新的事件,所有具有非规范化时期引用的模式都可以观察到,是更好的解决scheme吗? 我不太清楚如何解决这个问题。

好吧,当我等待比自己更好的回答时,我会尽力公布我迄今为止所做的事情。

前/后中间件

我尝试的第一件事是使用前/后中间件来同步相互引用的文档。 (例如,如果您有AuthorQuote ,并且作者有一个types的数组: quotes: [{type: Schema.Types.ObjectId, ref:'Quotes'}] ,那么无论何时删除一个报价, d必须从数组中删除它的_id ,或者如果作者被删除,你可能需要删除所有的引号)。

这种方法有一个重要的优点 :如果你定义每个Schema在它自己的文件中,你可以在那里定义中间件,并把它整齐排列 。 每当你看到架构,就在下面,你可以看到它的作用,它的变化如何影响其他实体等:

 var Quote = new Schema({ //fields in schema }) //its quite clear what happens when you remove an entity Quote.pre('remove', function(next) { Author.update( //remove quote from Author quotes array. ) }) 

但是,主要的缺点是当你调用update或任何Model静态更新/删除函数时,这些钩子不会被执行 。 相反,您需要检索文档,然后调用save()remove()

另一个更小的缺点是Quote现在需要知道任何引用它的人,以便在更新或删除引用时更新它们。 所以我们假设一个Period有一个引号列表, Author也有一个引号列表,Quote需要知道这两个来更新它们。

原因是这些函数直接发送primefaces查询到数据库。 虽然这很好,但我讨厌使用save()Model.Update(...)之间的不一致。 也许别人或你将来会意外地使用静态更新function,而你的中间件不会被触发,给你头痛的是你很难摆脱。

NodeJS事件机制

我现在所做的并不是最佳的,但是它给我提供了足够的好处,可以真正地超越利弊(或者我相信,如果有人愿意给我一些好的反馈)。 我创build了一个包装模型的服务,比如AuthorService ,它扩展了AuthorService ,它是一个构造函数,它看起来大概是这样的:

 function AuthorService() { var self = this this.create = function() {...} this.update = function() { ... self.emit('AuthorUpdated, before, after) ... } } util.inherits(AuthorService, events.EventEmitter) module.exports = new AuthorService() 

优点:

  • 任何感兴趣的function都可以注册到服务事件并通知。 那样的话,例如,当Quote更新时,AuthorService可以收听并相应地更新Authors 。 (注1)
  • 报价不需要知道引用它的所有文档,服务只是触发QuoteUpdated事件,当发生这种情况时,所有需要执行操作的文档都会这样做。

注1:只要有人需要与mongoose互动,就使用这项服务。

缺点:

  • 添加了样板代码,直接使用服务而不是mongoose。
  • 现在,触发事件时调用哪些函数并不十分明显。
  • 你以可读性为代价来解耦生产者和消费者(因为你只是emit('EventName', args) ,那么哪个服务正在监听这个事件并不明显)

另一个缺点是有人可以从服务中检索一个模型,并调用save() ,其中事件不会被触发,但我确信这可以通过这两种解决scheme之间的某种混合来解决。

我对这个领域的build议很开放(这就是为什么我把这个问题放在第一位)。

从编码的angular度来看,我将从架构的angular度讲更多的东西,因为当它来到它时,你可以用足够多的代码实现任何事情。

就我所能理解的,你最关心的问题是保持数据库的一致性,主要是在删除文档时删除文档,反之亦然。

因此,在这种情况下,我不build议将所有的function都包含在额外的代码中,而是build议使用primefaces操作,其中Action是您自己定义的一个方法,可以从数据库(文档和参考)中完整地删除一个实体。

因此,例如,当您要删除作者的报价时,您可以执行一些操作,如从数据库中删除报价文档,然后从作者文档中删除参考。

这种架构确保每个这些动作执行一个单一的任务,并执行它,而无需挖掘事件(发射,消费)或任何其他的东西。 这是一个独立完成自己独特任务的方法。