4.1 Profesionalita s Firebase V9 – Hygiena systému – Zpracování chyb a transakce

Poslední revize:červen 2022

Úvod

Protože jsou tyto příspěvky určeny především čtenářům, kteří se stále snaží prosadit ve světě IT, ukázky kódu, které jsem zatím poskytl, předpokládají, že věci budou obecně fungovat tak, jak mají. Jinak by to znamenalo další zmatek!

I teď se budu držet zpátky, abych vás nezahltil detaily. Mám v úmyslu pouze načrtnout široký nástin problémů, které chci pokrýt, a poskytnu vám reference, které budete moci sledovat ve svém volném čase. Ale jsou věci, o kterých si myslím, že je opravdu důležité, abyste si jich byli vědomi.

Ve skutečném světě se věci nedělají vždy pracovat podle plánu. Váš kód bude téměř jistě obsahovat syntaktické nebo logické chyby a váš prohlížeč jej při prvním spuštění brutálně vyhodí. Ale i když to narovnáte, uvidíte stejný výsledek, když se na to vaši uživatelé dostanou a zadají „hloupá“ data – tj. data, která způsobí, že váš kód znovu selže, protože jste nepředvídali, že se to může stát. Obávám se, že je to opět vaše chyba a musíte provést příslušné ověření platnosti v

Ty jsou předvídatelné problémy, kterým lze spolehlivě předejít pečlivým kódováním a testováním

Ale jiná třída chyb – věci, které nazýváme nepředměty - nelze se vyhnout. Příkladem může být selhání síťového připojení nebo problémy s hostitelem vzdálené databáze. Jediné, co můžete v této situaci udělat, je napsat kód, který rozpozná, že nastal problém, a poté podnikne příslušné kroky. Někdy to nejlepší, co můžete udělat, je jednoduše zobrazit zprávu ve stylu „Je nám líto – systém je momentálně nedostupný“. Ale to bude vždy lepší, než nechat uživatele koukat na prázdnou, zamrzlou obrazovku! Zde můžete získat body tím, že prokážete svůj profesionální zájem o uživatele. Jak za chvíli uvidíte, toto se ostře dostane do centra pozornosti, když si uvědomíte, že v databázových aplikacích mohou nepředvídané chyby, ke kterým dochází v nevhodných okamžicích, vést ke ztrátě dat .

Tento příspěvek je tedy celý o tom, jak reagovat na tyto různé výzvy:jak napsat zvukový kód na prvním místě, jak informovat uživatele, když se stane nepředvídatelná situace, a jak udržet databázi ve zdravém a konzistentním stavu.

Oprava předvídatelného – Dobré kódovací/testovací postupy

Psaní spolehlivého a udržovatelného počítačového kódu je část umění a část inženýrská disciplína. Existuje mnoho různých pohledů na to, co představuje „dobrý kód“. Pro Javascript vás opět odkazuji na Eloquent Javascript. Prvky správné kódovací praxe budou zahrnovat rozvržení, konvence pojmenování a strukturu programu. Kromě toho se opravdu naučíte pouze to, co funguje a co ne, ze směsi praktických zkušeností a při pohledu na práci ostatních.

"Testování" je samozřejmě postup, který dodržujete, abyste potvrdili spolehlivost kódu. Na vaše IDE a prohlížeč (prostřednictvím jeho nástroje pro ladění systému) se můžete spolehnout, že vám pevně řeknou, kdy je vaše syntaxe chybná nebo kdy jste uvedli svůj program do stavu, kdy nelze příkaz spustit. Obzvláště užitečná funkce IDE VSCode je, že vás upozorní na chyby během psaní kódu (tj. předtím, než se jej pokusíte spustit). Ve skutečnosti bude předkládat návrhy a pomůže vám vytvořit správný kód na prvním místě - což je obrovská úspora času. Kromě toho však musíte vytvořit „scénáře“, kde od známých počátečních podmínek sledujete naplánované trasy vaší aplikací a kontrolujete, zda výsledky odpovídají očekávání. Samozřejmě si uvědomujete, že to budete muset opakovat, kdykoli budete provádět změny ve vašem systému! Možná byste se rádi podívali na systémy „testovacích běžců“, které používají tradiční profesionální vývojáři k systematizaci postupu. "Jest" je příklad, který by vás mohl zajímat. Jak již bylo řečeno – seriózní, profesionální vývoj IT systému je těžká práce!

Oprava nepředvídatelného - zařízení Javascript "catch"

Pokud máte obavy ohledně zranitelnosti bloku kódu, systém Javascript vám umožní zabalit to do try{.. vulnerable code block...} catch{.. do something about it ...} struktura. To znamená, že pokud cokoli v bloku kódu „vyhodí“ chybu, řízení je přesměrováno na kód v bloku catch { }.

Co znamená "vyhodit chybu"? Znamená to, že kus kódu rozpoznal, že se něco nedaří, a v nejjednodušším případě provedl throw 'Explanation'; tvrzení. Zde je „Vysvětlení“ řetězec, který vysvětluje problém. throw příkaz zpřístupní 'Vysvětlení' catch(error) jako error.message .

Tyto zprávy, které uvidíte v konzole prohlížeče, když jste vytvořili chybný kód, se objevily, protože je prohlížeč „hodil“. Pokud svůj kód vložíte do zkušebních bloků (ne že bych naznačoval, že by to byl vždy dobrý nápad), mohli byste tyto chyby zachytit a "ošetřit" je.

Takže například, zatímco kód webapp jako následující:

let x = 1 / a;

kde a je proměnná, kterou jste nedefinovali, bude zastavena vaším prohlížečem, když ji spustíte. I když se budete dívat na prázdnou obrazovku, budete vězte, že můžete zjistit, co se pokazilo, když se podíváte na konzolu v systémových nástrojích prohlížeče. Zde najdete ReferenceError: a is not defined zpráva. Ale vaši uživatelé o tom samozřejmě nebudou vědět – jediné, co uvidí, bude mrtvá webová aplikace.

Na druhou stranu:

try {
    let x = 1 / a;
} catch (error) {
    alert("Oops Code has thrown the following error: " + error)
}

vytvoří výstražnou zprávu, která je jasně viditelná pro uživatele webové aplikace.

Vzhledem k tomu, že „vyhozená“ chyba může být pohřbena hluboko ve složité vnořené hierarchii aplikačního kódu a funkcí SDK, můžete se také divit, jak Javascript dokáže zajistit toto uspořádání. Znovu vás odkazuji na Eloquent Javascript (kapitola 8).

U webové aplikace Firebase budete s největší pravděpodobností chtít „chytit“ chyby vyvolané funkcemi Firestore nebo Cloud Storage. Máte dvě možnosti:zatímco celý balík kódu může být zabalen do uspořádání try/catch, které jsem právě popsal, pokud z nějakého důvodu chcete monitorovat jednotlivé funkce, Javascript vám nabídne .catch() metodu, kterou můžete připojit k volání funkce Firestore. Zde je příklad z laboratoře kódu Google:

SpaceRace.prototype.deleteShip = function(id) {
    const collection = firebase.firestore().collection('ships');
    return collection.doc(id).delete().catch((error) => {
            console.error('Error removing document: ', error);
        });
};

Dávám přednost tomuto uspořádání pro pokus/chytání bloků, protože si myslím, že díky tomu je můj kód o něco čitelnější.

Pokud vás zajímá, jak .catch funguje, odpověď je, že Javascript poskytuje tuto "metodu" automaticky pro jakoukoli funkci, která vrací Promise - a většina funkcí Firestore vrací Promise. Pro pozadí slibů a klíčového slova čekání se podívejte na můj dřívější příspěvek:Klíčové slovo „čekat“

Transakce

Jak je uvedeno výše, nepředvídatelné problémy s hardwarem mohou vést k poškození produkční databáze, pokud software webové aplikace na tuto možnost dostatečně nepozoruje a není vybaven, aby ji zvládl.

Zde je příklad. Jistě si vzpomenete, že aplikace „Nákupní seznam“ představená v „kódování jednoduché webové aplikace“ umožňovala uživatelům vytvářet seznamy „nákupních položek“. Představte si, že by se „vedení“ rozhodlo, že by bylo dobré průběžně počítat, kolikrát se koupě objevila na nákupních seznamech uživatelů. V souladu s tím byla do databáze přidána kolekce „purchaseMI“ obsahující dokumenty „průběžný celkový počet“. Nyní pokaždé purchaseItem je přidána nebo odstraněna z nákupního seznamu, webová aplikace musí upravit odpovídající položku v purchaseMI.

Problém je v tom, že nepohodlné selhání v polovině takového postupu zanechá databázi v poškozeném stavu. S opatrností by bylo možné takové selhání "chytit" a pokusit se s ním vypořádat, ale ve složitější situaci by to nebyl jednoduchý úkol.

Věci vypadají ještě chmurněji, když zvážíte, co se může stát, když vaše databáze zpracovává "současné" požadavky od více uživatelů.

Předpokládejme, že dva uživatelé do svých seznamů současně přidají uživatelePurchase pro, řekněme, "role". Každý z nich tak přistupuje ke kolekci purchaseMI pro průběžný součet pro „válce“ – a každý tak zjistí, že drží stejné hodnoty aktuálního součtu pro danou položku – řekněme, že stojí na „10“. A ano – jsem si jistý, že jste viděli problém, který nyní nastává. Poté, co každý použil svou aktualizaci na průběžný součet, zatímco toto by mělo znít „12“, ve skutečnosti je to jen „11“. Databáze je nyní poškozená – aktuální hodnota pole průběžného součtu pro „rolls“ v purchaseMI se neshoduje s hodnotou, kterou byste získali, kdybyste hledali „rolls“ v userShoppingLists.

Potřebujeme pomoc od společnosti Google, protože tyto obavy týkající se „souběhu“ jsou příliš složité na to, aby je řešila webová aplikace. Potřebujeme nějaký způsob, jak definovat "transakci" - sekvenci databázových příkazů, které buď všechny uspějí, nebo jsou všechny zahozeny. S takto deklarovanou transakcí se webová aplikace musí zabývat pouze celkovým výsledkem – nemusí se zabývat vnitřními detaily procesu.

Odpověď společnosti Google je poskytnout transaction objekt s metodami, které lze použít ke spouštění příkazů CRUD způsobem, který jim umožňuje vzájemnou komunikaci. Toto transaction objekt je vytvořen pomocí runTransaction funkce, která zase spustí funkci s transaction objekt jako svůj argument. Tím se zabalí sekvence příkazů CRUD a tím se definuje transakce. Firestore je pak schopen bez dalšího úsilí z naší strany zajistit, že i když transakce může selhat, pokud byla databáze konzistentní před zahájením transakce, zůstane konzistentní i po jejím dokončení.

Abyste měli představu, jak to vypadá, zde je ukázkový kód pro aktualizovanou verzi funkce Odstranit webové aplikace „Nákupní seznamy“.

 async function deleteShoppingListDocument(id, userPurchase) {

    // id =>  a userShoppingLists document
    // userPurchase =>  the userPurchase field for this document

    await runTransaction(db, async (transaction) => {

        const purchaseMIDocRef = doc(db, 'purchaseMI', userPurchase);
        const purchaseMIDoc = await transaction.get(purchaseMIDocRef);

        const shoppingListsDocRef = doc(db, 'userShoppingLists', id);
        transaction.delete(shoppingListsDocRef);

        const newUserPurchaseTotal = purchaseMIDoc.data().userPurchaseTotal - 1;
        transaction.update(purchaseMIDocRef, { userPurchaseTotal: newUserPurchaseTotal });

    }).catch((error) => {alert("Oops - Transaction failed : " + error)});
 }

Pro vysvětlení:

  1. Musel jsem přidat runTransaction k importu pro firebase/firestore/lite . Dalšími přípravami bylo vytvoření purchaseMI kolekce s dokumenty zadanými na userPurchase a obsahující pole userPurchaseTotal. Také jsem přidal pravidlo povolující volný přístup pro čtení/zápis do purchaseMI .

  2. Funkce deleteDoc, kterou jsem dříve používal k odstranění dokumentu shoppingLists, je nyní nahrazena transaction.delete funkce. Všechny funkce CRUD, které bych možná potřeboval použít, jsou podobně nenápadně změněny – viz firebase.firestore.Transaction pro dokumentaci Google k objektu Transaction. Všimněte si, že getDocs , formulář dotazu getDoc není podporováno transaction objekt.

    • transaction.get nahrazuje getDoc
    • transaction.set nahrazuje setDoc
    • transaction.update nahrazuje updateDoc
    • transaction.delete nahrazuje deleteDoc
  3. Pořadí, ve kterém jsou v příkladu prováděny databázové příkazy, se může zdát nepřirozené. Je to proto, že v transakci Firestore musí být všechny operace „čtení“ dokončeny před spuštěním jakýchkoli aktualizací.

  4. Zatímco transaction.get stále vrací příslib, a proto je třeba jej volat s předchozím klíčovým slovem „wait“, což žádná z jiných transakčních metod nedělá.

  5. Pokud Firestore zjistí, že jiný uživatel upravil data, která právě četl, odloží vše, co mohl udělat, a znovu spustí transakci. Transakce tak může proběhnout více než jednou, a proto je třeba dbát na všechny příkazy, které vytvářejí „vedlejší účinky“. Například příkaz aktualizace pole čítače může způsobit zmatek.

  6. Transakce mohou zapisovat maximálně do 500 dokumentů a existuje limit přibližně 20 MB na objem úložiště, který může být transakcí ovlivněn.

  7. Transaction zde použitý koncept – definovaný jako „soubor čtení a zápisu operace s jedním nebo více dokumenty" - je paralelní s Batched writes zařízení – „množina zápisu operace s jedním nebo více dokumenty." Batched Writes jsou mnohem jednodušší než Transactions a jsou preferovány, kde je to vhodné.

  8. Cloudové funkce mohou také využívat transakce a v tomto případě jsou některá omezení popsaná výše zmírněna – například SDK pro transakce Cloud Function podporuje dotaz ve tvaru get

Jak vidíte, dá se o tom hodně říct. Ale teď, když jsem uvedl téma a dodal příklad, myslím, že by pravděpodobně bylo nejlepší, kdybych se zastavil a nechal vás, abyste si přečetli dokumentaci Transakce a dávkové zápisy Google. Možná budete chtít také spustit nějaký testovací kód! Do výše uvedených dokumentů Google je zasunuto vynikající video, které také důrazně doporučuji zhlédnout.

Závěrem lze říci, že transakce nejsou pro slabé povahy, ale právě díky nim bude vaše webová aplikace skutečně profesionální produkt. Hodně štěstí!

Další příspěvky v této sérii

Pokud vás tento příspěvek zaujal a rádi byste se o Firebase dozvěděli více, možná by stálo za to podívat se na index této série.