Zum Inhalt springen

Grundlagen des System Design

SAP Implementation Consulting & ERP - Startupistan Deutschland · Block I · Lernnotizen zur Wiederholung.


Die eine Idee, die diesen ganzen Block neu einordnet: Derselbe Code kann in völlig unterschiedlichen Systemen leben. Eine Funktion, die eine Karte auf dem Bildschirm zeichnet, kümmert sich nicht darum, ob die Daten in einer lokalen Datei lagen oder vom anderen Ende der Welt kamen - der Code bleibt, während sich das System um ihn herum ändert. System Design ist die eigenständige Fähigkeit, zu entscheiden, welche Teile es gibt, wofür jedes Teil zuständig ist und wie sie miteinander sprechen. Genau dafür wird ein Implementierungsberater bezahlt: nicht für clevere Funktionen, sondern für das Wissen, was eine Organisation hat, was sie braucht und wie alles zusammenhängen muss.

Jeder Austausch in diesem ganzen Thema hat genau zwei Rollen. Der Client fragt; der Server antwortet. Das war’s - es ist kein Gespräch, und der Server spricht nie zuerst. Stell dir ein Restaurant vor: Ich (Client) bitte die Küche (Server) um ein Gericht; die Küche bereitet es zu und schickt es raus, geht aber nie unaufgefordert an meinen Tisch.

ClientServer
RolleFragt (sendet einen Request)Antwortet (sendet eine Response)
Wer beginntErgreift immer die InitiativeAntwortet nur - nie zuerst
Beispiel hierDer Browser, der meine Seite ausführtEin Programm, das auf Requests lauscht
KernwahrheitKann von jedem verändert/umgangen werdenDer einzige Teilnehmer, den ich kontrolliere

Der tiefste Punkt: Ein Server ist eine Rolle, keine Maschine. Jedes Programm, das lauscht und antwortet, ist ein Server, egal wo es läuft - sogar ein Dev-Server auf meinem eigenen Laptop. Ein einzelner Laptop kann beide Rollen gleichzeitig innehaben.

Der Roundtrip - was passiert, wenn ein Browser einer URL folgt

Abschnitt betitelt „Der Roundtrip - was passiert, wenn ein Browser einer URL folgt“

Zuerst zerfällt eine URL (Uniform Resource Locator) in benannte Teile:

https://www.example.com:443/artists/list?genre=folk#top
scheme host port path query fragment
scheme = die Regeln (meist https)host = wen fragenport = nummerierte Türpath = was ich willquery = zusätzliches name=value-Detailfragment = Notiz an den Browser, wird nie gesendet

Dann die Reise. Beachte DNS (Domain Name System) - das Telefonbuch des Internets, das einen Namen wie example.com in eine numerische Adresse verwandelt:

DNS fragenName → Nummer
→
Verbindenzu host:port öffnen
→
RequestBrowser fragt
→
ResponseServer antwortet
→
Assets holenCSS, JS, Bilder
Ein Seitenaufruf besteht aus vielen Requests, nicht aus einem - die erste Response nennt jedes Stylesheet, Skript und Bild, und der Browser fragt für jedes erneut an. Jede Zeile im Network-Tab der DevTools ist einer dieser Requests.

Die erste Design-Entscheidung ist meist, Daten aus dem Code heraus zu bewegen. Vier Gründe - alles Design-Gründe, keine Programmier-Gründe:

  • Sie ändern sich unabhängig - einen Datensatz hinzuzufügen sollte nicht bedeuten, Code zu bearbeiten und neu zu deployen.
  • Sie können zu groß zum Mitliefern sein - fünf Datensätze wiegen nichts; 500.000 Kundendatensätze können nicht an jeden Besucher gesendet werden.
  • Sie werden geteilt, also müssen sie übereinstimmen - zwei Personen müssen dieselbe Liste sehen, was eine autoritative Kopie erfordert, die beide erreichen können.
  • Manches muss privat bleiben - alles in heruntergeladenem Code ist öffentlich; Zugangsdaten, unveröffentlichte Preise und persönliche Datensätze können dort nicht liegen.

Der Speicher kann kein Netzwerk überqueren - nur Text kann das. Also gibt es eine vereinbarte Art, ein Objekt als Text zu schreiben, es zu senden und auf der anderen Seite wieder aufzubauen: JSON (JavaScript Object Notation). Es sieht aus wie ein JS-Objektliteral, aber mit strengeren Regeln.

{
"name": "Ada",
"genre": "Country",
"songs": 5
}

Regeln, die die Datei kaputtmachen, wenn man sie ignoriert: jeder Key in doppelten Anführungszeichen, keine nachgestellten Kommata, keine Kommentare und keine Funktionen / kein undefined - JSON trägt nur Daten. Zwei Methoden bewegen sich zwischen den Welten:

const artist = { name: "Ada", genre: "Country" };
const text = JSON.stringify(artist); // object → text: '{"name":"Ada",...}'
const back = JSON.parse(text); // text → object
console.log(back.name); // "Ada"

Eine .json-Datei ist nur Text auf der Festplatte; sie wird erst zu Objekten, wenn etwas sie parst. (Praktische Angewohnheit: JSON.stringify(obj, null, 2) gibt lesbaren, eingerückten Text aus.)

Wohin Formulardaten gehen, nachdem der Server sie erhalten hat

Abschnitt betitelt „Wohin Formulardaten gehen, nachdem der Server sie erhalten hat“

Wenn eine Übermittlung einen Server erreicht, tut er drei Dinge, und das mittlere ist der ganze Sinn:

  1. Validiert erneut - alles, was der Browser geprüft hat, plus Regeln, die der Browser nie könnte (ist diese E-Mail bereits registriert?).
  2. Speichert, was er akzeptiert, an einem dauerhaften Ort, der den Request überlebt.
  3. Antwortet und teilt dem Browser mit, ob es funktioniert hat.

Profis teilen eine Anwendung in drei Schichten, jede mit einer Verantwortung:

Presentationzeigt & sammelt - entscheidet nie
→
ApplicationRegeln & Entscheidungen - das Geschäft
→
Datagespeicherte Fakten, die Requests überdauern
Presentation zeigt, Application entscheidet, Data bleibt bestehen. Die Trennlinien existieren, damit jede Schicht neu gebaut werden kann, ohne die anderen zu brechen - Systeme leben Jahre und ändern sich Stück für Stück.

Die Grenzen sind logisch, nicht physisch - die drei könnten sich heute einen Laptop teilen und sich in der Produktion über viele Maschinen verteilen. Und hier kommt die Pointe für diese Karriere: Die Application-Schicht ist der Ort, an dem ein Unternehmen tatsächlich lebt - Preisregeln, Freigabeprozesse, wer was tun darf. Ein Enterprise-System wie SAP ist diese mittlere Schicht, gebaut im industriellen Maßstab, und sie an eine reale Organisation anzupassen ist genau die Aufgabe des Implementierungsberaters.

Eine JSON-Datei ist eine ehrliche Datenschicht für fünf Datensätze; reale Systeme wachsen schnell über Dateien hinaus. Eine Datenbank ist ein Programm, das für drei Aufgaben gebaut ist, die Dateien nicht können:

  • Abfragen (Querying) - präzise Fragen über Ausschnitte beantworten (“alle Folk-Künstler, sortiert, gezählt”), ohne alles zu verschicken.
  • Gleichzeitiger Zugriff (Concurrent access) - viele gleichzeitige Leser und Schreiber konsistent halten.
  • Regeln - Daten ablehnen, die die vorgegebene Form brechen.

Zwei Familien: relationale Datenbanken speichern Daten in Tabellen mit definierten Spalten und erzwingen Beziehungen zwischen ihnen (die Familie, auf der SAP aufbaut, abgefragt mit SQL - Structured Query Language); nicht-relationale speichern Dokumente oder Key-Value-Paare, wo Flexibilität wichtiger ist als feste Struktur. Eine Platzierungsregel gilt immer: Der Browser spricht nie direkt mit der Datenbank - jeder Request geht zuerst durch die mittlere Schicht, denn dort leben Berechtigung und Validierung.

Eine Umgebung ist eine vollständige Kopie des Systems, die zu einem Zweck läuft:

UmgebungZweckCharakter
DevelopmentWo Entwickler bauenBewusst zerbrechlich (mein Laptop)
Test / StagingÄnderungen unter realistischen Bedingungen prüfenBevor echte Nutzer sie treffen
ProductionEchte Nutzer, echte Daten, echte KonsequenzenHier passiert nichts beiläufig

Die Disziplin: Derselbe Code läuft in allen dreien, gegen unterschiedliche Daten. Was sich zwischen ihnen ändert, ist die Konfiguration - welche Adressen, welche Datenbank, welche Keys - nie der Code selbst. Eine Änderung zu promoten heißt, sie die Leiter hochzubewegen (dev → test → prod) mit Verifizierung auf jeder Sprosse, sodass ein Daten-zerstörender Bug im Test gefangen und nicht in der Produktion entdeckt wird. Die SAP-Arbeit läuft auf einer formalisierten Version genau dieser Leiter.

Synchroner Code beendet jede Zeile, bevor die nächste startet. Das ist in Ordnung, bis eine Zeile auf die Welt warten muss - eine Datei, einen Klick und vor allem einen Netzwerk-Request. JavaScript führt eine Seite auf einem einzelnen Thread aus: eine Spur, auf der alles der Reihe nach passiert. Wenn Code in dieser Spur wartet (oder sich fünf Sekunden in einer Schleife dreht), kann nichts anderes laufen - keine Klicks, keine anderen Handler, nicht einmal das Repaint, das den Bildschirm neu zeichnet. Dieses Gefühl des eingefrorenen Tabs ist Blockieren: irgendein Code hält die Spur.

Asynchroner Code bricht das Warten absichtlich auf - eine Aufgabe starten, sofort weitermachen, das Ergebnis behandeln, wann immer es ankommt. Gleiche Gesamtzeit; der Unterschied ist, was ich getan habe, während es arbeitete. (Wie eine Nummer an einer Theke zu ziehen und sich hinzusetzen, statt eingefroren an der Kasse zu stehen.)

Code läuft innerhalb von Execution Contexts - ein globaler Kontext, der erstellt wird, wenn das Skript startet, plus ein frischer Function Context für jeden Aufruf. Die Engine verfolgt ihren Ort mit dem Call Stack: ein Aufruf wird gepusht, eine Rückkehr poppt ihn ab, und das oberste Element ist das, was gerade läuft.

function prepare(a) { return "Now playing " + format(a); }
function format(a) { return a.name.toUpperCase(); }
console.log(prepare({ name: "Ada" }));
// stack grows: global → prepare → format, then unwinds as each returns

Ein Stack Trace in einem Fehler ist einfach eine Momentaufnahme dieses Stacks im Moment des Bruchs, den innersten Aufruf zuerst. (Ausufernde Rekursion - eine Funktion, die sich selbst ohne Stopp aufruft - lässt ihn überlaufen: RangeError: Maximum call stack size exceeded.)

Der Stack ist der einzige Ort, an dem Code läuft. Wie also wartet ein Thread auf tausend Dinge? Der Browser (rund um die Engine) übernimmt das eigentliche Warten nebenbei und gibt die Spur sofort frei. Wenn eine wartende Aufgabe fertig ist, drängt sich ihr Callback nicht vor - er stellt sich in eine Queue. Der Event Loop ist der Dispatcher mit einer ewigen Regel: Wenn der Call Stack leer ist, nimm den nächsten Callback aus der Queue und pushe ihn. Nie bevor der Stack leer ist, nie laufenden Code unterbrechend.

Call Stackführt eine Sache aus
→
Browser wartetTimer / Netzwerk, off-thread
→
Microtask-QueuePromises - leert sich zuerst
→
Task-QueueTimer-Callbacks
→
Event Loop→ Stack, wenn leer
Zwei Queues: die Microtask-Queue (Promise-Reaktionen) leert sich immer vollständig, bevor die Task-Queue (Timer-Callbacks) an die Reihe kommt.

Diese eine Regel erklärt ein berühmtes Rätsel:

console.log("one");
setTimeout(() => console.log("two"), 0);
console.log("three");
// prints: one, three, two

Ein 0ms-Timer bedeutet nicht jetzt - er bedeutet, dass der Callback sofort in die Task-Queue eintritt und erst nachdem der Stack den aktuellen Durchlauf beendet hat, ausgeliefert wird. Die Verzögerung eines Timers ist eine Mindestwartezeit, nie ein exakter Termin.

Callbacks sind das erste Werkzeug: setTimeout(fn, ms) führt fn einmal nach mindestens ms aus (abbrechen mit clearTimeout(id)); setInterval(fn, ms) wiederholt, bis clearInterval(id). Sie funktionieren, aber verkettetes Warten (lade A, dann B, das A braucht, dann C, das B braucht) schachtelt Callbacks in eine nach rechts wandernde Treppe - die berüchtigte Callback Hell. Dieser Schmerz ist der Grund, warum Promises existieren.

Ein Promise ist ein Objekt, das für ein Ergebnis steht, das noch nicht angekommen ist. Ein Objekt zu sein (keine Fire-and-forget-Anweisung) ist seine Stärke: Es kann gespeichert, übergeben und zurückgegeben werden. Es befindet sich immer in genau einem von drei Zuständen:

Pendingnoch nicht angekommen
→
Fulfilledhält den Wert
Pendingnoch nicht angekommen
→
Rejectedhält den Grund
Einmal fulfilled oder rejected ist ein Promise settled - dauerhaft. Es ändert seinen Zustand nie wieder, feuert nie zweimal, geht nie zurück. Deshalb sind Promises vertrauenswürdig.

Handler werden mit drei Methoden angehängt - then() für Erfolg, catch() für Fehler, finally() für Aufräumarbeiten, die in jedem Fall laufen müssen (klassisch das Entfernen eines Ladespinners):

loadArtists()
.then((artists) => renderCards(artists))
.catch((error) => console.log("Load failed", error.message))
.finally(() => (statusBox.textContent = ""));

Promises lassen sich außerdem verketten (chain) und glätten die Treppe: Gib einen Wert aus then() zurück, und das nächste then() erhält ihn; gib ein Promise zurück, und die Kette wartet, bis es settled ist. Ein einziges catch() am Ende deckt jeden Schritt darüber ab - ein Fehler irgendwo überspringt den Rest und landet dort.

Aber der meiste alltägliche async-Code nutzt heute zwei Keywords, die Promise-Code synchron aussehen lassen. async markiert eine Funktion (sie gibt immer ein Promise zurück); await pausiert nur diese Funktion, bis ein Promise settled ist, und gibt dann den Wert zurück - ohne den Thread zu blockieren, denn darunter ist es immer noch Promise-Maschinerie:

async function showEverything() {
const artists = await loadArtists(); // reads like plain assignment...
renderCards(artists);
const label = await loadLabel(artists[0]); // ...but each is a real wait
showLabel(label);
}

Standardmäßig await für Lesbarkeit; greife nur dann zu einer then()-Kette, wenn die Arbeit ehrlich eine Pipeline von Transformationen ist.

Umschließe awaitete Arbeit mit try...catch - eine Rejection landet in catch genau so, wie sie es im .catch() einer Kette täte. finally läuft unabhängig davon:

async function showArtists() {
try {
const artists = await loadArtists();
renderCards(artists);
} catch (error) {
statusBox.textContent = "We could not load the artists.";
} finally {
spinner.remove();
}
}

Zwei Angewohnheiten, die es sich zu verankern lohnt: throw ein Error-Objekt, nie einen nackten String (ein Error trägt message, name und den Stack Trace, den jedes Werkzeug erwartet - TypeError, ReferenceError usw. sind nur seine Subklassen); und wenn ich etwas fange, das ich nicht behandeln kann, füge Kontext hinzu und wirf erneut (rethrow), statt es zu verschlucken. Die teuerste Angewohnheit in professionellem Code ist der leere catch-Block - der Fehler geschah, die Beweise wurden vernichtet, und jemand verbringt einen Tag damit, ihn neu zu entdecken.

await-Zeilen laufen nacheinander, was für abhängige Schritte richtig ist. Aber drei unabhängige Requests, nacheinander awaitet, brauchen drei Roundtrips, wo sie einen brauchen könnten. Die Kombinatoren führen Promises zusammen aus; wähle zwischen ihnen, indem du fragst: “Was soll passieren, wenn einer fehlschlägt?”

KombinatorWartet aufBei einem Fehlschlag
Promise.alldass alle fulfillenrejected sofort - nutze, wenn Ergebnisse nur zusammen Sinn ergeben
Promise.allSettleddass alle settlenrejected nie - meldet jedes Ergebnis; nutze, wenn Teilergebnisse helfen
Promise.anyden ersten Erfolgignoriert Fehler, außer alle schlagen fehl - nutze, wenn jede Quelle genügt
const [artists, label] = await Promise.all([loadArtists(), loadLabel()]);

Eine API (Application Programming Interface) ist eine vereinbarte Art, wie ein Programm ein anderes um etwas bittet. Der Teil “vereinbart” trägt das ganze Gewicht - zwei von Fremden geschriebene Programme, in verschiedenen Sprachen, kooperieren, weil beide dieselbe Vereinbarung einhalten. Die hier gemeinte Art ist eine Web-API: ein über das Netzwerk erreichbarer Server, der mit Daten antwortet, meist JSON. Zwei Klarstellungen, die endlose Verwirrung verhindern: Eine API ist keine Datenbank (sie ist der Empfangstresen, der entscheidet, was gefragt werden darf; die Speicherung liegt dahinter) und keine Website (eine Website antwortet mit Seiten für Menschen; eine API antwortet mit Daten für Programme).

Die professionelle Angewohnheit ist, die Doku vor dem Schreiben von Code zu lesen und fünf Dinge festzustellen:

Adresse (Endpoint)Methode - Art des FragensParameter, die sie akzeptiertResponse-FormLimits - wie oft / wie viel

Ein Protokoll ist ein vereinbartes Regelwerk für einen Austausch. Das des Webs ist HTTP (HyperText Transfer Protocol): ein Request, eine Response, dann fertig. Jeder Request trägt eine Methode, eine Adresse, Header und manchmal einen Body; jede Response trägt einen Statuscode, Header und meist einen Body.

Die Eigenschaft, die ganze Systeme prägt: HTTP ist zustandslos (stateless). Der Server erinnert sich nicht an meinen vorherigen Request - jeder Request muss alles mitführen, was er braucht, jedes Mal. Wenn sich eine Seite an mich “erinnert” (eingeloggt, Warenkorb behalten), ist diese Erinnerung auf zustandslosem HTTP aufgebaut, indem mit jedem einzelnen Request ein identifizierender Token gesendet wird. Genau deshalb existieren Sessions und Tokens überhaupt. (Das S in https = dasselbe HTTP, in Verschlüsselung verpackt, sodass niemand auf der Strecke es lesen oder verändern kann.)

Die Methode ist das erste Wort jedes Requests - ein Versprechen über die Absicht, auf das sich Browser, Caches und Server alle verlassen. Ein GET, der heimlich Daten ändert, ist kein schlechter Stil; er ist ein Bug, über den das korrekte Verhalten anderer Software stolpert.

MethodeBedeutungÄndert Daten?
GETUm etwas bittenNein - sicher wiederholbar/cachebar
POSTEtwas Neues senden, das erstellt werden sollJa
PUTEtwas ganz ersetzenJa
PATCHEinen Teil von etwas ändernJa
DELETEEtwas entfernenJa

Jede Response beginnt mit einem dreistelligen Statuscode; die erste Ziffer sortiert ihn in eine Familie. Die in der Praxis wichtigste Linie verläuft zwischen 4xx (der Fragende hat gefehlt) und 5xx (der Antwortende ist kaputtgegangen) - diese eine Ziffer sagt mir, welche Seite ich debuggen muss.

FamilieBedeutungAlltägliche Codes
1xxNoch am Arbeiten(selten gesehen)
2xxErfolg200 OK · 201 Created
3xxWoanders suchen (Redirect)301 Moved Permanently
4xxDer Request war falsch400 Bad Request · 401 Unauthorized · 403 Forbidden · 404 Not Found · 429 Too Many Requests
5xxDer Server ist gescheitert500 Internal Server Error

Schnell gelesen: 401 = wer bist du? (Identität erforderlich); 403 = Identität bekannt, Berechtigung verweigert; 429 = du hast das Rate-Limit überschritten.

Header sind Name-Wert-Paare, die den Austausch beschreiben - der Umschlag, nicht der Brief. Der eine, den ich selbst setze, ist Content-Type, der kennzeichnet, was der Body ist (ein Body ist nur Bytes, bis er gekennzeichnet wird); JSON sagt Content-Type: application/json. Drei weitere, die man kennen sollte: Accept (welche Antwort der Client verarbeiten kann), Authorization (Identitätsnachweis), User-Agent (welche Software fragt).

fetch() ist die eingebaute Art des Browsers, HTTP-Requests zu machen. Der Teil, der alle überrascht: fetch() gibt ein Promise zurück, das mit einem Response-Objekt fulfillt, nicht mit den Daten. Das Lesen von JSON braucht also zwei awaits - zuerst die Header, dann den Body:

const response = await fetch("http://localhost:3000/artists");
const artists = await response.json(); // second await parses the body

Die Response trägt außerdem ok (true für 2xx - gleich die wichtigste Eigenschaft), status, statusText, headers, url und text() für Nicht-JSON-Bodys.

Die Falle, die jeden Anfänger erwischt: fetch() rejected NICHT bei einem 404 oder 500. Aus Sicht des Netzwerks wurde ein Request gesendet und eine Antwort kam zurück, also fulfillt das Promise - selbst wenn die Antwort “nicht gefunden” sagt. Nur gar keine Antwort (Server unten, Netzwerk weg) bringt fetch() zum Rejecten. Also ist Prüfen meine Aufgabe:

async function loadArtists() {
const response = await fetch("http://localhost:3000/artists");
if (!response.ok) {
throw new Error("Request failed with status " + response.status);
}
return response.json();
}

Diese vier Zeilen geben jedem Aufrufer einen ehrlichen Vertrag: fulfilled = gute Daten, rejected = irgendein Fehler überhaupt. Drei Fehlerfamilien, mit denen zu rechnen ist: Netzwerkfehler (keine Antwort - fetch rejected selbst), Request-Fehler (eine Antwort sagte, der Request sei falsch - gefangen von der ok-Prüfung) und Fehler innerhalb einer erfolgreichen Response (Status 200, aber der Body meldet ein Problem). Ein fehlgeschlagener Request darf nie eine stillschweigend leere Seite bedeuten - zeige eine ehrliche Meldung, räume den Ladezustand in finally auf.

Ein Origin ist die Kombination aus Scheme + Host + Port - ändere eines davon und es ist ein anderer Origin. Standardmäßig hindert ein Browser eine Seite daran, Responses zu lesen, die von einem anderen Origin geholt wurden. CORS (Cross-Origin Resource Sharing) ist der Mechanismus, mit dem der antwortende Server diese Regel lockert, indem er in einem Response-Header (Access-Control-Allow-...) erklärt, welche Origins seine Antworten lesen dürfen. Die Regel existiert, um den Besucher zu schützen, nicht die API: Mein Browser trägt meine Cookies und Logins, also könnte ohne sie jede Seite, die ich öffne, still mein Webmail von innerhalb meines eingeloggten Browsers holen und die Antwort lesen. Zwei Dinge zum Merken: Ein CORS-Fehler zeigt sich als eine unverwechselbare Konsolenfehlermeldung, und die Behebung gehört immer auf den Server - kein Client-Code kann Zustimmung erteilen, und ein Request, der von einem Terminal-Werkzeug funktioniert, kann im Browser trotzdem fehlschlagen.

Das Senden von Daten füllt das zweite Argument von fetch() aus - das Options-Objekt, das Methode, Header und Body benennt:

form.addEventListener("submit", async (event) => {
event.preventDefault();
const newArtist = { name: nameInput.value, genre: genreInput.value };
const options = {
method: "POST",
headers: { "Content-Type": "application/json" }, // label the body
body: JSON.stringify(newArtist), // object → travel-safe text
};
const response = await fetch("http://localhost:3000/artists", options);
console.log(response.status); // 201 Created - the right answer to a good POST
});

Jedes Stück ist bereits vertraut: Die Methode ist die sendende Art des Fragens, der Content-Type-Header kennzeichnet den Body, und JSON.stringify() verwandelt das Objekt in Text, der reisen kann. Nach einem erfolgreichen POST bleiben die Daten bestehen (persist) - aktualisiere und sie sind noch da; öffne einen zweiten Tab und sie sind auch dort. Zwei Clients, eine Wahrheit, übereinstimmend, weil beide denselben Server fragen - was der Grund ist, warum Server überhaupt existieren.

Echte APIs müssen wissen, wer fragt. Drei Formen des Nachweises decken das meiste ab:

FormWas sie nachweistGesendet als
API-KeyWelches Projekt fragtEin langer geheimer String, meist in einem Header
Bearer-TokenEin angemeldeter NutzerDer Authorization-Header
OAuthAnmeldung über ein anderes KontoEin Protokoll, das Tokens erzeugt, kein Passwort geteilt

Eine Regel übertrumpft die Details: Ein Geheimnis, das in Client-seitigem JavaScript platziert ist, ist kein Geheimnis - alles, was der Browser herunterlädt, kann jeder lesen. Also halten echte Systeme den Key auf ihrem eigenen Server (dessen Code kein Besucher lesen kann) und lassen ihn den authentifizierten Request machen. Identität ermöglicht auch Rate Limiting - zu zählen, wie oft ein Key fragt, und 429 zurückzugeben, wenn er sein Kontingent überschreitet.

Die Wörter, die Systeme beschreiben, die auf die reale Welt treffen - jedes ist ein Trade-off, nie ein kostenloser Gewinn:

BegriffKlartext
LatencyZeit zwischen Fragen und Empfangen (die Verzögerung auf den Pfeilen)
ThroughputWie viel pro Zeiteinheit fließen kann (kann gut sein, selbst wenn die Latency schlecht ist)
LoadWie viel gleichzeitig gefragt wird; jedes System hat eine Kapazität
Scaling up / outEine stärkere Maschine vs. mehr Maschinen, die sich die Arbeit teilen
CachingWiederverwenden einer gespeicherten Antwort - schnell, aber kann veraltete Daten liefern
Single Point of FailureEin Teil, dessen Ausfall alles zum Erliegen bringt; die Lösung ist Redundanz

Die ehrliche professionelle Frage ist nie “welche Option ist am besten?”, sondern “welche Kosten kann ich mir leisten?” - das schnellste System ist oft das, das die Daten von gestern liefert, und ob das akzeptabel ist, ist eine geschäftliche Entscheidung, keine technische.

Fast nichts läuft allein - die Lohnabrechnung spricht mit der Zeiterfassung, ein Shop spricht mit Zahlungs- und Lagersystemen. Integration ist, dass Systeme Requests mit Systemen austauschen, nach demselben Request/Response-Modell, jetzt ohne Mensch an einem der beiden Enden. Was es funktionieren lässt, ist der Vertrag (Contract): die vereinbarten Adressen, Formen und Regeln des Austauschs (genau die fünf Dinge, die aus der API-Doku gelesen werden). Das tiefe Prinzip - der Vertrag zählt mehr als die Interna beider Systeme. Jede Seite kann morgen komplett neu geschrieben werden und die Integration überlebt, solange der Vertrag hält. Enterprise-Implementierungsarbeit, allen voran SAP, ist größtenteils genau das: was mit was verbunden ist, unter welchem Vertrag, mit welchen Daten, die wann fließen.

  • “Ein Server ist eine Maschine.” Ein Server ist eine Rolle; hinter einer stark frequentierten Adresse stehen viele Maschinen, die sie sich teilen.
  • “Mehr Maschinen bedeutet immer schneller.” Scaling out fügt Koordinationskosten hinzu, und Arbeit, die sich nicht teilen lässt, kann nicht verteilt werden - manchmal ist es langsamer. Messen, nicht annehmen.
  • “Dem Browser kann man vertrauen, weil ich die Seite geschrieben habe.” Ich habe die Seite geschrieben, die ehrliche Besucher ausführen; jeder kann sie bearbeiten oder umgehen. Validierung und Berechtigung leben auf dem Server.
  • “System Design zählt nur bei großer Skalierung.” Selbst ein System mit fünf Datensätzen hat Schichten, eine Origin-Grenze, einen Vertrag, Latency und einen Single Point of Failure. Skalierung erhöht den Einsatz bei diesen Fragen; sie erschafft sie nicht.

KernpunktKurz gemerkt
System DesignDie Teile, ihre Verantwortungen und ihre Kommunikation festlegen - getrennt vom Coden
Client vs. ServerClient fragt, Server antwortet; Server spricht nie zuerst
Server ist eine RolleJedes Programm, das lauscht und antwortet - keine bestimmte Maschine
RoundtripDNS → verbinden → Request → Response → Assets holen; ein Aufruf = viele Requests
Daten aus dem CodeÄndern sich unabhängig, zu groß zum Mitliefern, geteilt, manchmal privat
JSONTextform, in der Daten reisen; Keys in Anführungszeichen, keine nachgestellten Kommata/Kommentare/Funktionen
FormulardatenServer validiert erneut, speichert, antwortet - Server-Prüfungen sind die echte Verteidigung
Drei-SchichtenPresentation zeigt · Application entscheidet · Data bleibt bestehen - SAP ist die Mitte im großen Maßstab
DatenbankQuerying + Concurrency + Regeln; Browser erreicht sie nie außer über den Server
UmgebungenDev / Test / Prod - gleicher Code, andere Daten/Config; die Leiter hochpromoten
BlockierenEinzelner Thread; Code, der die Spur hält, friert Klicks, Handler, Rendering ein
Call StackAufrufe pushen, Rückkehr poppt; ein Stack Trace ist sein Foto beim Fehler
Event LoopCallbacks aus der Queue laufen nur, wenn der Stack leer ist; Microtasks vor Tasks
PromiseObjekt für ein zukünftiges Ergebnis; pending → fulfilled/rejected, Settling ist dauerhaft
async/awaitasync gibt ein Promise zurück; await pausiert nur diese Funktion, kein Blockieren
Fehlerbehandlungtry/catch um await; Error-Objekte werfen; nie verschlucken - Kontext hinzufügen, rethrow
Promise.allFührt Unabhängige zusammen aus; rejected, wenn eines fehlschlägt (wählen nach “was, wenn eines scheitert?”)
APIVereinbarte Art, wie Programme einander fragen; keine Datenbank, keine Website
HTTP statelessServer vergisst jeden Request; alles Gemerkte reist jedes Mal mit
MethodenGET fragt · POST erstellt · PUT ersetzt · PATCH ändert · DELETE entfernt
Statuscodes2xx ok · 3xx woanders · 4xx Fragender hat gefehlt · 5xx Server kaputt
HeaderBeschreiben den Austausch; Content-Type kennzeichnet den Body
fetchGibt eine Response zurück, keine Daten → zwei awaits (Header, dann Body)
fetch-FalleRejected nicht bei 404/500 - prüfe response.ok selbst
CORSBrowser blockiert Cross-Origin-Lesen; Server erteilt Zustimmung; schützt den Besucher
POST-Datenmethod + Content-Type + JSON.stringify-Body; bleibt über Refresh hinaus bestehen
AuthAPI-Key / Bearer-Token / OAuth; ein Geheimnis in Client-JS ist kein Geheimnis
SkalierungLatency, Throughput, Load, Cache, Single Point of Failure - alles Trade-offs
IntegrationSysteme, die unter einem Vertrag sprechen; der Vertrag übertrumpft die Interna