2014-03-05 25 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

從編碼的角度來看,我會從架構的角度講更多的東西,因爲當它來到它的時候,你可以用足夠多的代碼實現任何事情。

就我所能理解的,你主要關心的是在你的數據庫中保持一致性,主要是在刪除文檔時刪除文檔,反之亦然。

因此,在這種情況下,而不是將整個功能封裝在額外的代碼中,我建議去原子動作,其中一個動作是你自己定義的一個方法,從數據庫執行一個實體的完全刪除和參考)。

因此,例如,當您要刪除作者的報價時,您可以執行類似於從數據庫中刪除報價文檔,然後從作者文檔中刪除參考。

這種架構確保每個這些動作都能執行單個任務並且執行得很好,而無需使用事件(發射,消耗)或其他任何東西。這是一個獨立完成自己獨特任務的方法。