2014-03-05 32 views
9

当您拥有非规范化模式时传播更新的最佳方式是什么?是否应该在同一个功能中完成?使用Mongoose进行非规范化:如何同步更改

我有一个模式,像这样:

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, 
    ... 
}) 

现在,猫鼬不知道周期字段已连接到周期模式。因此,当某个时期的名称发生更改时,需要更新该字段。我创建了一个提供了接口,这样的服务模块:

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

在这个函数中我经历的变化来更新需要更新的时期文件。所以这是我的问题。那么,我应该更新此方法中的所有作者吗?因为那么该方法必须知道Author模式和任何其他使用句点的模式,从而在这些实体之间创建了很多耦合。有没有更好的办法?

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

回答

7

好吧,当我等待比我自己更好的答案时,我会尝试发布迄今为止我一直在做的事情。

前/后中间件

我想的第一件事就是用pre/post middlewares来相互引用的文件同步。 (例如,如果您有AuthorQuote,并且作者有一个类型为:quotes: [{type: Schema.Types.ObjectId, ref:'Quotes'}]的数组,则每当删除一个报价时,您必须从数组中删除其_id;或者如果作者被删除,您可能需要删除所有的引号)。

这种方法具有重要的优势:如果定义在它自己的文件中的每个模式,你可以有定义的中间件和拥有这一切整齐。每当你看看架构,右下面你可以看到它做什么,它的变化是如何影响其他实体,等:

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. 
    ) 
}) 

The main disadvantage however is that these hooks are not executed when you call update or any Model static updating/removing functions。相反,您需要检索文档,然后在其上调用save()remove()

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

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

事件的NodeJS机制

什么我目前做的是不是真的最佳,但它为我提供足够的好处实际上outweight的缺点(或者说,我相信,如果有人关心给我一些反馈意见,倒是很棒)。我创建了周围的模型包装服务,说AuthorService扩展events.EventEmitter,是一个构造函数,将大致如下:

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() 

优点:

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

注1:只要有人需要与猫鼬交互时使用此服务。

缺点:

  • 加样板代码,直接使用服务,而不是猫鼬的。
  • 现在,当您触发事件时,哪些功能会被调用并不十分明显。
  • 您脱钩生产者和消费者在易读性的成本(因为 你只是emit('EventName', args),它不是立即明显 哪些服务是听了这个事件)

另一个缺点是,有人可以检索从模型服务和电话save(),其中事件将不会被触发,但我确信这可以解决这两种解决方案之间的某种混合。

我很乐于接受这方面的建议(这就是为什么我首先发布了这个问题)。

0

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

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

因此,在这种情况下,而不是将整个功能封装在额外的代码中,我建议去原子动作,其中一个动作是你自己定义的一个方法,从数据库执行一个实体的完全删除和参考)。

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

这种架构确保每个这些动作都能执行单个任务并且执行得很好,而无需使用事件(发射,消耗)或其他任何东西。这是一个独立完成自己独特任务的方法。