Updating a MiniMongo collection doc while it is being added

#1

I have a situation of unexpected, and inconsistent Mongo collection docs events firing.
To simplify, it goes like this:

Collection A: Bookings.
Collection B: Invoices.
Each Booking can have zero or one invoices, and is referenced by an optional attribute ‘invoiceID’ on the Booking doc.
Invoices are created and manipulated by a handler that listens to the 3 events fired by the Bookings Mini Mongo collection:

Bookings.observe({
      added: this._onBookingsAdded,
      changed: this._onBookingChanged,
      removed: this._onBookingRemoved,
    });

So when a Booking is added, the Bookings collection fires ‘added’, and the Invoice handler listens and creates a new Invoice. Then, still in the ‘added’ callback, it updates MiniMongo’s Bookings collection. It updates the newly added Booking doc with the matching invoiceID by calling:

_onBookingsAdded = newBooking => {
  const newInvoiceID = Invoices.insert(new InvoiceOM({...}));
  Bookings.update(
      { _id: newBooking._id },
      { $set: { invoiceID: newInvoiceID } },
    );
  };

Then I get a ‘changed’ event for that same Booking doc, with the only change of having an ‘invoiceID’ set to it.
Just as expected.
So essentially, I am updating the MiniMongo’s new doc while the ‘added’ event has not yet finished.

Now comes the weird part -
One of 2 options of unexpected behaviors happen, and it seems quite random which one of the 2 (I distinguish them from debugging, and it seems that each booking triggers one of the 2):

  1. After updating the invoiceID on the new Booking, a second passes, and I get a ‘removed’ event for that same booking, followed by an ‘added’ event for the same booking again.
  2. After updating the invoiceID on the new Booking, I get another ‘changed’ event for that Booking, only this time the change is reverting the invoiceID from what it was set, back to undefined again.

Any ideas why? How can I have a reliable chain of events, and have the invoiceID updated only once?
I know that Meteor Collection Hooks might solve this, but I want to understand it first.

#2

You should try to achieve this some other way.

You can’t, if you mutate the document’s ID in an added event. It’s like mutating a list while iterating through it—a big no no!

What’s your actual objective, to correctly create invoices? You shouldn’t create documents reactively this way. Whenever you make a Bookings document, make a corresponding Invoices document in the same method!

#3

I know that just calling them both from the same method would not result in this, but due to the whole app’s design, and separation of control, I cannot mix the 2:
Bookings are created and manipulated from many different entry points in the app, while the finances are separated and dealt from handlers who are listening to the Bookings collection.
I want to keep them decoupled and not call finances directly every time someone changes a booking.

And from my understanding, this is not like mutating a list while iterating it, because the event is called ‘added’, which means it should be called after all adding process have been done, which means I could also update it after it was added.

So, I have 2 ideas to solve this:

  1. Use collection hooks on Bookings.
  2. Use only server methods to ensure the collection is really ready before updating it again, and by this going around direct access to MiniMongo (which will result in losing MiniMongo’s advantages like working temporarily offline).

Any thoughts?

#4

Yes, but look what you’re doing. Changing an ID? Not only are you not supposed to do that, but think about if you change the ID of the document, how does minimongo (or any stateless function) know that a document has been changed, instead of one document removed and another added?

There’s just so much wrong it’s hard to really work in a framework where you want to perpetuate how many things are broken.

I know you’ve committed to a specific architecture, but it’s a bad architecture. You shouldn’t reactively add and remove documents inside your application server in order to enforce what is essentially a relational database in a document store. That’s pretty much a negative sign in front of what you’re supposed to do. It feels like a small error, but if you put a negative sign in front of your rocket ship calculations, it flies straight into the ground. It’s 200% wrong.

You definitely shouldn’t use the client to enforce adding and removing invoices. What if the person’s Internet gets disconnected while they are adding a booking? They create the booking, 100ms later, their cellphone tries to Invoices.insert, all of a sudden its internet connection is dead, and now the invoice isn’t inserted?

The same could happen on the server. If it was a method, okay, when you interrupt node, it will finish all the in-flight methods because fibers are great like that. If it’s a reactive thing like you’re doing, there’s no way for node to know that when it’s shut down, it’s “interrupting” something that is decoupled. You shut down node every time you update, so you’ve created a data corruption liability just by updating your code (the worst kind).

What were you supposed to do?

Bookings.insert({
   name: 'Booking name',
   invoices: [{
     _id: 'invoiceDocumentId1',
     price: 100
   }, {
     _id: "invoiceDoucmentId2",
     price: 200
   }] });

I know that you feel like it’s too late for this. But you gotta understand, you’ll be reimplementing a relational database on top of mongo using meteor, a really terrible and difficult thing to do, trying to fix what is wrong with what you are doing.

Or just insert the invoice after the booking is added? I mean really, with a code editor, you can find the 10 places that happens. Maybe they’re like, 2 places. What difference does it make? Decoupling for what?

#5

The fun part, by the way, is that most data you deal with on a website is indeed inherently relational.