Aufgabe 09-2a - Client trifft Server
Lernziele
Abschnitt betitelt „Lernziele“- Du wählst begründet zwischen dem Datenzugriff in der Server-Komponente und
fetchim Client. - Du baust ein Formular mit Prüfung im Browser, Schutz gegen Doppelklick und Fehleranzeige aus dem Fehlerformat des Vertrags.
- Du machst die drei Zustände einer Mutation (unterwegs, gelungen, gescheitert) in der Oberfläche sichtbar.
- Du aktualisierst die Anzeige nach einer Mutation, ohne die Seite neu zu laden.
Ausgangslage
Abschnitt betitelt „Ausgangslage“Die Buffet-API aus Aufgabe 09-1a bekommt ein Gesicht: eine Weboberfläche, die über deine eigene Schnittstelle liest und schreibt. Client und Server leben im selben Projekt, und alles aus den React- und Async-Wochen kommt hier zusammen (siehe Kapitel Next.js: API und Datenfluss). Die ersten zwei Punkte der Stufe Wesentlich_überwiegend passen nach Einheit 9.2 (Daten lesen), alles Weitere nach Einheit 9.3 (Daten verändern).
Ausgangspunkt ist dein Projekt aus Aufgabe 09-1a, mindestens im Stand Wesentlich_vollständig. Fehlt dir dieser Stand, bekommst du von deiner Lehrkraft ein Startprojekt.
Allgemein
Abschnitt betitelt „Allgemein“Projektordner anlegen siehe https://qiz.at/projektordner
- Lies vorher kurz https://qiz.at/assignmentsabgabe durch.
- Nur vollständige Abgaben (siehe https://qiz.at/rules) können positiv bewertet werden.
- Abgabe- und Testdetails siehe https://qiz.at/assignmentsabgabe
- Die Übung liegt als Unterordner
uebung-09-2ain deinem Jahres-RepositorySJ26_27_4AHITM_SEW_<Benutzername>_exercises. Kopier dafür den Ordneruebung-09-1aohnenode_modules/und.next/und arbeite allein in der Kopie weiter;uebung-09-1ableibt, wie es abgegeben wurde. - Die Rolle wird weiter über den Header
x-rolenachgestellt (buffetoderstudent). - Schriftliche Antworten stehen in der
README.mddes Projekts.npm run buildmuss fehlerfrei durchlaufen,npm run lintohne Fehler;node_modules/und.next/kommen nicht ins Repository. Alle Artikel und Bestellungen sind erfunden.
Wesentlich_überwiegend
Abschnitt betitelt „Wesentlich_überwiegend“Aufgabe
Abschnitt betitelt „Aufgabe“- Die Seite
/buffetzeigt das Tagesangebot mit Name, Preis in Euro und verfügbarer Menge. Entscheide: Liest die Server-Komponente direkt über dein Modullib/store.ts, oder holt sie die Daten perfetchüber die eigene API? Begründe die Wahl in zwei Sätzen in der README. - Ausverkaufte Artikel werden angezeigt, aber sichtbar deaktiviert. Das ergibt sich aus der Menge; es gibt dafür keinen eigenen State.
- Das Bestellformular ist eine Client-Komponente: Positionen zusammenklicken, höchstens 5 Positionen und je Artikel höchstens die verfügbare Menge. Der Knopf “Bestellen” ist gesperrt, solange die Bestellung ungültig ist.
- Absenden per
fetchanPOST /api/ordersnach dem Muster aus Einheit 9.3: In-Flight-Flag gegen den Doppelklick, Ladezustand am Knopf, bei einer Ablehnung die Meldung aus dem Fehlerformat (error.message), bei einem Netzwerkfehler eine eigene Meldung. - Bei Erfolg erscheint die Abholnummer groß, das Formular wird geleert, und das Tagesangebot zeigt die neuen Mengen, ohne dass die Seite neu geladen wird.
- Der Client kennt die Grenzen, aber der Server prüft noch einmal. Beantworte in der README: Warum wird doppelt geprüft, und welche der beiden Prüfungen wäre verzichtbar?
Tipp: Legst du über die API einen Artikel an und die Seite zeigt ihn nicht, dann lies in Einheit 9.2 den Abschnitt Zwei Kopien desselben Moduls. Preise kommen als Cent; die Umrechnung in Euro ist Sache der Anzeige. Prüf am Ende mit
npm run build, dass/buffetnicht mit○erscheint.
Bewertungskriterien
Abschnitt betitelt „Bewertungskriterien“Vollständig, wenn:
npm run buildundnpm run lintfehlerfrei durchlaufen./buffetdas Tagesangebot aktuell zeigt, auch nachnpm run buildundnpm start, und die Wahl des Datenwegs begründet ist.- ausverkaufte Artikel sichtbar deaktiviert sind und sich nicht bestellen lassen.
- das Formular alle drei Zustände zeigt: gesperrter Knopf mit Ladetext, Abholnummer bei Erfolg, Meldung aus
error.messagebei einer Ablehnung. - nach einer Bestellung die Mengen ohne Neuladen stimmen und die Frage zur doppelten Prüfung richtig beantwortet ist.
Wie unter Allgemein.
Wesentlich_vollständig
Abschnitt betitelt „Wesentlich_vollständig“Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.
Aufgabe
Abschnitt betitelt „Aufgabe“Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieses Projekts (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):
- Die Seite
/buffet/adminzeigt die Bestellliste mit Abholnummer, Positionen und Status. Jede Bestellung hat die Knöpfe “fertig” und “abgeholt”, diePATCH /api/orders/[id]aufrufen. - Nach jeder Aktion aktualisiert sich die Liste. Wähl dafür
router.refresh()oder ein Update im State und begründe die Wahl in einem Satz in der README. - Fehler sind sichtbar: Antwortet die API mit
409, etwa weil “abgeholt” vor “fertig” geklickt wurde, erscheint die Meldung direkt bei der betroffenen Bestellung. Keinalert, kein stiller Fehlschlag. - Der Header
x-rolewird bei jedemfetchmitgeschickt. Kapsle das in einer kleinen Hilfsfunktion inlib/api-client.ts, damit es in Aufgabe 12a an genau einer Stelle ersetzt werden kann.
Tipp: Jede Bestellung braucht ihren eigenen Fehler- und Ladezustand; am einfachsten ist eine eigene Client-Komponente pro Zeile. Die Hilfsfunktion nimmt Pfad und Optionen wie
fetchund ergänzt nur den Header.
Bewertungskriterien
Abschnitt betitelt „Bewertungskriterien“Vollständig, wenn:
/buffet/adminalle Bestellungen aktuell zeigt und beide Knöpfe den Status über die API ändern.- die Liste nach jeder Aktion ohne Neuladen stimmt und die Wahl des Verfahrens begründet ist.
- eine
409-Antwort als Meldung bei der betroffenen Bestellung erscheint und die anderen Zeilen unberührt bleiben. - kein
fetchden Headerx-roleselbst setzt; alle gehen über die Hilfsfunktion.
Wie unter Allgemein.
PlusPlus Aufgabe
Abschnitt betitelt „PlusPlus Aufgabe“Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.
Aufgabe
Abschnitt betitelt „Aufgabe“Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieses Projekts (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):
- Störungstest: Stopp den Server, während die Seite
/buffetoffen ist, und schick eine Bestellung ab. Halt indisruption.mdfest, was die Oberfläche zeigt und welcher Zustand zurückbleibt. - Härte das Formular: Der Netzwerkfehler erscheint als verständliche Meldung mit einem Knopf “Erneut versuchen”. Nach dem Neustart des Servers schickt dieser Knopf dieselbe Bestellung ab, ohne dass die Seite neu geladen wird. Kein Formularinhalt geht verloren.
- Wiederhol den Störungstest und halt das neue Verhalten in
disruption.mdfest.
Tipp: Wirft
fetch, gibt es gar keine Antwort; das ist ein anderer Fall als eine Antwort mit4xxoder5xx(Einheit 5.2). Nach dem Neustart ist der In-Memory-Speicher wieder im Startzustand; das gehört in dein Protokoll.
Bewertungskriterien
Abschnitt betitelt „Bewertungskriterien“Vollständig, wenn:
disruption.mddas Verhalten vor und nach der Härtung beschreibt.- der Netzwerkfehler mit eigener Meldung und dem Knopf “Erneut versuchen” erscheint.
- der erneute Versuch nach dem Neustart ohne Neuladen gelingt und die gewählten Positionen erhalten bleiben.
Wie unter Allgemein, dazu disruption.md.
MandalorianPlus Aufgabe
Abschnitt betitelt „MandalorianPlus Aufgabe“Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.
Aufgabe
Abschnitt betitelt „Aufgabe“Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieses Projekts (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):
- Prüf die heikle Frage der Doppelbestellung: Der Server hat die Bestellung angenommen, aber die Antwort hat den Client nie erreicht. Was passiert bei “Erneut versuchen”? Stell den Fall nach und halt das Verhalten deiner API in
disruption.mdfest. - Behebe das Problem: Der Client vergibt für jede Bestellung eine eigene Kennung und schickt sie mit; der Server legt zu einer Kennung höchstens eine Bestellung an und antwortet bei der Wiederholung mit der schon angelegten. Trag die Änderung in
api.mdsamt Changelog ein. - Erklär in drei Sätzen, warum ein wiederholtes
POSTheikel ist, ein wiederholtesPATCHauf denselben Zielstatus aber nicht. - Führ den ganzen Bestellablauf samt Störungstest als Folge von Screenshots oder als kurzes Video vor.
Tipp: Den Fall “angenommen, aber Antwort verloren” stellst du nach, indem der Route Handler vor der Antwort einige Sekunden wartet und du den Server in dieser Zeit stoppst; mit einem Speicher im Arbeitsspeicher ist die Bestellung dann allerdings auch weg. Überleg, wie du die Wiederholung trotzdem zeigen kannst. Eine eindeutige Kennung liefert im Browser
crypto.randomUUID(). Stichwort: Idempotenz (Einheit 5.1).
Bewertungskriterien
Abschnitt betitelt „Bewertungskriterien“Vollständig, wenn:
- der Fall der Doppelbestellung nachgestellt und in
disruption.mdbeschrieben ist. - zwei Anfragen mit derselben Kennung genau eine Bestellung ergeben und
api.mddie Änderung samt Changelog enthält. - die Erklärung zu
POSTundPATCHden Begriff Idempotenz richtig verwendet. - die Screenshots oder das Video den Ablauf samt Störungstest zeigen.
Wie unter Allgemein, dazu die Screenshots im Ordner screenshots/ oder das Video.
Wissenscheck
Abschnitt betitelt „Wissenscheck“- Nach welchen Kriterien entscheidest du zwischen dem Datenzugriff in der Server-Komponente und
fetchim Client? - Warum ersetzt die Prüfung im Browser die Prüfung am Server nicht?
- Welche drei Zustände jeder Mutation muss die Oberfläche sichtbar machen?
- Was bewirkt
router.refresh()nach einer gelungenen Mutation? - Warum ist ein wiederholtes
POSTheikel, ein wiederholtesPATCHauf denselben Zielstatus aber nicht?
Zeit dafür
Abschnitt betitelt „Zeit dafür“Durchschnittlich bis Wesentlich_vollständig: ca. 100 min
Autoren
Abschnitt betitelt „Autoren“- Oliver Maurhart (MAO)
- Michael Gabriel (GAM)
CC-BY-SA