4.1 Professioneel worden met Firebase V9 - Systeemhygiëne - Foutafhandeling en transacties

Laatst beoordeeld :juni 2022

Inleiding

Omdat deze berichten in de eerste plaats bedoeld zijn voor lezers die nog steeds moeite hebben om voet aan de grond te krijgen in de IT-wereld, gaan de codevoorbeelden die ik tot nu toe heb gegeven ervan uit dat de dingen over het algemeen zullen werken zoals bedoeld. Anders zou de verwarring alleen maar groter zijn geworden!

Zelfs nu blijf ik achter om je te overladen met details. Ik ben alleen van plan om in grote lijnen de onderwerpen te schetsen die ik wil behandelen en zal u referenties geven die u op uw gemak kunt opvolgen. Maar er zijn dingen waarvan ik denk dat het heel belangrijk is dat je je daarvan bewust bent.

In de echte wereld doen dingen niet altijd werken zoals bedoeld. Uw code zal vrijwel zeker syntaxis- of logische fouten bevatten en zal brutaal worden gedumpt door uw browser wanneer u deze voor het eerst uitvoert. Maar zelfs als je het hebt rechtgezet, zie je hetzelfde resultaat als je gebruikers eraan komen en "domme" gegevens invoeren - dat wil zeggen gegevens die ervoor zorgen dat je code opnieuw faalt omdat je niet had voorzien dat dit zou kunnen gebeuren. Weer jouw fout, vrees ik, en je moet de juiste validatiecontroles plaatsen

Deze zijn voorspelbaar problemen die op betrouwbare wijze kunnen worden vermeden door zorgvuldig te coderen en te testen

Maar een andere soort fouten - dingen die we onvoorziene omstandigheden noemen - kan niet worden vermeden. Voorbeelden zijn het mislukken van een netwerkverbinding of problemen met de externe databasehost. Het enige dat u in deze situatie kunt doen, is code schrijven die herkent dat er een probleem is opgetreden en vervolgens passende actie onderneemt. Soms kunt u het beste gewoon een bericht weergeven in de trant van "Sorry - systeem momenteel niet beschikbaar". Maar dit is altijd beter dan uw gebruikers naar een leeg, bevroren scherm te laten kijken! Hier kunt u punten scoren door uw professionele zorg voor uw gebruikers aan te tonen. Zoals u zo zult zien, komt dit scherp in beeld wanneer u zich realiseert dat in databasetoepassingen onvoorziene fouten die op ongelegen momenten optreden kunnen leiden tot gegevensverlies .

Dit bericht gaat dus helemaal over hoe u op deze verschillende uitdagingen kunt reageren:hoe u in de eerste plaats geluidscode schrijft, hoe u uw gebruikers op de hoogte houdt wanneer het onvoorspelbare gebeurt en hoe u uw database in een gezonde, consistente staat houdt.

Het voorzienbare repareren - Goede coderings-/testpraktijken

Het schrijven van betrouwbare, onderhoudbare computercode is deels kunst en deels technische discipline. Er zijn veel verschillende opvattingen over wat "goede code" is. Voor Javascript verwijs ik u nogmaals naar Eloquent Javascript. Elementen van goede codeerpraktijken zijn lay-out, naamgevingsconventies en programmastructuur. Daarnaast leer je pas echt wat werkt en wat niet door een combinatie van praktijkervaring en door naar het werk van anderen te kijken.

"Testen" is natuurlijk de procedure die u volgt om de betrouwbaarheid van uw code te bevestigen. Er kan worden vertrouwd op uw IDE en de browser (via de systeemfoutopsporingstool) om u duidelijk te vertellen wanneer uw syntaxis defect is of wanneer u uw programma in een staat hebt gebracht waarin een instructie niet kan worden uitgevoerd. Een bijzonder handige functie van de VSCode IDE is dat het u waarschuwt voor fouten terwijl u nog steeds code schrijft (dwz voordat u het probeert uit te voeren). Het zal in feite suggesties doen en u helpen om in de eerste plaats de juiste code te maken - een enorme tijdbesparing. Daarnaast moet u echter "scenario's" creëren waarin u, uitgaande van bekende beginvoorwaarden, geplande routes door uw toepassing volgt en controleert of de resultaten overeenkomen met de verwachtingen. U realiseert zich natuurlijk dat u dit telkens moet herhalen wanneer u wijzigingen aan uw systeem aanbrengt! Misschien wilt u kijken naar de "testrunner"-systemen die door reguliere professionele ontwikkelaars worden gebruikt om de procedure te systematiseren. "Jest" is een voorbeeld dat u misschien interessant vindt. Zoals eerder vermeld - serieuze, professionele ontwikkeling van IT-systemen is hard werken!

Het onvoorziene repareren - de Javascript "catch" -faciliteit

Als u zich zorgen maakt over de kwetsbaarheid van een codeblok, stelt het Javascript-systeem u in staat dit in een try{.. vulnerable code block...} catch{.. do something about it ...} te verpakken structuur. Dit betekent dat als iets in het codeblok een fout "gooit", de besturing wordt omgeleid naar de code in het catch { }-blok.

Wat betekent "gooi een fout"? Het betekent dat een stukje code heeft herkend dat er iets mis gaat en in het eenvoudigste geval een throw 'Explanation'; heeft uitgevoerd uitspraak. Hier is 'Explanation' een string die het probleem uitlegt. De throw statement maakt 'Explanation' beschikbaar voor catch(error) als error.message .

Die berichten die u in de browserconsole hebt gezien toen u foutieve code had gemaakt, zijn verschenen omdat de browser ze heeft "gegooid". Als u uw code in try-blokken plaatst (niet dat ik suggereer dat dit altijd een goed idee zou zijn), kunt u deze fouten opvangen en ze "afhandelen".

Dus, bijvoorbeeld, terwijl webapp-code als volgt:

let x = 1 / a;

waarbij a een variabele is die u niet hebt gedefinieerd, wordt door uw browser gestopt wanneer u deze uitvoert. Hoewel u hierdoor naar een leeg scherm blijft kijken, u weet dat je kunt vinden wat er mis is gegaan door naar de console in de systeemtools van de browser te kijken. Hier vind je een ReferenceError: a is not defined bericht. Maar uw gebruikers zullen dit natuurlijk niet weten - het enige wat ze zullen zien is een dode webapp.

Aan de andere kant:

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

zal een waarschuwingsbericht produceren dat duidelijk zichtbaar is voor de webapp-gebruiker.

Gezien het feit dat de "gegooide" fout diep begraven kan zijn in een complexe geneste hiërarchie van applicatiecode en SDK-functies, kun je je ook afvragen hoe Javascript erin slaagt om deze regeling te leveren. Ik verwijs u nogmaals naar Eloquent Javascript (hoofdstuk 8).

Voor een Firebase-webapp wilt u waarschijnlijk fouten 'vangen' die worden veroorzaakt door Firestore- of Cloud Storage-functies. Je hebt twee opties:terwijl een hele stapel code kan worden verpakt in de try/catch-regeling die ik zojuist heb beschreven, als je om de een of andere reden individuele functies wilt controleren, biedt Javascript je een .catch() methode die u kunt koppelen aan een Firestore-functieaanroep. Hier is een voorbeeld uit een Google-codelab:

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

Ik geef de voorkeur aan deze opstelling om blokken te proberen/vangen omdat ik denk dat het mijn code een beetje leesbaarder maakt.

Als je je afvraagt ​​hoe .catch werkt, is het antwoord dat Javascript deze "methode" automatisch biedt voor elke functie die een Belofte retourneert - en de meeste Firestore-functies retourneren Beloften. Voor achtergrondinformatie over beloften en het wait-sleutelwoord, bekijk mijn eerdere bericht:Het "wait"-sleutelwoord

Transacties

Zoals hierboven aangegeven, kunnen onvoorspelbare hardwareproblemen leiden tot beschadiging van een productiedatabase, tenzij webapp-software voldoende alert is op de mogelijkheid en erop toegerust is om ermee om te gaan.

Hier is een voorbeeld. U zult zich herinneren dat de toepassing "Boodschappenlijst" die is geïntroduceerd in "een eenvoudige webapp coderen" gebruikers in staat stelde lijsten met "aankoopartikelen" te maken. Stel je voor dat het "management" had besloten dat het een goed idee zou zijn om bij te houden hoe vaak een aankoopartikel op de boodschappenlijstjes van gebruikers verscheen. Dienovereenkomstig is een "purchaseMI"-verzameling met "running total"-documenten aan de database toegevoegd. Nu elke keer een purchaseItem wordt toegevoegd aan of verwijderd uit een boodschappenlijst, moet de webapp de corresponderende invoer aanpassen in purchaseMI.

Het probleem hiermee is dat een onhandige storing halverwege een dergelijke procedure de database in een corrupte staat zal achterlaten. Met zorg zou het mogelijk zijn om zo'n mislukking te 'vangen' en te proberen ermee om te gaan, maar in een complexere situatie zou dit geen eenvoudige taak zijn.

Het ziet er nog somberder uit als je bedenkt wat er kan gebeuren als je database "gelijktijdige" verzoeken van meerdere gebruikers verwerkt.

Stel dat twee gebruikers tegelijkertijd een userPurchase voor bijvoorbeeld "rolls" aan hun lijsten toevoegen. Elk van hen heeft dus toegang tot de aankoopMI-verzameling voor het lopende totaal voor "rollen" - en elk van hen heeft dus identieke waarden voor het huidige totaal voor dat item - laten we zeggen dat het op "10" staat. En ja - ik weet zeker dat je het probleem hebt gezien dat zich nu voordoet. Nadat ze elk hun update op het lopende totaal hebben toegepast, terwijl dit "12" zou moeten zijn, staat er eigenlijk gewoon "11". De database is nu beschadigd - de huidige waarde van het lopende totaalveld voor een "rolls" in purchaseMI komt niet overeen met de waarde die u zou krijgen als u zou zoeken naar "rolls" in userSHoppingLists.

We hebben hier wat hulp van Google nodig, omdat deze 'concurrency'-problemen te complex zijn om door de webapp te worden aangepakt. Wat we nodig hebben is een manier om een ​​"transactie" te definiëren - een reeks databasecommando's die ofwel allemaal slagen, ofwel allemaal worden weggegooid. Met een aldus gedeclareerde transactie heeft de webapp alleen te maken met het algehele resultaat - hij hoeft zich niet bezig te houden met de interne details van het proces.

Het antwoord van Google is om een ​​transaction . te geven object met methoden die kunnen worden gebruikt om CRUD-opdrachten te starten op een manier die hen in staat stelt met elkaar te communiceren. Deze transaction object is gemaakt door een runTransaction functie die op zijn beurt een functie start met de transaction object als argument. Dit omhult de reeks CRUD-commando's en definieert zo de transactie. Firestore kan dan stappen ondernemen om ervoor te zorgen, zonder verdere inspanning van onze kant, dat, hoewel de transactie kan mislukken, als de database consistent was voordat een transactie start, deze consistent blijft nadat deze is voltooid.

Om u een idee te geven hoe dit eruit ziet, volgt hier een voorbeeldcode voor een bijgewerkte versie van de verwijderfunctie van de "Boodschappenlijstjes"-webapp.

 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)});
 }

Ter toelichting:

  1. Ik moest runTransaction . toevoegen naar de import voor firebase/firestore/lite . Bijkomende voorbereidingen waren de creatie van een purchaseMI verzameling met documenten die zijn ingetoetst op userPurchase en die een veld userPurchaseTotal bevatten. Ik heb ook een regel toegevoegd die gratis lees-/schrijftoegang toestaat tot purchaseMI .

  2. De deleteDoc-functie die ik eerder gebruikte om een ​​shoppingLists-document te verwijderen, is nu vervangen door een transaction.delete functie. Alle CRUD-functies die ik mogelijk moet gebruiken, zijn op dezelfde subtiele manier gewijzigd - zie firebase.firestore.Transaction voor de documentatie van Google over het transactie-object. Merk op dat getDocs , het vraagformulier van getDoc wordt niet ondersteund door de transaction object.

    • transaction.get vervangt getDoc
    • transaction.set vervangt setDoc
    • transaction.update vervangt updateDoc
    • transaction.delete vervangt deleteDoc
  3. De volgorde waarin databasecommando's in het voorbeeld worden uitgevoerd, lijkt misschien onnatuurlijk. Dit komt omdat bij een Firestore-transactie alle "lees"-bewerkingen moeten worden voltooid voordat er updates worden gestart.

  4. Terwijl transaction.get retourneert nog steeds een belofte en moet daarom worden aangeroepen met een voorafgaand zoekwoord "wachten", geen van de andere transactiemethoden.

  5. Als Firestore detecteert dat een andere gebruiker de gegevens heeft gewijzigd die het zojuist heeft gelezen, maakt het een back-up van alles wat het heeft gedaan en voert het de transactie opnieuw uit. Een transactie kan dus meer dan één keer worden uitgevoerd en u moet dus oppassen voor eventuele verklaringen die "bijwerkingen" veroorzaken. Een update-instructie voor het tellerveld kan bijvoorbeeld chaos veroorzaken.

  6. Transacties kunnen naar maximaal 500 documenten schrijven en er is een limiet van ongeveer 20 MB op het opslagvolume dat door een transactie kan worden beïnvloed.

  7. De Transaction hier gebruikt concept - gedefinieerd als "een set van lezen en schrijven bewerkingen op een of meer documenten" - gaat gepaard met een Batched writes facility - "een set van write bewerkingen op een of meer documenten". Batched Writes zijn een stuk eenvoudiger dan Transactions en hebben de voorkeur, waar van toepassing.

  8. Cloudfuncties kunnen ook transacties gebruiken en in dit geval worden enkele van de hierboven beschreven beperkingen versoepeld - de Cloud Function-transactie-SDK ondersteunt bijvoorbeeld het queryformulier van get

Zoals je ziet, valt hier veel over te zeggen. Maar nu ik het onderwerp heb geïntroduceerd en een voorbeeld heb gegeven, denk ik dat het waarschijnlijk het beste zou zijn als ik gewoon zou stoppen en u de documentatie over transacties en batched schrijven van Google zou laten lezen. Misschien wil je ook wat testcode uitvoeren! Er is een uitstekende video verscholen in de Google-documenten waarnaar hierboven wordt verwezen en die ik u ook ten zeerste aanbeveel.

Concluderend, transacties zijn niet voor angsthazen, maar ze maken uw webapp een echt professioneel product. Veel succes!

Andere berichten in deze serie

Als je dit bericht interessant vond en meer wilt weten over Firebase, is het misschien de moeite waard om de Index van deze serie te bekijken.