Или как не успях да събера тези двете заедно
Приложението („при съответния ангажимент“) има команди, които записват събития в базата данни в реално време на 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
достатъчно бързо, всички обратни извиквания завършват с едно и също състояние, с което да работят, и по някаква причина събитията се обработват в обратен ред, от най-новите към най-старите. (В тази реализация събитията имат свойство seq
uence 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.
- [^] Не е идеално, следователно проблем #5 за извличане на базата на поток.
- [^] Облачните функции на Firebase също имат тригери за база данни в реално време, подобни на тези на SDK за администратор и клиент.
- [^] Далеч от съвършенство, но подреждането на базата на времеви клейма във Firebase не се получи.