Или как не успях да събера тези двете заедно

Приложението („при съответния ангажимент“) има команди, които записват събития в базата данни в реално време на Firebase в „/event_store“, а функцията apply прилага събитията върху текущото състояние на агрегата в пътя „/state_store“. За да уточним, apply задейства once()listener, за да извлече състоянието на всички агрегати¹ в паметта, а promise-chained on() listener на /event_store ще приложи всички нови събития, променяйки състоянието в паметта и актуализирайки „/state_store“.

Всичко работи добре, дори когато събития идват от множество източници (като например от администраторските команди в репото или от iOS приложението). Реших, че облачен сървър може да бъде пощаден да обработва събития, когато apply се внедри отново като облачна функция. Разпределението обаче не е лесно...

Носещо състояние

Има (вероятни) начини да се опитате да заобиколите бездържавния характер на облачните функции [1, 2], изглеждаше по-добре да приема истината, че няма да мога да направя това.

Преди да призная (временно?) поражение, се опитах да пренапиша apply (вижте cloud_apply ), за да имитират облачни функции², като извиках „/state_store“ once() слушателя вътре в обратното извикване на on() „/event_store“ слушателя. Тоест, всяко ново събитие ще задейства извличане на състоянието от базата данни, ще работи върху това състояние и ще запише мутиралото състояние обратно в „/state_store“.

За съжаление, ако събитията задействат cloud_apply достатъчно бързо, всички обратни извиквания завършват с едно и също състояние, с което да работят, и по някаква причина събитията се обработват в обратен ред, от най-новите към най-старите. (В тази реализация събитията имат свойство sequence number, за да се справят с подреждането.³)

$ node
> var f = require('./admin-functions.js');
> f.public_commands.add_user({first_name: "Attila", last_name: "Gulyas", username: "", email: "[email protected]", account_types: ["admin", "reader"]})
// 4 events in the bank.
> f.cloud_apply()
Stream: -LLf1MW_BjNdImtFLpl8 event: -LLf1MXo7XODvaHYXxll added_to_group
  Action: replay (event_seq: 4, state_seq: 0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Stream: -LLf1MW_BjNdImtFLpl8 event: -LLf1MXnv_wxX6yl7wwo added_to_group
  Action: replay (event_seq: 3, state_seq: 0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Stream: -LLf1MW_BjNdImtFLpl8 event: -LLf1MXknyGts_5jIHq9 email_added
  Action: replay (event_seq: 2, state_seq: 0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Stream: -LLf1MW_BjNdImtFLpl8 event: -LLf1MWaV2o95AUg7VbN person_added
  Action: replay (event_seq: 1, state_seq: 0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Документиране на странно поведение

Интересното е, че при издаването

f.ADMIN_APP.database().ref("state_store").on('value', function() {})

(обърнете внимание на празното обратно извикване), проблемът с поръчката изчезна. Не разгледах причината за това и така или иначе не би било безопасно да разчитаме на такова недокументирано (?) поведение.

Обработка на събития извън ред (или как си спомняте бъдещето?)

Не е гарантирано, че облачните функции ще се задействат по ред или само веднъж. Когато състоянието е извън картината, как човек се справя с нередовни събития, когато редът има значение? Представете си събитието email_deleted, приложено преди email_added; дори ако идемпотентността е осигурена, трябва да има начин да се запомни, че бъдещо свойство се изтрива.

Както подсказва цитираният SO отговор по-долу, таблица със състояния (вероятно) може да бъде решението, но приложението става наистина сложно.

Проблемът с обратния ред може да не засегне внедряването на облачната функция, но въпреки това функциите вероятно ще бъдат задействани неправилно. „Не се гарантира, че функциите в облака ще се задействат по ред или само веднъж»“ (вижте също „въпрос“).

Заключение

Беше хубав мисловен експеримент и може да продължи това, когато времето позволява, но най-безопасното би било просто да стартирате виртуална машина с демонизирано приложение Node.

  1. [^] Не е идеално, следователно проблем #5 за извличане на базата на поток.
  2. [^] Облачните функции на Firebase също имат тригери за база данни в реално време, подобни на тези на SDK за администратор и клиент.
  3. [^] Далеч от съвършенство, но подреждането на базата на времеви клейма във Firebase не се получи.