Ok, mentre aspetto una risposta migliore della mia, cercherò di postare quello che ho fatto finora.
Pre/Post Middleware
La prima cosa che ho provato è stata usare i pre/post middleware
per sincronizzare i documenti che fanno riferimento a vicenda. (Ad esempio, se hai Author
e Quote
e un autore ha una matrice del tipo:quotes: [{type: Schema.Types.ObjectId, ref:'Quotes'}]
, quindi ogni volta che un preventivo viene eliminato, dovresti rimuovere il suo _id
dalla matrice. Oppure, se l'autore viene rimosso, potresti voler rimuovere tutte le sue citazioni).
Questo approccio ha un vantaggio importante :se definisci ogni schema nel proprio file, puoi definire lì il middleware e avere tutto organizzato in modo ordinato . Ogni volta che guardi lo schema, sotto puoi vedere cosa fa, in che modo le sue modifiche influiscono su altre entità, ecc.:
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.
)
})
Il principale svantaggio tuttavia, questi hook non vengono eseguiti quando si chiama update o qualsiasi funzione di aggiornamento/rimozione statica del modello
. Piuttosto devi recuperare il documento e quindi chiamare save()
o remove()
su di loro.
Un altro svantaggio minore è che Quote ora deve essere a conoscenza di chiunque vi faccia riferimento, in modo che possa aggiornarle ogni volta che una quotazione viene aggiornata o rimossa. Quindi diciamo che un Period
ha un elenco di citazioni e Author
ha anche un elenco di citazioni, Quote dovrà conoscere queste due per aggiornarle.
Il motivo è che queste funzioni inviano direttamente query atomiche al database. Anche se questo è carino, odio l'incoerenza tra l'uso di save()
e Model.Update(...)
. Forse qualcun altro o tu in futuro utilizzerai accidentalmente le funzioni di aggiornamento statico e il tuo middleware non viene attivato, causandoti mal di testa di cui fatichi a liberarti.
Meccanismi di eventi NodeJS
Quello che sto facendo attualmente non è davvero ottimale, ma mi offre abbastanza vantaggi da superare effettivamente i contro (o almeno così credo, se qualcuno volesse darmi un feedback sarebbe fantastico). Ho creato un servizio che avvolge un modello, diciamo AuthorService
che estende events.EventEmitter
ed è una funzione Costruttore che avrà più o meno questo aspetto:
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()
I vantaggi:
- Qualsiasi funzione interessata può registrarsi ai Serviceevents ed essere avvisata. In questo modo, ad esempio, quando un
Quote
aggiornato, AuthorService può ascoltarlo e aggiornare gliAuthors
di conseguenza. (Nota 1) - Non è necessario che il preventivo sia a conoscenza di tutti i documenti che lo fanno riferimento, il Servizio attiva semplicemente il
QuoteUpdated
evento e tutti i documenti che necessitano di eseguire operazioni quando ciò accade lo faranno.
Nota 1:fintanto che questo servizio viene utilizzato ogni volta che qualcuno ha bisogno di interagire con la mangusta.
Gli svantaggi:
- Aggiunto codice boilerplate, utilizzando direttamente un servizio invece di mangusta.
- Ora non è esattamente ovvio quali funzioni vengono chiamate quando si attiva l'evento.
- Separa produttore e consumatore a scapito della leggibilità (dal momento che si limita a
emit('EventName', args)
, non è immediatamente evidente quali Servizi stiano ascoltando questo evento)
Un altro svantaggio è che qualcuno può recuperare un Modello dal Servizio e chiamare save()
, in cui gli eventi non verranno attivati anche se sono sicuro che questo potrebbe essere affrontato con una sorta di ibrido tra queste due soluzioni.
Sono molto aperto a suggerimenti in questo campo (motivo per cui ho postato questa domanda in primo luogo).