使用 Mongoose 进行非规范化:如何同步更改

Posted

技术标签:

【中文标题】使用 Mongoose 进行非规范化:如何同步更改【英文标题】:Denormalization with Mongoose: How to synchronize changes 【发布时间】:2014-04-07 18:11:42 【问题描述】:

当您拥有非规范化架构时,传播更新的最佳方式是什么?是否应该全部在同一个函数中完成?

我有这样的架构:

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 不再知道 period 字段已连接到 Period 架构。因此,当一个时期的名称发生变化时,由我来更新该字段。我创建了一个提供如下接口的服务模块:

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

在此函数中,我通过更改来更新需要更新的期间文档。所以这是我的问题。那么,我应该更新此方法中的所有作者吗?因为那时该方法必须了解 Author 模式和任何其他使用句点的模式,从而在这些实体之间创建大量耦合。有没有更好的办法?

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

【问题讨论】:

【参考方案1】:

好的,虽然我在等待比我自己更好的答案,但我会尝试发布我到目前为止所做的事情。

前置/后置中间件

我尝试的第一件事是使用pre/post middlewares 来同步相互引用的文档。 (例如,如果您有AuthorQuote,并且作者有一个类型为quotes: [type: Schema.Types.ObjectId, ref:'Quotes'] 的数组,那么每当删除引用时,您都必须从数组中删除它的_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.
    )
)

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 现在需要了解引用它的任何人,以便在更新或删除 Quote 时更新它们。所以假设Period 有一个引号列表,Author 也有一个引号列表,Quote 需要知道这两个来更新它们。

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

NodeJS 事件机制

我目前所做的并不是最理想的,但它为我提供了足够的好处,实际上超过了缺点(或者我相信,如果有人愿意给我一些反馈,那就太好了)。我创建了一个围绕模型的服务,例如 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) Quote 不需要知道引用它的所有文档,Service 只需触发QuoteUpdated 事件,所有需要在发生这种情况时执行操作的文档都会这样做。

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

缺点:

添加了样板代码,直接使用服务而不是猫鼬。 现在还不清楚当您调用什么函数时 触发事件。 以易读性为代价将生产者和消费者分离(因为 你只是emit('EventName', args),这不是很明显 哪些服务正在侦听此事件)

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

我对这个领域的建议非常开放(这就是我首先发布这个问题的原因)。

【讨论】:

【参考方案2】:

我将更多地从架构的角度而不是从编码的角度发言,因为当涉及到它时,你几乎可以用足够多的代码行来实现任何事情。

据我所知,您主要关心的是保持整个数据库的一致性,主要是在删除参考文献时删除文档,反之亦然。

所以在这种情况下,我建议不要将整个功能包装在额外的代码中,而是使用原子操作,其中操作是您自己定义的一种方法,它执行从数据库中完全删除实体(文档和参考)。

例如,当您想删除作者的引用时,您可以执行一些操作,例如从数据库中删除引用文档,然后从作者文档中删除引用。

这种架构可确保这些操作中的每一个都执行单个任务并很好地执行它,而无需利用事件(发射、消耗)或任何其他东西。它是一种执行自己独特任务的独立方法。

【讨论】:

以上是关于使用 Mongoose 进行非规范化:如何同步更改的主要内容,如果未能解决你的问题,请参考以下文章

具有非规范化的 cassandra 数据建模

java多线程理解2

非规范化模式?

Mongoose ODM,保存前更改变量

Mongoose ODM,保存前更改变量

如何使用 _id 更改 mongoose 文档中的数据?