Node.js bietet Entwicklern eine leistungsstarke Plattform für die Erstellung von Backend-Diensten. Mit der V8 JavaScript-Engine ermöglicht es die Ausführung von JavaScript-Code auf der Serverseite, und das auf eine Art und Weise, die den Umgang mit asynchronem, nicht-blockierendem Code vereinfacht. Die Integration zahlreicher nativer Module, die in der Node.js-Umgebung verfügbar sind, erleichtert die Entwicklung von Webanwendungen und -diensten erheblich. Diese Module decken eine breite Palette von Funktionen ab, von Netzwerkkommunikation und Dateisystemzugriff bis hin zu Kryptographie und Performance-Messung. Ein zentrales Element von Node.js ist jedoch nicht nur seine Architektur, sondern auch der npm-Paketmanager, der die Entwicklung und Verwaltung von Softwarekomponenten massiv vereinfacht.
Die Node.js-Module decken eine Vielzahl von Aufgaben ab: von der Implementierung kryptografischer Funktionen (node:crypto) bis hin zur Netzwerkkommunikation (node:net). Diese Module bieten nicht nur die Basisfunktionen für den Betrieb von Servern und Clients, sondern auch Werkzeuge zur Messung der Anwendungsperformance (node:perf_hooks) und zur Handhabung von Datenströmen (node:stream). In vielen Fällen sind diese Module nicht nur zur direkten Verwendung auf Servern vorgesehen, sondern auch als Unterstützung für Tools, die außerhalb des klassischen Webserver-Umfelds zum Einsatz kommen.
Ein zentraler Aspekt von Node.js ist seine Modularität und die Tatsache, dass es mit dem npm-Paketmanager ausgestattet ist. Dieser Paketmanager hat die Art und Weise revolutioniert, wie JavaScript-Entwickler mit externen Bibliotheken und Tools arbeiten. Mit npm können Entwickler aus einer riesigen Sammlung von mehr als einer Million Paketen wählen, um ihre Anwendungen mit neuen Funktionen auszustatten. Diese Pakete reichen von kleinen Hilfsfunktionen bis hin zu komplexen Frameworks für die Entwicklung kompletter Anwendungen. Besonders bemerkenswert ist, dass npm und Node.js nicht nur im Backend-Bereich, sondern auch für Frontend-Anwendungen von Bedeutung sind. Beispielsweise kann man mit Tools wie Webpack, TypeScript und Prettier den Entwicklungsprozess optimieren, auch wenn man nicht direkt Node als Webserver verwendet.
Webpack hilft dabei, Frontend-Anwendungen zu bündeln und für die Produktion vorzubereiten. TypeScript fügt statische Typisierung und zusätzliche Sprachfeatures zu JavaScript hinzu, was insbesondere bei großen Codebasen die Wartbarkeit und Skalierbarkeit fördert. Prettier sorgt wiederum für eine konsistente und automatische Formatierung des Codes, was insbesondere bei größeren Teams von unschätzbarem Wert ist, um den Code konsistent und fehlerfrei zu halten. Diese Tools ergänzen sich perfekt und machen den Entwicklungsprozess sowohl im Backend als auch im Frontend effizienter und zuverlässiger.
Allerdings gibt es auch berechtigte Bedenken hinsichtlich der Nutzung von Node.js, vor allem im Hinblick auf seine einzigartige Art der asynchronen, nicht-blockierenden Codeausführung. Für Entwickler, die mit dieser Denkweise noch nicht vertraut sind, kann es anfangs verwirrend und herausfordernd sein. Die Verwaltung von Code, der viele asynchrone Operationen umfasst, erfordert ein anderes Verständnis und eine angepasste Herangehensweise an Software-Design. Zudem sorgt die Verbreitung des CommonJS-Modulsystems zusammen mit der Unterstützung von ES-Modulen für zusätzliche Komplexität. Während Node beide Module unterstützt, kann die gleichzeitige Nutzung beider Systeme in einem Projekt zu Inkonsistenzen und Problemen führen, insbesondere für Anfänger.
Ein weiteres Problem ist die Größe des node_modules-Verzeichnisses, das bei großen Projekten schnell anwachsen kann. Projekte, die hunderte von Drittanbieterpaketen nutzen, benötigen eine regelmäßige Wartung und ein Management dieser Abhängigkeiten. Es ist nicht ungewöhnlich, dass Node-Projekte von Sicherheitslücken betroffen sind, die durch veraltete oder unsichere Pakete entstehen. Daher müssen Entwickler kontinuierlich darauf achten, dass alle genutzten Pakete aktuell sind und keine potenziellen Sicherheitsrisiken bestehen.
Ein schwerwiegendes Argument gegen den Einsatz von Node.js ist die Tatsache, dass es aufgrund seiner Single-Thread-Architektur nicht optimal für CPU-intensiven Code geeignet ist. Aufgaben wie die Bild- oder Videobearbeitung, die eine hohe Rechenleistung erfordern, können in Node.js zu Performanceengpässen führen. Auch wenn Node in der Lage ist, viele I/O-Operationen gleichzeitig zu verarbeiten, so ist es für schwere Berechnungen aufgrund der fehlenden Multi-Core-Nutzung weniger geeignet. In solchen Fällen müssen Entwickler möglicherweise auf zusätzliche Tools oder alternative Lösungen zurückgreifen, um eine höhere Rechenleistung zu erreichen.
Sicherheit ist ein weiteres großes Thema bei Node.js. Skripte, die auf dieser Plattform ausgeführt werden, haben weitreichenden Zugriff auf das Dateisystem, das Netzwerk und andere Systemressourcen. Dies stellt ein erhebliches Risiko dar, wenn man auf unsicheren oder schlecht gewarteten Code von Drittanbietern angewiesen ist. Node.js hat bereits ein neues Berechtigungsmodell eingeführt, um den Zugriff auf bestimmte Ressourcen während der Ausführung von Prozessen einzuschränken, jedoch sind diese Restriktionen nicht standardmäßig aktiviert, was zusätzliche Aufmerksamkeit und Vorsicht von den Entwicklern erfordert.
Die Dynamische Typisierung von JavaScript ist ein weiteres Hindernis. Während es für kleinere Projekte von Vorteil sein kann, dass keine expliziten Typen deklariert werden müssen, kann dies bei größeren, komplexeren Anwendungen zu Problemen führen. Insbesondere die Fehlersuche und das Debugging von Code werden schwieriger, wenn der Code zur Laufzeit verändert wird. Hier hilft es, statische Typisierung wie in TypeScript zu integrieren, um diese Probleme zu vermeiden.
Node.js bleibt trotz dieser Herausforderungen eine der leistungsfähigsten und vielseitigsten Plattformen für die Backend-Entwicklung. Seine Fähigkeit, JavaScript auf der Serverseite auszuführen, gepaart mit der Flexibilität und Vielfalt der verfügbaren Module und Tools, hat die Art und Weise, wie moderne Webanwendungen entwickelt werden, tiefgreifend verändert. Dennoch sollten Entwickler die Besonderheiten und Herausforderungen von Node verstehen, um die bestmöglichen Entscheidungen für ihre Projekte treffen zu können.
Wie man mit asynchronen Funktionen und Callbacks in Node.js arbeitet
In der Entwicklung mit Node.js stößt man oft auf das Konzept der asynchronen Funktionen. Diese sind entscheidend, um die Effizienz von Anwendungen zu steigern, insbesondere wenn langwierige oder blockierende Operationen durchgeführt werden müssen. Der Kern dieses Konzepts liegt in der Verwendung von Callbacks, Promises und der neueren async/await-Syntax. Doch nicht alle Operationen lassen sich einfach asynchronisieren. In einigen Fällen müssen Entwickler Wege finden, um den Hauptthread nicht zu blockieren, während langwierige Aufgaben ausgeführt werden.
Ein typisches Beispiel für eine asynchrone Callback-Funktion ist das Lesen einer Datei, wobei das Ergebnis der Datei nach Abschluss der Operation durch den Callback zurückgegeben wird. In Node.js könnte das so aussehen:
Hier wird die Datei asynchron gelesen, und der Callback erhält entweder einen Fehler oder die Daten. Wenn alles gut geht, wird ein Array von Zeilen zurückgegeben, das die Inhalte der Datei widerspiegelt. Ein Fehler in der Dateioperation führt dazu, dass der Fehler an den Callback übergeben wird, der dann weiterverarbeitet werden kann. Diese Art von Funktion ist typisch für Node.js, da sie das Event-Driven-Model von Node unterstützt und die Event-Schleife effizient hält.
Das Hauptmerkmal von Callbacks in Node.js ist das sogenannte "Error-First"-Prinzip: Der erste Parameter des Callbacks ist immer der Fehler (wenn einer auftritt). Dies trägt zur besseren Lesbarkeit und einer konsistenten Fehlerbehandlung bei. Die typische Struktur einer solchen Funktion ist, dass der Callback am Ende der Argumentliste steht und alle Fehler und Ergebnisse dort behandelt werden.
In einem weiteren Beispiel, bei dem Zahlen aus einer Datei verarbeitet werden, könnte der Code so aussehen:
In diesem Beispiel wird die Datei numbers.txt eingelesen und in Zahlen umgewandelt. Anschließend werden die ungeraden Zahlen gezählt. Der Vorteil dieser Technik liegt in der Einfachheit und der Fähigkeit, mit großen Datenmengen umzugehen, ohne den Hauptthread zu blockieren.
Doch obwohl diese Technik weit verbreitet ist, gibt es auch mehrere Nachteile. Ein häufig genanntes Problem ist die sogenannte "Pyramide des Schreckens" (auch "Callback-Hölle" genannt), die auftritt, wenn mehrere asynchrone Operationen miteinander verknüpft sind und Callbacks ineinander verschachtelt werden müssen. Dies führt zu unübersichtlichem und schwer wartbarem Code.
Ein weiteres Problem mit Callbacks ist, dass die Kontrolle darüber, was nach einer asynchronen Aufgabe passieren soll, in den Callbacks selbst liegt. Der Entwickler muss darauf vertrauen, dass der Callback korrekt arbeitet, was zu Problemen führen kann, wenn diese Annahme nicht zutrifft. Ein besserer Ansatz, um diese Probleme zu umgehen, ist die Verwendung von Promises.
Im Vergleich zu Callbacks bieten Promises den Vorteil, dass Erfolg und Fehler separat behandelt werden können, was den Code klarer und weniger anfällig für Fehler macht. So könnte die oben gezeigte readFileAsArray-Funktion auch mit Promises verwendet werden:
In diesem Beispiel verwendet die Funktion .then(), um das Ergebnis der asynchronen Operation zu verarbeiten, und .catch(), um Fehler zu behandeln. Diese Methode ist klarer und ermöglicht das einfache Verketten mehrerer asynchroner Operationen, ohne dass der Code in eine tief verschachtelte Struktur gerät.
Um diese Vorteile zu nutzen, muss die ursprüngliche Funktion so angepasst werden, dass sie ein Promise-Objekt zurückgibt:
Mit dieser Anpassung kann die Funktion sowohl als Callback- als auch als Promise-basierte Lösung verwendet werden. Der Entwickler kann selbst entscheiden, welches Muster er bevorzugt.
Obwohl Promises viele Vorteile bieten, ist es immer noch möglich, "Pyramiden" zu erzeugen, wenn mehrere asynchrone Operationen miteinander verschachtelt werden. Ein einfacherer Weg, dies zu verhindern, ist das Verketten der Operationen durch die .then()-Methode, anstatt mehrere Callbacks ineinander zu schachteln. So bleibt der Code übersichtlich und verständlich.
Ein weiterer Fortschritt in der Handhabung von asynchronen Operationen ist die Verwendung der async/await-Syntax. Diese ermöglicht eine noch klarere und synchrone Struktur des Codes, indem die asynchronen Operationen wie normale, synchrone Funktionsaufrufe behandelt werden. Ein try/catch-Block ermöglicht zudem die einfache Fehlerbehandlung.
Die Auswahl der richtigen Methode hängt oft von der Komplexität der Anwendung ab. Während Callbacks immer noch eine valide Option darstellen, bieten Promises und async/await eine elegantere Lösung, insbesondere bei komplexeren Anwendungsfällen.
Wie lässt sich Node.js skalieren und verfügbar machen?
Die Skalierbarkeit und Verfügbarkeit sind entscheidende Faktoren für den Erfolg von Anwendungen, die auf Node.js basieren. Die Architektur von Node.js selbst ist stark auf Event-Driven-Programmierung und asynchrone Operationen ausgerichtet, was es zu einer leistungsstarken Plattform für skalierbare Systeme macht. Jedoch bringt diese Architektur auch besondere Herausforderungen mit sich, die es zu bewältigen gilt, um die Ausfallsicherheit und die Leistung einer Anwendung zu optimieren.
Node.js basiert auf einem Single-Threaded-Modell, was bedeutet, dass nur ein einzelner Prozess für die Bearbeitung von Anfragen zuständig ist. Dies stellt bei steigender Last ein Problem dar, da ein einzelner Prozess nur eine begrenzte Anzahl von Anfragen gleichzeitig bearbeiten kann. Aus diesem Grund ist die Skalierbarkeit eines Node.js-Systems von zentraler Bedeutung. Eine gängige Strategie zur Skalierung von Node.js-Anwendungen ist der Einsatz von Cluster-Modulen, die es ermöglichen, mehrere Prozesse auf einem System auszuführen, um die Last zu verteilen und eine bessere Nutzung der verfügbaren Rechenressourcen zu erzielen.
Das Cluster-Modul von Node.js ermöglicht es, einen Master-Prozess zu erstellen, der als Manager fungiert, und mehrere Worker-Prozesse zu starten, die die eigentliche Arbeit erledigen. Jeder Worker-Prozess kann unabhängig arbeiten und eine eigene Event-Schleife betreiben, was bedeutet, dass mehrere Anfragen gleichzeitig bearbeitet werden können. Diese Methode verbessert nicht nur die Leistung, sondern sorgt auch für eine hohe Verfügbarkeit, da bei einem Ausfall eines Workers die anderen weiterhin Anfragen bearbeiten können.
Ein weiterer wichtiger Aspekt bei der Skalierung und Sicherstellung der Verfügbarkeit von Node.js-Anwendungen ist die Nutzung von Broadcasting-Techniken. In verteilten Systemen müssen Nachrichten und Daten oft an alle beteiligten Komponenten übermittelt werden. Das bedeutet, dass eine effiziente Kommunikation zwischen den Prozessen gewährleistet werden muss, um die Konsistenz und Synchronisation der Daten sicherzustellen. Hierzu gibt es verschiedene Strategien, wie etwa die Verwendung von Messaging-Systemen oder Datenbanken, die das Broadcasting von Informationen übernehmen.
Ein oft unterschätztes Thema in der Skalierung ist die Verfügbarkeit von Node.js-Anwendungen. Es reicht nicht aus, dass die Anwendung einfach nur funktioniert – sie muss jederzeit verfügbar sein, auch wenn einzelne Komponenten ausfallen. In einem hochverfügbaren System müssen Mechanismen wie Zero-Downtime-Restarts integriert werden. Diese ermöglichen es, Anwendungen ohne Unterbrechung neu zu starten, um beispielsweise Fehler zu beheben oder Updates einzuspielen. Durch den Einsatz solcher Techniken können auch Wartungsarbeiten ohne Auswirkungen auf den Betrieb durchgeführt werden, was besonders in produktiven Umgebungen wichtig ist.
Ebenso essenziell für den Betrieb einer Node.js-Anwendung ist das Management des Zustands. In einem verteilten System müssen die verschiedenen Prozesse stets auf den gleichen Stand der Daten zugreifen können. Dies erfordert eine zentrale Verwaltung des Zustands, um Konflikte und Inkonsistenzen zu vermeiden. Hier kommen sogenannte Prozessmanager ins Spiel. Sie helfen dabei, die einzelnen Worker-Prozesse zu überwachen und sicherzustellen, dass sie korrekt laufen und bei einem Absturz automatisch neu gestartet werden. Zudem stellen sie sicher, dass die Last gleichmäßig auf alle Worker verteilt wird.
Neben der technischen Skalierung und Verfügbarkeit gibt es jedoch noch weitere Faktoren, die für den langfristigen Erfolg einer Node.js-Anwendung wichtig sind. Ein tiefes Verständnis der internen Funktionsweise von Node.js ist erforderlich, um die richtigen Entscheidungen bei der Wahl von Bibliotheken und Tools zu treffen. Viele Entwickler neigen dazu, externe Frameworks und Bibliotheken wie Express.js oder Koa zu verwenden, ohne zu wissen, wie die zugrunde liegende Node.js-Architektur funktioniert. Dies kann dazu führen, dass das Potenzial von Node.js nicht vollständig ausgeschöpft wird und ineffiziente Lösungen entstehen. Ein solides Verständnis der Node.js-Interna hilft dabei, die bestmögliche Architektur für die eigenen Anforderungen zu wählen und Libraries gezielt und effizient zu integrieren.
Zusätzlich sollte jeder Entwickler darauf achten, dass die Codequalität und Wartbarkeit nicht auf der Strecke bleiben. Auch wenn Node.js durch seine Asynchronität und Event-gesteuerte Architektur eine hohe Performance bietet, kann schlecht strukturierter Code schnell zu Problemen führen, die die Skalierbarkeit und Verfügbarkeit negativ beeinflussen. Tools wie ESLint oder Prettier helfen dabei, den Code konsistent zu halten und potenzielle Fehlerquellen frühzeitig zu identifizieren.
Die Bedeutung von Test- und Entwicklungspraktiken sollte ebenfalls nicht unterschätzt werden. Skalierbarkeit und hohe Verfügbarkeit sind nur dann gewährleistet, wenn die Anwendung gründlich getestet wird. Dabei sind vor allem Lasttests und Tests zur Überprüfung der Robustheit unter extremen Bedingungen wichtig, um sicherzustellen, dass die Anwendung auch unter hoher Belastung stabil bleibt.
Endtext.
Wie funktioniert ein grundlegender Webserver in Node.js und wie verwendet man externe Pakete?
Im Folgenden wird das grundlegende Konzept eines einfachen Webservers in Node.js erläutert. Der Node-Prozess läuft in der Regel unendlich weiter, es sei denn, es treten unerwartete Fehler auf. Dies geschieht, weil der Server im Hintergrund auf eingehende Anfragen wartet, um auf diese zu reagieren. Obwohl das Beispiel eines Webservers sehr einfach ist, enthält es eine Reihe von wichtigen Konzepten, die es zu verstehen gilt.
Ein zentrales Element dieses Beispiels ist die require-Funktion, die Teil des ursprünglichen Modul-Management-Systems von Node.js ist. Mit ihr kann ein Modul (wie zum Beispiel server.js) die Funktionen eines anderen Moduls (wie node:http) nutzen. Durch das Anfordern des node:http-Moduls hängt das server.js-Modul nun von ihm ab und kann ohne dieses nicht ausgeführt werden. Eine weitere Möglichkeit, die Funktionen eines Moduls zu verwenden, besteht darin, eine import-Anweisung aus den ES-Modulen zu verwenden. ES-Module sind der moderne ECMAScript-Standard zum Arbeiten mit Modulen in JavaScript. In diesem Buch werden wir hauptsächlich mit ES-Modulen arbeiten, da sie das bevorzugte Modul-System von Node.js sind. Dennoch ist es auch wichtig, das ursprüngliche CommonJS-Modul-System zu lernen, da viele bestehende Node.js-Projekte und -Bibliotheken noch darauf basieren und es wahrscheinlich ist, dass man damit arbeiten muss, auch wenn man ein neues Projekt startet.
Das node:http-Modul gehört zur Standardbibliothek von Node.js, und man muss nichts zusätzlich installieren, um es zu nutzen. Allerdings muss es, wie jedes andere Modul, durch require oder import eingebunden werden. In einer REPL-Sitzung von Node.js sind eingebaute Module wie node:http global verfügbar, ohne dass sie ausdrücklich mit require geladen werden müssen. Das gilt jedoch nicht für ausführbare Skripte – hier müssen alle Module, einschließlich der eingebauten, zuerst deklariert werden.
Das Beispiel in diesem Abschnitt lädt nur die Funktion createServer, eine von vielen Funktionen und anderen Objekten, die im node:http-Modul zur Verfügung stehen. Mit der Funktion createServer wird ein Serverobjekt erstellt, wobei ihr Argument eine weitere Funktion ist, die als RequestListener bekannt ist. Dabei handelt es sich um eine Listener-Funktion, die mit einem bestimmten Ereignis verknüpft ist und ausgeführt wird, wenn dieses Ereignis ausgelöst wird. In diesem Fall wird die Listener-Funktion jedes Mal ausgeführt, wenn eine eingehende Verbindung zum Webserver hergestellt wird.
Die Listener-Funktion empfängt zwei Argumente: das request-Objekt (im Beispiel als req bezeichnet) und das response-Objekt (im Beispiel als res bezeichnet). Das request-Objekt enthält Daten über die eingehende Anfrage, etwa die angeforderte URL oder die IP-Adresse des Clients. Das response-Objekt ermöglicht es, eine Antwort an den anfragenden Client zu senden. In diesem Beispiel wird der Statuscode der Antwort auf 200 gesetzt, was für eine erfolgreiche Antwort steht, und der Content-Type-Header wird auf text/plain gesetzt. Danach wird die Textnachricht „Hello World“ mit der end-Methode des res-Objekts an den Client gesendet.
Die Funktion createServer erstellt lediglich das Serverobjekt, aktiviert es jedoch nicht. Um den Webserver tatsächlich in Betrieb zu nehmen, muss die listen-Methode aufgerufen werden. Diese Methode akzeptiert viele Argumente, wie zum Beispiel die Portnummer und die Host-Adresse des Servers. Das letzte Argument der Methode ist eine Funktion, die ausgeführt wird, sobald der Server erfolgreich auf dem angegebenen Port läuft. In diesem Beispiel wird eine Konsole-Nachricht ausgegeben, die bestätigt, dass der Server erfolgreich läuft.
Die Funktionen, die der createServer- und listen-Methode übergeben werden, sind Beispiele für Handler-Funktionen, die mit Ereignissen verbunden sind, die eine asynchrone Operation auslösen. In Kapitel 3 werden wir genauer betrachten, wie diese Ereignisse und deren Handler-Funktionen verwaltet werden.
Ein weiteres wichtiges Konzept ist die Handhabung von externen Paketen und Modulen. Node.js bietet dafür das Paketverwaltungssystem npm, das eine einfache Kommandozeilen-Schnittstelle zur Verfügung stellt, mit der externe Pakete in einem Node-Projekt installiert und verwaltet werden können. Ein npm-Paket kann ein einzelnes Modul oder eine Sammlung von Modulen sein, die zusammen eine API zur Verfügung stellen.
Um ein externes npm-Paket zu installieren, verwendet man den Befehl npm install. Ein Beispiel für ein solches Paket ist die populäre JavaScript-Bibliothek lodash, die viele nützliche Methoden für Zahlen, Strings, Arrays und Objekte bereitstellt. Der folgende Befehl lädt das Paket herunter:
Dieser Befehl lädt das Paket aus dem npm-Registry herunter und legt es im node_modules-Verzeichnis ab. In deinem Node-Code kannst du dann das lodash-Modul verwenden, indem du es mit require einbindest:
Dieser Code gibt eine zufällige Zahl zwischen 1 und 99 aus, wobei _ ein üblicher Name für das lodash-Modul ist, aber auch jeder andere Name verwendet werden kann.
Sobald du ein externes Modul in einem Node-Projekt verwendest, ist es wichtig, dies in der package.json-Datei festzuhalten. Diese Datei enthält alle Abhängigkeiten des Projekts. Wenn du ein Modul installierst, wird es automatisch in der dependencies-Sektion der package.json vermerkt. In einem Teamprojekt sollten alle Entwickler die Datei mit den Abhängigkeiten kennen, um die gleichen Module zu installieren.
Das package.json-Format bietet auch Raum für Metadaten zum Projekt, wie Name, Version und Beschreibung, sowie für Skripte, die über die Kommandozeile ausgeführt werden können. Beispiel:
Beim Arbeiten mit externen Paketen ist es wichtig, den Befehl npm install zu verwenden, um die benötigten Pakete zu installieren und sicherzustellen, dass alle Entwickler Zugriff auf die gleichen Abhängigkeiten haben. Wenn ein Paket nicht mehr benötigt wird, kann es mit npm uninstall aus der package.json entfernt werden.
Es gibt auch Pakete, die nur in der Entwicklungsumgebung notwendig sind, wie etwa ESLint. Solche Pakete werden durch Hinzufügen der Option --save-dev installiert, damit sie nur in der Entwicklungsumgebung und nicht in der Produktionsumgebung verwendet werden.
Wie man die Transformation von Variablen bei Wahrscheinlichkeitsverteilungen bestimmt: Einblicke und Anwendung
Wie funktionieren Verdampfer für volatile Anästhetika und wie beeinflussen sie die Wirkung von Sevofluran?
Wie kann ITIL4 im Gesundheitswesen fortlaufend verbessert werden?
Wie gesellschaftliche Strukturen die Stellung der Frau im Rigveda prägten

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский