{"id":22395,"date":"2022-02-27T18:53:59","date_gmt":"2022-02-27T17:53:59","guid":{"rendered":"https:\/\/blog.mi.hdm-stuttgart.de\/?p=22395"},"modified":"2023-06-18T17:46:45","modified_gmt":"2023-06-18T15:46:45","slug":"applikationsinfrastruktur-einer-modernen-web-anwendung","status":"publish","type":"post","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2022\/02\/27\/applikationsinfrastruktur-einer-modernen-web-anwendung\/","title":{"rendered":"Applikationsinfrastruktur einer modernen Web-Anwendung"},"content":{"rendered":"\n<p><em><strong>ein Artikel von Nicolas Wyderka, Niklas Schildhauer, Lucas Cr\u00e4mer und Jannik Smidt<\/strong><\/em><\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2022\/02\/Bildschirmfoto_2022-02-27_um_18.59.07.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"596\" data-attachment-id=\"22427\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2022\/02\/27\/applikationsinfrastruktur-einer-modernen-web-anwendung\/bildschirmfoto_2022-02-27_um_18-59-07\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2022\/02\/Bildschirmfoto_2022-02-27_um_18.59.07.png\" data-orig-size=\"1220,710\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"Bildschirmfoto_2022-02-27_um_18.59.07\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2022\/02\/Bildschirmfoto_2022-02-27_um_18.59.07-1024x596.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2022\/02\/Bildschirmfoto_2022-02-27_um_18.59.07-1024x596.png\" alt=\"\" class=\"wp-image-22427\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2022\/02\/Bildschirmfoto_2022-02-27_um_18.59.07-1024x596.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2022\/02\/Bildschirmfoto_2022-02-27_um_18.59.07-300x175.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2022\/02\/Bildschirmfoto_2022-02-27_um_18.59.07-768x447.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2022\/02\/Bildschirmfoto_2022-02-27_um_18.59.07.png 1220w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Projektbeschreibung<\/h1>\n\n\n\n<p>In diesem Blogeintrag wird die Entwicklung der Applikation- und Infrastruktur des Studienprojekts sharetopia beschrieben. Als Teil der Vorlesung System Engineering and Management wurde besonders darauf geachtet, die Anwendung nach heutigen Best Practices zu entwickeln und dabei kosteneffizient zu agieren<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>Bei sharetopia handelt es sich um eine Vermietungsplattform, in welcher Mieter und Vermieter von Produkten aller Art vermittelt werden. In der Anwendung kann ein Vermieter ein Produkt anlegen und es zur Vermietung freigeben. Der Mieter kann \u00fcber eine Suche bestimmte Produkte finden und eine Mietanfrage an den Vermieter senden. Die Desktop-Webanwendung wurde dieses Semester im Rahmen der Vorlesung Web Application Architecture entwickelt. Dabei wurde als Framework im Frontend Vue.js und im Backend Spring verwendet.<\/p>\n\n\n\n<p>Um eine zukunftssichere Anwendung f\u00fcr den Endbenutzer zur Verf\u00fcgung zu stellen, ist eines unsere Hauptziele eine skalierbare und performante L\u00f6sung zu entwickeln. Daf\u00fcr ist es das Ziel, ein Kubernetes Cluster f\u00fcr die einzelnen Services der Anwendung aufzubauen.&nbsp;<\/p>\n\n\n\n<p>Zus\u00e4tzlich soll eine effiziente CI\/CD-Pipeline f\u00fcr dieses Cluster zur Verf\u00fcgung stehen. Diese Pipeline soll nur beim erfolgreichen Absolvieren von Integrations- und Unit-Tests die Anwendung deployen. Zur bestm\u00f6glichen Unterst\u00fctzung w\u00e4hrend der Anwendungsentwicklung, ist ein Ziel das Aufsetzen von Development, Staging und Production Environments, welche die CI\/CD-Pipelines durchlaufen und anschlie\u00dfend in die Kubernetes-Cluster deployt werden sollen.<\/p>\n\n\n\n<p>Um bei dem Deployment und der sp\u00e4teren Nutzung des Clusters eine \u00fcbersichtliche \u00dcberwachung aller Vorg\u00e4nge zu behalten, soll ebenfalls ein Logging Framework in die Anwendung integriert werden und diese Logs durch eine Logstash Pipeline weiterverarbeitet und analysiert werden k\u00f6nnen.<\/p>\n\n\n\n<p>Da es sich bei dem Projekt um ein Studentenprojekt handelt, ist ein weiteres Ziel, die Kosten f\u00fcr die Infrastruktur m\u00f6glichst gering zu halten und verschiedene Provider nach Kosten-Nutzung zu vergleichen.<\/p>\n\n\n\n<p>Zusammengefasst ergeben sich folgende Ziele:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Erstellung eines robusten Testing-Konzepts<\/li><li>Deployment in einem Kubernetes Cluster<\/li><li>Nutzung von CI\/CD-Pipelines mit Environments<\/li><li>\u00dcberwachung der Anwendung durch Monitoring und Logging Konzept&nbsp;<\/li><li>Auswahl m\u00f6glichst kosteneffizienter und standardisierte Technologien<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Aufbau<\/h2>\n\n\n\n<p>Der folgende Blogartikel ist in die Kapitel Applikation und Infrastruktur aufgeteilt. Im ersten Teil wird zuerst auf die Architektur und die Auswahl der Technologien eingegangen. Dabei wird auch die Umsetzung des Testing-Konzepts beschrieben.&nbsp;<\/p>\n\n\n\n<p>Im Kapitel Infrastruktur wird zuerst auf die Auswahl des verwendeten Cloud-Anbieters eingegangen und anschlie\u00dfend die Umsetzung der Infrastruktur-Ziele beschrieben. Dazu geh\u00f6ren der Aufbau einer CI\/CD-Pipeline, das Aufsetzen eines Kubernetes Clusters, sowie die Umsetzung und Erstellung von Logging und Monitoring Konzepten zur Messung von Performance-Metriken.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Applikation<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Architektur<\/h2>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh3.googleusercontent.com\/fVcnqdASaU6q_bEUPC1bpsE29PHLCOVXAQzVfbdxVZuYzxiDKHCwu8a96zTLU00YhQKzoxavZSBLOAzrLgyCG7ZadRxpE69u7bxg7MdZK6LRsbNupm6OoR38Z4ejJ1MPYB62zkrv\" alt=\"\" \/><figcaption>Architekturkonzept<\/figcaption><\/figure>\n\n\n\n<p>Die Architektur der Webapplikation ist aufgeteilt zwischen einem Frontend und einem Backend. Diese zwei Komponenten kommunizieren mittels HTTP-Requests. Das Frontend besteht aus einer Multipage-Applikation, welche mit Vue.js erstellt wurde. Innerhalb des Frontends wird ebenfalls TailwindCSS verwendet, um ein Responsive Design zu erm\u00f6glichen. Innerhalb der Vue.js Applikation werden HTTP-Requests an das Backend gesendet, um Daten zu persistieren oder abzurufen. Das Backend setzt eine Microservice-Architektur um, welche mithilfe von Kubernetes verwaltet wird. Dies erm\u00f6glicht eine gute separation-of-concern zwischen den verschiedenen Services. Insgesamt gibt es vier verschiedene Services, den Product-Service, den Search-service, den User-Service und den Payment-Service. Jeder dieser Services ist ein separater Pod in dem Kubernetes-Cluster und kann unabh\u00e4ngig von den anderen Services verwaltet und gewartet werden. Die Services sind Java-Spring Applikationen und stellen die ben\u00f6tigten Endpunkte f\u00fcr das Frontend bereit. F\u00fcr die Persistierung der Daten existieren zwei Datenbanken, eine NoSQL-Datenbank, um die Inserate der Benutzer zu speichern und eine SQL-Datenbank f\u00fcr Bezahlungen. F\u00fcr eine schnelle Suche in der NoSQL-Datenbank wird ebenfalls ein ElasticSearch-Container gehostet.<\/p>\n\n\n\n<p>F\u00fcr die Authentifizierung wird der externe Service Cognito von AWS genutzt. Cognito stellt alle wichtigen Funktionen, sowie die Erstellung von Tokens f\u00fcr die HTTP-Requests bereit. Gleichzeitig stellt es die Sicherheit der Userdaten sicher. Rich Content und Mediendateien werden in einem S3-Bucket von AWS gespeichert.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Technologien<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Frontend<\/h3>\n\n\n\n<p>Das Frontend wurde mit dem Framework Vue.js der Version 3.0 entwickelt. Als Programmiersprache wurde TypeScript verwendet. Vue.js ist ein clientseitiges JavaScript-Web-Framework. Es ist leicht zug\u00e4nglich, leistungsf\u00e4hig und ein sehr vielseitiges Framework f\u00fcr die Erstellung von Webanwendungen und Benutzeroberfl\u00e4chen. Dadurch eignet sich Vue.js nicht nur f\u00fcr die Erstellung von Single-Page-Webanwendungen, sondern auch f\u00fcr komplexere und vielschichtigere Webanwendungen. Dabei baut Vue.js auf HTML, CSS und Javascript auf und stellt dem Programmierer ein deklaratives und komponentenbasiertes Programmiermodell bereit.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Vorteile<\/h4>\n\n\n\n<p>Vue.js bietet sehr viele Vorteile f\u00fcr Entwickler, sowie auch f\u00fcr die Nutzer. Es ist sehr leichtgewichtig und h\u00e4lt die Dateigr\u00f6\u00dfe aller erstellten Build-Dateien nach dem Kompilieren sehr gering, wobei es aber sehr flexibel bleibt. Bei \u00c4nderungen auf einer Seite werden nur die Teile neu gerendert, welche sich auch ge\u00e4ndert haben. F\u00fcr Entwickler ist es einfach zu lernen und auch zu benutzen. Eine Typescript Dokumentation ist enthalten und dank der Composition API wird der Code sehr \u00fcbersichtlich gehalten.&nbsp;<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Nachteile<\/h4>\n\n\n\n<p>Bei unserer Entwicklung mit Vue.js hatten wir gr\u00f6\u00dftenteils positive Erfahrungen, dennoch lassen sich auch negative Punkte anmerken. Eines unserer Hauptprobleme war die rapide Weiterentwicklung des Frameworks. Gerade weil wir die neueste Version 3 von Vue.js genutzt haben, welche es erst seit wenigen Wochen gibt. Dadurch war die Dokumentation f\u00fcr neue Funktionen oft sehr mangelhaft oder veraltet und third Party Bibliotheken eher sp\u00e4rlich verf\u00fcgbar. Am Ende des Semesters wurde dann von Vue eine komplett neue Dokumentation ver\u00f6ffentlicht, welche wir uns schon zu Beginn unseres Projektes gew\u00fcnscht h\u00e4tten.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Composition API<\/h4>\n\n\n\n<p>Durch die neu vorgestellte Composition API ergab sich eine neue Best Practice innerhalb von Vue.js. Es handelt sich dabei um die Nutzung der composition Functions (oder auch useFunctions genannt). Im Vergleich zu der herk\u00f6mmlichen Options API k\u00f6nnen so gro\u00dfe Teile der Logik in den Komponenten in die composition Functions ausgelagert werden. Dadurch k\u00f6nnen Logikabschnitte wiederverwendet werden und der Logikanteil in den Komponenten wird verkleinert. Vergleichbar ist dies mit den Services von Angular.&nbsp;<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Komponenten<\/h4>\n\n\n\n<p>Vue.js verwendet Komponenten, welche es erm\u00f6glichen, benutzerdefinierte Inhalte und Logik zu kapseln. Prinzipiell ist eine Komponente eine Abstraktion, die es uns erm\u00f6glicht, umfangreiche Anwendungen zu erstellen, die aus kleinen, in sich geschlossenen und oft wiederverwendbaren Komponenten bestehen.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh3.googleusercontent.com\/iFymBy-bLzcrhMDjo5qn0XgZG7j93Z-aKYE0fvNiLpbi8EzRM1DUk8jwnFVEZ36EZV7MmJbDCdJJGR-V9un_neJ2K6R3j2cwOo_Sfg26lwufJ76gLmtBsYIChAyPe1Z1H4cv0XF7\" alt=\"\" \/><figcaption><a href=\"https:\/\/vuejs.org\/guide\/essentials\/component-basics.html\">https:\/\/vuejs.org\/guide\/essentials\/component-basics.html<\/a><\/figcaption><\/figure>\n\n\n\n<p>Im Fall von unserem Projekt wurde zwischen drei verschiedenen Komponententtypen unterschieden. Die Screen-Komponenten geben das Layout der Seite vor, die Logik Komponenten erf\u00fcllen bestimmte Aufgaben wie z.B. ein Suchergebnis anzeigen und die UI-Komponente ist eine lediglich anzeigende Komponente ohne Logik wie z.B. ein Button.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Single File Components (SFC)<\/h4>\n\n\n\n<p>Eine weit verbreitete Best Practice von Vue ist die Nutzung der *.vue-Files, welche sowohl das Template (HTML), die Logik (JavaScript bzw. TypeScript) und CSS Code b\u00fcndeln. Diese Files werden vom SFC-Compiler in ein Standard JavaScript (EC) Module pre-compiled, wodurch diese Files wie andere Module importiert werden k\u00f6nnen.&nbsp;<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Lessons learned<\/h4>\n\n\n\n<p>Zu Beginn des Semesters erschien uns Vue.js in der Version 3 als die richtige Wahl, aufgrund der neuen Unterst\u00fctzung f\u00fcr Typescript, sowie der neuen Composition API. Im Nachhinein kann jedoch gesagt werden, dass es auch Nachteile mit sich gebracht hat, diese&nbsp; zeitgleich mit dem Release von Version 3 zu verwenden.&nbsp;<\/p>\n\n\n\n<p>Ein Nachteil dabei war, dass viele Third-Party-Libraries noch keine Unterst\u00fctzung f\u00fcr die Version 3 lieferten, bzw. Typescript nicht unterst\u00fctzt wurde. Ein Beispiel hierf\u00fcr ist die DatePicker Komponente, welche die Third-Party-Library v-calendar nutzt. Bei dieser waren wir gezwungen, die Alpha-Version zu verwenden, weil noch keine stabile Version f\u00fcr Version 3 existiert. Aufgrund der nicht stabilen Version kam es auch zur Situation, dass die Komponente von einem auf den anderen Tag nicht mehr funktionierte. Gl\u00fccklicherweise wurde sie in einer Wrapper-Komponente implementiert, wodurch die Third-Party-Library nur an einer Stelle ausgetauscht werden musste.<\/p>\n\n\n\n<p>Ebenfalls war ein gr\u00f6\u00dferes Problem die fehlende und sich h\u00e4ufig \u00e4ndernde Dokumentation des Frameworks. Dazu kam die sehr geringe Informationsdichte zu neuen Features des Frameworks in Foren wie Stackoverflow hinzu, welche das Entwickeln bei Problemen deutlich verlangsamt hat.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Backend<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>Spring<\/strong><\/h4>\n\n\n\n<p>Spring ist ein popul\u00e4res modulares Framework aus der Java Welt. Spring\u2019s wichtigstes Feature ist hierbei die Dependency Injection. Dependency Injection ist ein Software Design Pattern, welches dem Inversion of Control Prinzip folgt. Inversion of Control verschiebt den Kontrollfluss, der f\u00fcr das Instanziieren von Klassen ben\u00f6tigt wird, vom eigentlichen Applikations-Code zum Framework. Dependency Injection im Fall von Spring sorgt daf\u00fcr, dass Abh\u00e4ngigkeiten von einer Klasse zu anderen Klassen automatisch vom Framework aufgel\u00f6st werden. Dadurch kann \u201cGlue-Code\u201d f\u00fcr \u201cCreational-Patterns\u201d z. B. dem \u201cFactory-Pattern\u201d&nbsp; reduziert werden. Gleichzeitig wird das Testen der Anwendung dadurch eleganter: Unit-Tests von in der Applikation eigentlich abh\u00e4ngigen Klassen k\u00f6nnen dadurch einfach unabh\u00e4ngig durchgef\u00fchrt werden. Eine abh\u00e4ngige Klasse kann von einer alternativen Klasse, die das gleiche Interface implementiert, mit Mock-Properties\/Methoden im Dependency Container f\u00fcr den Unit-Test \u00fcberschrieben werden. In Spring k\u00f6nnen derartige Abh\u00e4ngigkeiten zwischen sogenannten Beans (Objekte, die von Spring instanziiert werden) ganz einfach \u00fcber die @Autowire Annotation deklariert werden.&nbsp;<\/p>\n\n\n\n<p>Spring bietet dar\u00fcber hinaus je nach Anwendungszweck noch zahlreiche weitere Module, die die sich idiomatisch in Spring\u2019s Paradigmen einf\u00fcgen und eine hohe Entwickler-Produktivit\u00e4t schaffen. In unserem Fall haben wir auf Spring Web gesetzt. Spring Web baut auf dem Tomcat Webserver auf und erm\u00f6glicht das Bauen von HTTP basierten APIs mit Spring. Hierbei liegt der Fokus in Tradition des Schichtenmodells auf der Trennung von Darstellung (-&gt; Controller), Business (-&gt; Service) und Daten (-&gt; Repository) Logik. Spring bietet auch hierf\u00fcr wieder praktische Annotations z.B. @RestController zum Erstellen eines Rest-Controllers f\u00fcr eine Ressource, dessen Methoden mit weiteren Annotations f\u00fcr Path\/Method-Mappings dekoriert werden k\u00f6nnen.<\/p>\n\n\n\n<p>Wir haben uns f\u00fcr den Product Service f\u00fcr Spring Boot Web mit Kotlin und Gradle entschieden. Grund hierf\u00fcr ist, dass Spring Boot Web standardm\u00e4\u00dfig viele Funktionen bietet, die wir f\u00fcr den Product Service nutzen wollten. Dazu z\u00e4hlen unter anderem Dependency Injection, Rest Controller sowie die M\u00f6glichkeit zus\u00e4tzliche Features aus entwickelten idiomatischen externen Bibliotheken nachzuinstallieren (z.B. Cognito Authentifizierung oder MongoDB Repositories). Da zuvor noch niemand von uns mit Spring gearbeitet hatte und wir eines der popul\u00e4rsten Frameworks aus der Microservice-Welt kennenlernen wollten, war dies ein weiterer Grund f\u00fcr die Auswahl von Spring. Eine valide Alternative mit ungef\u00e4hr \u00e4quivalentem Feature-Set w\u00e4re zum Beispiel das popul\u00e4re Node.js Framework NestJS gewesen, jedoch haben wir mit der ad-hoc type safety von TypeScript und dem allgemein teilweise mangelhaften TypeScript Support der Node.js Welt schlechte Erfahrungen gemacht. Deshalb haben wir uns lieber f\u00fcr eine Programmierumgebung mit nativer type-safety entschieden.<\/p>\n\n\n\n<p>Bei Spring haben uns f\u00fcr Kotlin statt Java entschieden, da Kotlin ebenfalls von Spring nativ unterst\u00fctzt wird und weil wir uns von Kotlin durch die elegantere Syntax eine h\u00f6here Entwickler-Produktivit\u00e4t versprochen haben. Kotlin ist eine relativ moderne Programmiersprache, die von den Entwicklern der IDE IntelliJ Jetbrains entwickelt worden ist und im Gegensatz zu Java mehr Syntactic Sugaring bietet. Weiterhin forciert sie im Vergleich zu Java die klassischen OOP Patterns nicht so stark. Kotlin ist interoperabel mit Java und kompiliert ebenfalls zu Bytecode f\u00fcr die JVM. Wir haben uns f\u00fcr Gradle statt Maven als Build Automation Tool entschieden, da uns Gradle bereits aus der Entwicklung von Android Apps bekannt war.<\/p>\n\n\n\n<p>Nachfolgend ist die Schichtenunterteilung innerhalb unseres Spring-Projekts dargestellt, die dem bereits erw\u00e4hnten Controller-Service-Repository Pattern folgt.<\/p>\n\n\n\n<p class=\"has-text-align-center\"><\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh4.googleusercontent.com\/Ausy5avs6OnwczRmsFWZ06_goQ8-4POo5rTCz07ljfhQFKslHNAVBcMBIpGRfJzQTtjwcMNgM3mK4qBiacf0HlczwXBGGWR9KsikOSPYEdPY0YMkqpHlH2AnorX63peESKHXAgMA\" alt=\"\" \/><\/figure>\n\n\n\n<p>Die einzelnen Schichten \u00fcbernehmen dabei die folgenden Aufgaben:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Controller Layer: Stellt Funktionalit\u00e4t nach au\u00dfen in Form einer HTTP-basierten API zur Verf\u00fcgung, die von Clients, in Form unseres Frontends, aufgerufen werden kann<\/li><li>Service Layer: Enth\u00e4lt die Businesslogik und wird durch den Controller aufgerufen<\/li><li>Repository Layer: Dient als Abstraktion zum Speichern und Abrufen von Daten aus den Datenhaltungssystemen MongoDB und Elasticsearch. Wird vom Service Layer aufgerufen.<\/li><\/ul>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>MongoDB<\/strong><\/h4>\n\n\n\n<p>Als zentrale Datenbank f\u00fcr unsere Anwendung haben wir uns f\u00fcr die dokumentenorientierte NoSQL Datenbank MongoDB entschieden. Diese speichert sogenannte Dokumente und eignet sich besonders gut f\u00fcr das Speichern von denormalisierten Dokumenten mit fest definierten Access Patterns.<\/p>\n\n\n\n<p>Ein zentraler Grund f\u00fcr die Verwendung von einer NoSQL DB wie MongoDB war, dass wir unseren Entwicklungsprozess eher agil gestalten wollten und unsere Daten daher nicht von Anfang an einem festen Schema unterliegen sollten, um so auf sich \u00e4ndernde Anforderungen reagieren zu k\u00f6nnen. So konnten wir w\u00e4hrend der Entwicklung z.B. den Produkt-Inseraten unkompliziert neue Felder hinzuf\u00fcgen, ohne dabei durch ein Datenbankschema eingeschr\u00e4nkt zu sein.<br>Neben diesem Vorteil bietet MongoDB Sharding- und Replikations-Features, die es erm\u00f6glichen, dass MongoDB bei korrekter Nutzung (fast) unendlich horizontal skalieren kann und gleichzeitig eine sehr hohe Verf\u00fcgbarkeit gew\u00e4hrleistet werden kann. Da es sich hierbei um ein studentisches Projekt handelt, war diese Tatsache zwar nicht direkt relevant, k\u00f6nnte in der Realit\u00e4t bei einem Betrieb einer solchen Anwendung von Bedeutung sein.<\/p>\n\n\n\n<p>Letztendlich haben wir in unserer Anwendung keine eigenen Queries auf die MongoDB Datenbank definiert, sondern nutzen das mongo-spezifische Repository Interface von Spring, welches f\u00fcr uns die Implementierung der gew\u00fcnschten Datenbank-Abfragen abstrahiert.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>ELK Stack<\/strong><\/h4>\n\n\n\n<p>In unserem Projekt kommen alle drei Komponenten des ELK Stacks zum Einsatz. Diese sind Elasticsearch, Logstash und Kibana. Die folgenden Use Cases f\u00fcr diese Technologien waren hierbei als Motivation f\u00fcr die Nutzung des ELK Stack ausschlaggebend:<\/p>\n\n\n\n<p>Zentrale Suche innerhalb der Anwendung<\/p>\n\n\n\n<p>Ein zentraler Bestandteil unserer Vermietungsplattform sollte eine Suchfunktion \u00fcber die Titel der Produkt-Inserate und die Tags der Produkte sein. Eine Darstellung dieser Suche ist in der folgenden Abbildung zu sehen:&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh4.googleusercontent.com\/_VS2irB6KxsooMS57kvN-kUJBGja48VI90BQBBQIz-CevwgS7SVMBjPjXHGEbiqdagZ1ftvTj7R0avc7ji_rEOdl-isUA2z7cSK6aDyGPG0u0bLiQnmpB3wCL4npo34I6z5MVVHW\" alt=\"\" \/><figcaption>Beispiel-Sucheingabe in der Anwendung<\/figcaption><\/figure>\n\n\n\n<p>Zwar k\u00f6nnten wir hierbei bis zu einem gewissen Grad auch \u00fcber die gespeicherten Produkte in unserer MongoDB Hauptdatenbank suchen, jedoch ist man im Vergleich zu speziell auf diesen Use-Case zugeschnittenen L\u00f6sungen doch eher eingeschr\u00e4nkt. Aus diesem Grund haben wir uns dazu entschieden Elasticsearch zu verwenden, was eine sehr popul\u00e4re Search- und Analyseengine ist. Diese verspricht, gro\u00dfe Datenmengen von Dokumenten nahezu in Echtzeit durchsuchen zu k\u00f6nnen und bieten viele weitere Features wie z.B. Fuzzy Search, die mit Typos umgehen kann oder Search-as-you type autocomplet Suchm\u00f6glichkeiten \u00fcber einen Index. Die hohe Performance bei der Volltextsuche von Elasticsearch wird dabei dadurch erreicht, dass die abgespeicherten Dokumente nicht direkt durchsucht werden, sondern ein sogenannter invertierter Index gebildet wird. Dieser gibt f\u00fcr jeden Begriff an, in welchen Dokumenten dieser vorkommt, wodurch sehr performant die zur Suche passenden Dokumente gefunden werden k\u00f6nnen. Weiterhin k\u00f6nnen die Begriffe bei der Indizierung weiteren Operationen und Transformationen durch die Definition eines sogenannten Analyzers unterzogen werden. Dies k\u00f6nnte z.B. ein sogenannter Normalizer sein, der unterschiedliche Formen desselben Worts auf eine normalisierte Form abbildet, um dieses Wort sp\u00e4ter zuverl\u00e4ssiger finden zu k\u00f6nnen. Ein solcher Analyzer innerhalb unseres Projekts ist in der folgenden Abbildung zu sehen:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh5.googleusercontent.com\/Gb6EuA3rVLHzVmf-OZlIVF0LCcs7VfG5ac-h9zd-0A4lFYTCf9PEkCJyFmKcumCNNvphtviGVuprmH1p7SWh0sVIVnAphuBJzMljdLwnInPFF6INUrI23TPvdIhkBPHPDvdVeMEb\" alt=\"\" \/><figcaption>Beispiel eines Elasticsearch-Analyzers&nbsp;<\/figcaption><\/figure>\n\n\n\n<p>Eine wichtige Bedingung f\u00fcr die Nutzung von Elasticsearch war, dass diese neben der Volltextsuche datumsbasierte und geobasierte Abfragen unterst\u00fctzt.<br>Diese Bedingung ergibt sich daraus, dass bei der Suche nach Produktinseraten auch ein Verf\u00fcgbarkeitszeitraum und ein Suchradius um eine bestimmte Stadt angegeben werden k\u00f6nnen sollte. Da solche Abfragen durch Elasticsearch erm\u00f6glicht werden, deckt diese Searchengine alle unsere Anforderungen ab.<\/p>\n\n\n\n<p>Damit die Suche immer auf dem aktuellsten Datenbestand der Produktinserate durchgef\u00fchrt wird, haben wir uns dazu entschieden, die Daten konzeptuell von der MongoDB Hauptdatenbank stets in den entsprechenden Elasticsearch Index zu spiegeln. Da hierbei die kompletten Produktdaten im Elasticsearch Index gespeichert werden, wird f\u00fcr die Suche selbst keine Beteiligung der MongoDB ben\u00f6tigt.<\/p>\n\n\n\n<p>Wie auch bei MongoDB nutzen wir hier auch wieder das Elasticsearch spezifische Repository Interface <em>ElasticsearchRepository<\/em>, das durch Spring bereitgestellt wird und den Zugriff auf den Index abstrahiert. Nur f\u00fcr einzelne sehr komplexe Queries greifen wir in unserem Code via Elasticsearch-Syntax auf den Index zu.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Verarbeitung von Logs<\/h4>\n\n\n\n<p>Basierend auf dem zuvor beschriebenen Use Case waren wir uns schon relativ zu Beginn des Projekts sicher, dass wir Elasticsearch verwenden m\u00f6chten. Da Elasticsearch Teil des ELK-Stacks ist, lag f\u00fcr uns die Idee nahe, auch die anderen Komponenten des ELK Stacks im Rahmen der System Engineering und Management Vorlesung einzubeziehen und zu \u00fcberpr\u00fcfen, wie diese uns beim Analysieren der Anwendungs-Logs unterst\u00fctzen k\u00f6nnen und mit welchem Aufwand der Aufbau einer solchen Pipeline verbunden ist.<\/p>\n\n\n\n<p>Im Speziellen stehen das L und K innerhalb des ELK-Stacks dabei f\u00fcr die Data-Processing-Pipeline Logstash und die auf Elasticsearch aufbauende Analyseplattform Kibana. Im Prinzip k\u00f6nnen wir mit Logstash die Logs unserer Anwendung weiterverarbeiten und durch Filterung besser auswerten, da wir die enthaltenen Felder separat parsen. Da wir diesen Output dann in Elasticsearch speichern, k\u00f6nnen wir die entsprechenden Indizes mit Kibana analysieren und so unsere Log-Daten besser verstehen.<\/p>\n\n\n\n<p>Wie wir diese beiden Tools eingebunden und verwendet haben, zeigen wir dabei im Infrastruktur-Kapitel unter dem Punkt \u201cLogging\u201d genauer.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Testing<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Backend<\/h3>\n\n\n\n<p>Beim Testing im Backend haben wir uns an die von Ham Vocke abgeleiteten Prinzipien aus der Testing-Pyramide von Mike Cohn orientiert.[10]<\/p>\n\n\n\n<p><strong><img loading=\"lazy\" decoding=\"async\" width=\"335\" height=\"177\" src=\"https:\/\/lh3.googleusercontent.com\/p1GYxLbSBp75SByrGGeSRy_6xV3JvlQPpi8IrAICW5ureWzWHoF2AcqtmHjW0hv212iLkdnMMLiJjekyyuoJviZWlk-xOTy5nJzObol4J1IeAzCY9WiYxMKyhWoazH6xP9RBuDyj\"><\/strong><\/p>\n\n\n\n<p>Diese Prinzipien lauten wie folgt:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Schreibe Tests mit unterschiedlicher Granularit\u00e4t<\/li><li>Je mehr \u201cHigh-Level\u201d die Tests sind, desto weniger Tests sollten auf diesem Level vorhanden sein<\/li><\/ul>\n\n\n\n<p>Daraus haben wir abgeleitet, dass wir f\u00fcr unsere Anwendung sowohl Integrations-Tests (in Pyramide als Service Tests bezeichnet) und lower-level Unit-Tests schreiben wollen. Wie durch die Prinzipien definiert, soll die Anzahl der isolierten, schnell auszuf\u00fchrenden Unit-Tests dabei gr\u00f6\u00dfer sein als die der eher langsamen und aufwendigen Integration-Tests.<\/p>\n\n\n\n<p>Initial haben wir uns zuerst auf das Schreiben von Integrations-Tests fokussiert.<br>Aus unserer Sicht l\u00e4sst sich bei dieser Testart das gr\u00f6\u00dfte Vertrauen in die korrekte Funktionsweise der Anwendung gew\u00e4hrleisten, da hierbei alle Komponenten der Anwendung instanziiert und verwendet werden. Die API wird dann durch tats\u00e4chliche HTTP-Requests abgefragt. Dadurch, dass hierbei nur wenige Teile der Daten als Setup f\u00fcr die Tests im Voraus modelliert werden m\u00fcssen, wird eine \u00dcberspezifikation der Tests vermieden und so nah wie m\u00f6glich an den \u201eRealbedingungen\u201c der Anwendung getestet. Implementiert man solche (<em>positive<\/em> und <em>negative<\/em>) Integration-Tests f\u00fcr alle Routen, erh\u00e4lt man einen fundierten \u00dcberblick dar\u00fcber, ob die Anwendung den Nutzern die gew\u00fcnschte Kernfunktionalit\u00e4t zur Verf\u00fcgung stellen kann oder ob eventuelle Fehler dies behindern.<\/p>\n\n\n\n<p>Mit der Annotation <em>@SpringBootTest <\/em>f\u00fcr unsere Test-Klasse stellt uns Spring komfortabler Weise einen speziellen Application-Context f\u00fcr das Testing bereit. Somit k\u00f6nnen wir wie gew\u00fcnscht alle Layer unserer Anwendung analog zum Produktivbetrieb nutzen und das korrekte Zusammenspiel dieser sicherstellen. Als Testing-Framework wurde an dieser Stelle JUnit 5 verwendet, welches Teil der spring-boot-test-starter Dependency ist.Ein beispielhafter Integrationstest f\u00fcr die Route in unserem <em>ProductController<\/em> im Spring-Projekt, welche ein neues Produkt-Inserat erstellt, ist in den nachfolgenden zwei Abbildungen gezeigt.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh3.googleusercontent.com\/oVKyl_7mK2MUtV_cb6V6zCIqZdg4sH9UAV7JsGbBj6docUiX9MNzwJOPOR8_4k1DUEPnsl9phuGx64-E2cUPwHQz0at4GWYjmhHgyRDoaOuhSs0m6M--3gW8H7TIiohmJZ2eT4c0\" alt=\"\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh5.googleusercontent.com\/XtENOgEwU8EzBAHONtmSgWmS_k0PqzG-6hcJPhq27yjVCOtfvq2IaWK5R4vfp0qlvH5F6AtqCb-eKxinZO3fPGAx_5jeW8SjfhgstFYxVX1qyyctFFPLTrEeXMtziDEuIL6UFIta\" alt=\"\" \/><figcaption>Code-Snippet eines Integrationstests<\/figcaption><\/figure>\n\n\n\n<p>Zuerst nutzen wir einen Testuser, um uns bei der Anwendung anzumelden, d.h. einen Access-Token zu erhalten. Dieser steht allen Testmethoden zur Verf\u00fcgung. In der eigentlichen Funktion \u201eshould create new product\u201d wird durch prepareProductRequest()ein DTO-Produkt-Objekt (DTOs = Data Transfer Objects) zur Verf\u00fcgung gestellt, welches durch die Route in der Anwendung gespeichert werden soll. Anschlie\u00dfend wird der HTTP-Header und die HTTP-Entity erzeugt, welche den erw\u00e4hnten Access-Token f\u00fcr die Autorisierung des Nutzers verwendet. Mit den genannten Daten wird der Endpunkt f\u00fcr das Erstellen eines Produktes aufgerufen. F\u00fcr die erhaltene Response werden dann verschiedene Bedingungen f\u00fcr ein erfolgreiches Testergebnis \u00fcberpr\u00fcft, wie z.B. dass die Daten im zur\u00fcckgegebenen (in der Datenbank gespeicherten) Produkts den \u00fcbergebenen Daten des DTOs entsprechen.Auch wenn unser initialer Fokus wie bereits angemerkt auf solchen Integrations-Tests lag, wollten wir ja entsprechend der Test-Pyramide und der daraus abgeleiteten Prinzipien unserem Projekt weitere Tests mit einer anderen Granularit\u00e4t hinzuf\u00fcgen.<br>Daher haben wir f\u00fcr unsere unter den Controllern liegenden Service-Klassen Unit-Tests mit JUnit 5 als Testing-Framework geschrieben. Diese Service-Klassen kapseln einen Teil der Business-Logik unserer Anwendung und stellen Methoden bereit, auf welche die Routen zugreifen k\u00f6nnen, um den entsprechenden Request abzuarbeiten. Einige dieser Methoden enthalten nur relativ wenig eigene Logik, da durch das Repository und die zugeh\u00f6rige Datenbankabfrage bereits die passenden Daten f\u00fcr die gew\u00fcnschte Response zur\u00fcckgeliefert werden. Das unten stehende Code-Snippet zeigt eine solche Funktion im <em>ProductService:<\/em><\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh5.googleusercontent.com\/qm0Q7p9Cd1_unRbYGHluOI-U7DPM_0Rf0tfn5LHpYl0-uUJ8aUnoPVbJHZkIOBMkHN4SN-8ltMOpqML0KMYpBBigsrBvgVEtZxH8Iw0Q553P_NyQNdRFZjqcU_ss4hB-u9r3bSdh\" alt=\"\" \/><figcaption>findAll()-Funktion aus der ProductService Klasse<\/figcaption><\/figure>\n\n\n\n<p>An diesen Stellen erscheint uns das Schreiben von Unit-Tests f\u00fcr diese Services als eher redundant, da durch den Integrations-Test bereits sichergestellt ist, dass die Response den Anforderungen entspricht.<br>Bei Funktionen, welche eine komplexere Logik implementieren, k\u00f6nnen solche Tests unserer Ansicht nach durchaus sinnvoll sein. Man kann in diesen F\u00e4llen eine Vielzahl von Test-Cases f\u00fcr einzelne Funktion schreiben (z.B. um m\u00f6gliche Randf\u00e4lle abzudecken), ohne dabei jedes Mal die gesamte Anwendung zu starten und wirklich einen Request senden zu m\u00fcssen. Dadurch sind diese Tests deutlich schneller und weniger aufwendig in der Ausf\u00fchrung.<br>Das isolierte Testing wird dabei durch das Mocking beteiligter externer Komponenten (z.B. Repositories) erreicht. Hierf\u00fcr haben wir in unserem Projekt das Mocking-Framework Mockito verwendet. Durch die Unit-Test-Cases kann dann sichergestellt werden, dass sich die einzelnen getesteten Funktion stets erwartungskonform verhalten und Fehler k\u00f6nnen durch das Testen solcher einzelnen Einheiten schneller identifiziert werden.<\/p>\n\n\n\n<p>Der Nachteil ist an dieser Stelle, dass diese komplexeren Funktionen in den Service-Klassen h\u00e4ufig mehrere Funktionen anderer Services und\/oder Repositories verwenden und diese, wie bereits angemerkt, gemockt werden m\u00fcssen. Dadurch ist tendenziell mehr Code f\u00fcr das Setup der Tests n\u00f6tig als bei den Integration-Tests.&nbsp;Ein beispielhafter Unit-Test f\u00fcr die <em>save()<\/em> Funktion ist in dem untenstehenden Code-Ausschnitt gezeigt.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh5.googleusercontent.com\/JX4SwDnYpmjXysefQ5_AcpW7xq0X2GBCDZWieBSoRLadLrDD2Y9dI6yHW-nPRhbPHl9ZnmXLtWa0otnQ_RczOrMA4Lits9yZmcVIPdVhvk5ifChGPKQg-t_q-gSF107cl083RUc9\" alt=\"\" \/><figcaption>Beispiel Unit-Test f\u00fcr eine Funktion des Service Layers<\/figcaption><\/figure>\n\n\n\n<p>Wir erstellen zuerst einen User der gespeichert werden soll. Anschlie\u00dfend definieren wir, was der R\u00fcckgabewert der <em>save()<\/em> Funktion des gemockten <em>UserRepository<\/em> sein soll. Nun folgt mit <em>userService.save(\u2026)<\/em> der eigentliche Aufruf der zu testenden Funktion. Da diese Funktion das Ergebnis der <em>save()<\/em> Funktion des gemocketen <em>UserRepository<\/em> direkt zur\u00fcckgibt, sind wir hier nicht unbedingt daran interessiert den R\u00fcckgabewert mithilfe von Assertions zu \u00fcberpr\u00fcfen. Stattdessen verifizieren wir mit dem <em>verify<\/em> Aufruf, dass innerhalb der <em>userService.save()<\/em> Funktion das Repository genau einmal mit den richtigen Parametern aufgerufen wird. Ist dies gegeben, wird der User also korrekt an das Repository f\u00fcr die Speicherung in der Datenbank weitergegeben.<br>Bei Funktionen, bei denen der R\u00fcckgabewert nicht einfach das Ergebnis einer Repository-Funktion ist, haben wir dagegen vor allem wieder auf Assertions bez\u00fcglich des R\u00fcckgabewerts gesetzt, um die korrekte Funktionsweise zu testen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Frontend<\/h3>\n\n\n\n<p>Da wir eine komplette Test Coverage f\u00fcr unser Projekt erreichen wollten, haben wir nicht nur alle Backend Funktionen testen wollen, sondern auch unser Vue.js Frontend. F\u00fcr das Testen im Frontend gibt es insgesamt drei Arten von Tests mit stark ansteigender Komplexit\u00e4t.<\/p>\n\n\n\n<p>Die einfachsten, schnellsten und am h\u00e4ufigsten angewendeten Tests sind auch hier wieder die Unit Tests. Wie bereits im Backend werden mit den Unit Tests die Eingaben in eine bestimmte Funktion, Klasse, oder auch Fall von Vue.js auch in eine Composable Function, auf die erwarteten Ausgaben und Seiteneffekte \u00fcberpr\u00fcft.<\/p>\n\n\n\n<p>Die n\u00e4chste Stufe sind die Component Tests. Component Tests \u00fcberpr\u00fcfen, ob die Components von Vue.js richtig gemounted und gerendert werden. Zus\u00e4tzlich dazu \u00fcberpr\u00fcfen sie, ob mit der Component interagiert werden kann und ob sie sich bei der Interaktion wie erwartet verh\u00e4lt. Man merkt hier aber direkt, dass die Component Test deutlich komplexer sind als die Unit Tests und mehr Code importieren m\u00fcssen. Daraus folgt auch ein gr\u00f6\u00dferer Zeitaufwand bei der Ausf\u00fchrung.<\/p>\n\n\n\n<p>Die letzte Testart und auch die aufw\u00e4ndigste sind End-to-End Tests. Diese \u00fcberpr\u00fcfen Funktionen, die sich \u00fcber mehrere Seiten erstrecken und echte Netzwerkanfragen an produktive Vue Anwendungen stellen. Au\u00dferdem ben\u00f6tigen End-to-End Tests h\u00e4ufig schon aufgesetzte Datenbanken und ein laufendes Backend.<\/p>\n\n\n\n<p>Mit dem Wissen \u00fcber die m\u00f6glichen Arten des Testings im Frontend, haben wir uns dazu entschieden, unseren Fokus auf die Unit und Component Tests zu legen. Diese Entscheidung wurde daher getroffen, da sich zur Zeit der Entwicklung noch kein laufendes Backend zur Verf\u00fcgung stand und End-to-End Test zu aufw\u00e4ndig f\u00fcr ihren letztendlichen Nutzen f\u00fcr die Entwicklung sind.<\/p>\n\n\n\n<p>Da die Businesslogik einer Webanwendung haupts\u00e4chlich im Backend bzw. auf Serverseite befinden soll, war die Anzahl der zu testenden Funktionen im Frontend eher gering. Ebenfalls verfolgt Vue.js das Model-View-Viewmodel (MVVM) Pattern, wobei Vue.js die Viewmodel Schicht repr\u00e4sentiert. Dadurch sollen die Vue Components lediglich die View dynamisch aktualisieren und den Input verarbeiten, aber keine gr\u00f6\u00dfere Logik beinhalten.<\/p>\n\n\n\n<p>Um unseren Code in Vue.js testen zu k\u00f6nnen, wird noch ein zus\u00e4tzlich Javascript-Test-Framework ben\u00f6tigt. Wir haben uns daf\u00fcr drei unterschiedliche Testsuites angeschaut und miteinander verglichen.<\/p>\n\n\n\n<p>Das erste Test-Framework war Jest, welches seinen Fokus auf Einfachheit und Schnelligkeit gelegt hat. Jest bietet auch eine einfache Integration in alle \u00fcblichen Javascript Frameworks wie React, Angular und Vue an, sowie einen Typescript Support.<\/p>\n\n\n\n<p>Jest bietet ebenfalls einen integrierten Custom Resolver f\u00fcr Imports im Code an, was ein einfaches Mocking von Funktionen und Packages erm\u00f6glicht. Es funktioniert sehr gut auch ohne Konfiguration und die Entwickler stellen eine saubere und gepflegte Dokumentation bereit.<\/p>\n\n\n\n<p>Das zweite Test-Framework war Mocha. Mocha ist ein feature-rich Test-Runner, der auf Node.js l\u00e4uft. Da Mocha ein Test-Runner ist, ben\u00f6tigt es andere Bibliotheken f\u00fcr das eigentliche Testen. Dadurch bietet es aber auch ein hohes Ma\u00df an Flexibilit\u00e4t bei der Testentwicklung. Eine der am h\u00e4ufigst verwendeten Bibliotheken mit Mocha ist Chai, eine assertion library ebenfalls f\u00fcr Node.js.<\/p>\n\n\n\n<p>Das letzte Test-Framework namens Vitest stammt direkt aus dem Hause Vue. Vitest basiert wie das CLI Tool create-vue ebenfalls auf VITE und kann dadurch dieselbe Konfiguration- und Transformation-Pipeline direkt von Vite nutzen. Da es wie bereits erw\u00e4hnt direkt von dem Vue und Vite Team entwickelt wird, l\u00e4sst es sich mit minimalem Aufwand in Vue.js integrieren und ist dadurch auch sehr schnell. Da Vitest speziell f\u00fcr Vue entwickelt wird, sind die bereitgestellten Tools f\u00fcr das Testen der Composables und Components sehr gut angepasst. Der gro\u00dfe Nachteil von Vitest war aber, dass es erst im Dezember 2021 in einer sehr fr\u00fchen Alpha Version erschienen ist und zum Start unseres Projektes noch nicht verf\u00fcgbar war.<\/p>\n\n\n\n<p>Nachdem wir die drei Frameworks miteinander verglichen haben, w\u00e4re unsere erste Wahl auf Vitest gefallen. Da Vitest aber wie erw\u00e4hnt noch nicht released war und erstmal in eine sehr fr\u00fche Alpha Phase kam, haben wir uns f\u00fcr unsere zweite Wahl entschieden: Jest. Unsere Entscheidung ging haupts\u00e4chlich von dem Punkt aus, da Jest bereits alle relevanten Funktionen in einem Framework beinhaltete und f\u00fcr uns wichtige Mock Funktionen bereits beinhaltet sind.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh5.googleusercontent.com\/K7Z5lSPBKi9dd7g5twQsLn2CVfygqqIS7N5j2OUNehbW7OmqxxVFQds6qKFxag6tdCeI7Rs46JkFej7fV3J_z6eXDnMOzTCBsfxSqZMB_WtmoeEBQhdAgeEmg1dNjfE7biCzDjMI\" alt=\"\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh3.googleusercontent.com\/1NL720WDF9WMBlmO7CWg-dX6O1wEYBpNUn3OXQYs7TuQrXUa8ohmjclC-Er0ZpVjnZbcP1CILNc-OUieBOjVR7dXo1xStE_HmTaHryw6-F-65HHZhIvFyyY6eRvdivC7nXr2u7SV\" alt=\"\" \/><figcaption>Code-Snippet von Unit-Tests f\u00fcr das Frontend<\/figcaption><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Infrastruktur<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Auswahl Cloud-Anbieter<\/h2>\n\n\n\n<p>Bevor auf die Auswahl des Cloud-Anbieters eingegangen wird, wollen wir kurz beschrieben, wieso wir Container und Orchestrierung genutzt haben.&nbsp;<\/p>\n\n\n\n<p>Als Entwickler m\u00f6chte man seine Anwendung m\u00f6glichst zuverl\u00e4ssig und portabel auf einer breiten Masse von Computern deployen k\u00f6nnen. Hierbei helfen Tools zum \u201cPackagen\u201d der Anwendung. Dieses Package sollte hierbei alle n\u00f6tige Software enthalten, die ben\u00f6tigt wird, um die Anwendung auszuf\u00fchren. So kann man zum Beispiel ein VM Image mit der Anwendung und aller ben\u00f6tigter Software vorinstalliert erstellen oder alternativ ein Container Image mit der Anwendung und aller ben\u00f6tigter Software bauen.<\/p>\n\n\n\n<p>Container haben den Vorteil, dass sie im Vergleich zu VM Images hardware-unabh\u00e4ngiger deployed werden k\u00f6nnen, weil sie keinen Linux Kernel mit Treibern etc. beinhalten. Container-Engines, wie Docker, nutzen stattdessen Linux Kernel Funktionalit\u00e4t wie CGroups und Namespaces zum Zuweisen und Isolieren von Ressourcen.<\/p>\n\n\n\n<p>Die meisten Systeme bestehen jedoch aus mehreren Anwendungen. Das aktuell popul\u00e4re Micro-Service Architektur Pattern verst\u00e4rkt diesen Effekt noch weiter. Diese Services ben\u00f6tigen Zugriff auf unterschiedliche Ressourcen, es bestehen unterschiedliche Erwartungen an die Verf\u00fcgbarkeit, Services m\u00fcssen untereinander kommunizieren k\u00f6nnen und sich gegenseitig finden etc. Container Orchestrator nehmen hierbei mithilfe von Software-Layern und Abstraktionen viel administrative Arbeit ab, die normalerweise ben\u00f6tigt werden w\u00fcrde, um Cluster aus Containern manuell zu betreiben.&nbsp;<\/p>\n\n\n\n<p>Da sich Kubernetes quasi als offener Industriestandard f\u00fcr Container-Orchestrierung etabliert hat und sich einfach auch lokal f\u00fcr Entwicklungszwecke mit z.B. Minikube installieren l\u00e4sst, wollten wir unsere Anwendung auch in einem Kubernetes Cluster deployen.<\/p>\n\n\n\n<p>Damit unsere Anwendung auch im Internet und nicht nur lokal verf\u00fcgbar ist, mussten wir uns f\u00fcr einen Cloud-Anbieter entscheiden, was sich als schwieriger herausgestellt hat als anfangs angenommen.<\/p>\n\n\n\n<p>Die Entscheidung f\u00fcr den Anbieter hierf\u00fcr sollte anhand von finanziellen Faktoren, objektiver Leistung sowie Soft-Factors gef\u00e4llt werden.<\/p>\n\n\n\n<p>F\u00fcr uns kamen in der Vorauswahl als Cloud-Anbieter AWS (Amazon Web Services) und GCP (Google Cloud Platform) in Betracht, da wir in unserer Gruppe mit beiden Anbietern bereits in Ber\u00fchrung gekommen sind, beide einen managed Kubernetes Service anbieten, beide Anbieter verbreitet und etabliert sind und einiges an Tooling und Lehrmaterialien f\u00fcr diese existiert. AWS\u2019 Service tr\u00e4gt den Namen EKS (Elastic Kubernetes Service) w\u00e4hrend GCP\u2019s Service GKE (Google Kubernetes Engine) hei\u00dft.<\/p>\n\n\n\n<p>Beim Betrachten der Preise f\u00e4llt auf, dass es komplex ist, auf den ersten Blick zu beurteilen, bei welchem Anbieter monatlich die geringeren Kosten entstehen.<\/p>\n\n\n\n<p>Dazu kommt, dass es sich zwar um relativ \u00e4hnliche, aber immer noch unterschiedliche Leistungen handelt und somit nicht feststeht, ob ein Nutzer bei beiden Deployments auch eine \u00e4quivalente User Experience hinsichtlich z. B. Latenzen, Verf\u00fcgbarkeit etc. bekommt.<\/p>\n\n\n\n<p>Man kann aus unserer Stichprobe allerdings ein paar Trends entnehmen. F\u00fcr den einfachen Betrieb eines Kubernetes Cluster ohne Nodes verlangen beide Anbieter den gleichen Betrag von $0.10 pro Stunde. Hier ist kein Gewinner auszumachen.<\/p>\n\n\n\n<p>Beim Betrieb der Nodes im Cluster wird die Entscheidung allerdings kompliziert. AWS bietet zum Betrieb von Nodes die AWS Fargate Compute Engine zum direkten deployen von Containern ohne Management von VMs. GCP bietet alternativ den Cloud-Autopilot an, f\u00fcr $0.10 zus\u00e4tzlich pro Stunde, der nach unserem Verst\u00e4ndnis das Kubernetes Cluster und dessen Ressourcen, wie Nodes, autonom managed und skaliert. Dadurch m\u00fcssen bei der Nutzung des Cloud-Autopilots ebenfalls keine VMs manuell gemanaged werden. Interessanterweise sind hier die Preise f\u00fcr Ressourcen bei beiden Anbietern ann\u00e4hernd gleich f\u00fcr vCPU per hour und RAM usage per hour.<\/p>\n\n\n\n<p>Alternativ kann man aber bei beiden Cloud Providern dem Kubernetes Cluster dedizierte VMs als Nodes zuteilen. Hierbei greifen beide Kubernetes Services auf die jeweiligen VM Services des Cloud-Anbieters (EC2 bei AWS und GCP VM bei GCP) zur\u00fcck und es gelten die entsprechenden Preise. Sowohl bei einem Cluster aus \u201cschwachen\u201d Nodes, als auch bei einem Cluster aus \u201cst\u00e4rkeren\u201d Nodes ist GCP signifikant g\u00fcnstiger als AWS.<\/p>\n\n\n\n<p>Beim Egress Network Traffic und persistentem SSD Storage wiederum ist AWS signifikant g\u00fcnstiger.<\/p>\n\n\n\n<p>Soft-Faktoren haben eher f\u00fcr EKS von AWS gesprochen, da AWS der Marktf\u00fchrer und etablierter als GCP ist. Weiterhin hatten unsere Entwickler bisher mehr Erfahrung mit AWS gesammelt. Ein weiterer Soft-Faktor der f\u00fcr EKS spricht ist, dass AWS mit CloudWatch ein f\u00fcr uns interessantes Produkt zum Auswerten von Application Metrics bietet, f\u00fcr das GCP unseres Wissensstandes nach keinen \u00e4quivalenten Dienst als Ersatz anbietet.&nbsp;<\/p>\n\n\n\n<p>F\u00fcr den Betrieb eines minimal funktionalen Prototyps unseres Clusters ben\u00f6tigen wir&nbsp; mindestens 5 Container (Product Service, ElasticSearch, Logstash, Kibana und MongoDB) pro Environment. Jeder dieser Container ben\u00f6tigt ungef\u00e4hr mindestens 1GB RAM, da es sich bis auf MongoDB ausschlie\u00dflich um JVM Anwendungen handelt, die unserer Erfahrung nach im lokalen Betrieb viel RAM konsumieren. Im Fargate Betrieb des Elastic Kubernetes Services von AWS kommen wir mit 5 Containern mit jeweils 0.25 vCPUs und 1GB RAM auf ca. $60.30 monatlich. Gehen wir davon aus, dass wir insgesamt im Cluster mindestens 5GB RAM ben\u00f6tigen, m\u00fcssen in dem Cluster alternativ 3 schwache dedizierte VM Instanzen (jeweils 2GB RAM) verf\u00fcgbar sein. Der Preis w\u00fcrde sich hier bei AWS auf $57.30 im Monat belaufen und bei GCP auf $36.18. Da wir aber die Ergonomie einer Container Compute Engine wie Fargate bevorzugen, haben wir uns f\u00fcr den EKS Service von AWS entschieden, da der Google Cloud Autopilot von GCP aufgrund des hohen Preises ausscheidet und die Soft-Faktoren f\u00fcr EKS sprechen. Wenn wir die Preise f\u00fcr Netzwerk Traffic und Storage mit einbeziehen, die f\u00fcr unser Studentenprojekt, aber erstmal vernachl\u00e4ssigbar sind, bewegt sich die Tendenz weiter zu AWS.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><\/td><td>AWS EKS<\/td><td>GCP GKE<\/td><\/tr><tr><td>Betrieb<\/td><td>$0.10($72 per month)<\/td><td>$0.10($72 per month)<\/td><\/tr><tr><td>Nodes<\/td><td><strong>Fargate<\/strong>:<br>per vCPU:<br>$0.04656 per hour<br>per GB:<br>$0.00511 per hour<br><br><strong>EC2<\/strong>:<br>Smallest recommended node t2.small (burst CPU, 2GB RAM):<br>$0.0268 per hour($19.30 per month)<br>General purpose medium x64 node m5a.2xlarge (8vCPU AMD EPYC, 32GiB):\u00a0<br>$0.416 per hour($299.52 per month)<\/td><td><strong>Google Cloud Autopilot<\/strong> (Additional $0.10 per hour):<br>per vCPU:<br>$0.0445 per hour<br>per GB:<br>$0.0049225 per hour<br><br><strong>GCP VM:<br><\/strong>Smallest recommended node e2-small (burst CPU, 2GB RAM):<br>$0.016751 per hour($12.06 per month)<br>General purpose medium x64 node e2-standard-8 (8vCPU AMD EPYC, 32GiB):\u00a0<br>$0.345336 per hour($248.64 per month)<\/td><\/tr><tr><td>Data Transfer Out<\/td><td>Within one AZ:<br>no charge<br>Within region:<br>$0.01\/GB<br>Across regions:<br>$0.01-0.02\/GB<br>To Internet:$0.09\/GB (first 10 TB)<\/td><td>Within one AZ:<br>no charge<br>Within region:<br>$0.01\/GB<br>Across regions:<br>$0.01-0.15\/GB<br>To Internet:<br>$0.12-0.23\/GB (first 1 TB)<\/td><\/tr><tr><td>Storage<\/td><td>EBS (SSD) per GB:<br>$0.0952 per month<\/td><td>Persistent Disk (SSD) per GB:<br>$0.204 per month<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Alle Preise gelten f\u00fcr die Region Frankfurt [12-14].<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Serverless Dienste&nbsp;<\/h3>\n\n\n\n<p>Amplify ist ein Produkt von AWS, welches eine Sammlung von Werkzeugen und Funktionen mit sich bringt. Unter anderem k\u00f6nnen so auf einfachem Weg AWS-Services erstellt und eingebunden werden. In sharetopia wurde mithilfe von Amplify der Authentifizierungs-Service Cognito und ein S3 Bucket von AWS eingebunden. Diese werden mithilfe der AWS CLI dem Projekt hinzugef\u00fcgt.&nbsp;<\/p>\n\n\n\n<p>Der Vorteil in der Verwendung von Amplify besteht darin, dass man durch die Serverless Architektur kaum Entwicklungsressourcen aufwenden muss, um Cloud-Infrastruktur zu pflegen und man ohne tiefgreifende Cloud Kenntnisse zurechtkommt. Es ist sozusagen das Backend-As-A-Service-Produkt von AWS.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">CI\/CD Pipelines<\/h2>\n\n\n\n<p>Continous Integration, Continous Delivery und Continous Deployment sind Praktiken, welche von Softwareentwickler-Teams, f\u00fcr eine kontinuierliche Automatisierung und \u00dcberwachung \u00fcber den gesamten Anwendungs-Lifecycle, genutzt werden. Dabei sind alle drei Praktiken zusammenh\u00e4ngend und werden oft auch als CI\/CD-Pipeline bezeichnet&nbsp; [1]. Das weiterf\u00fchrende Konzept hinter CI\/CD kann unter [1] nachgelesen werden.<\/p>\n\n\n\n<p>In einer CI\/CD Pipeline beschreibt Continous Integration die erste Phase. In dieser werden regelm\u00e4\u00dfig die neuen Code\u00e4nderungen erst gepr\u00fcft und anschlie\u00dfend in einem gemeinsamen Repository zusammengef\u00fchrt. Von Jez Humble wurden dabei drei Prinzipien einer idealen Verwendung von Continous Integration beschrieben [2]:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>T\u00e4gliche Commits auf den Master-Branch<\/li><li>Automatische Builds und Tests nach jedem Commit<\/li><li>Bei Auftritt eines Fehlers muss dieser in 10 Minuten behoben werden k\u00f6nnen.<\/li><\/ol>\n\n\n\n<p>Die folgende Phase (Continuous Delivery) beschreibt die automatisierte Freigabe des validierten Codes an ein Repository. Nach dieser Phase m\u00fcssen alle Code-\u00c4nderungen auf Bugs getestet und in einem Repository hochgeladen sein, von wo aus jederzeit eine Produktivversion bereitgestellt werden kann [1].<\/p>\n\n\n\n<p>Continous Deployment ist die abschlie\u00dfende Phase, welche das automatisierte Bereitstellen des produktionsreifen Builds in die Produktivphase beschreibt [1].&nbsp;<\/p>\n\n\n\n<p>Da CI\/CD Pipelines heutzutage in vielen modernen Softwareteams genutzt werden, wurde im Rahmen dieses Projekts die Gelegenheit genutzt, eigene CI\/CD Pipelines zu entwickeln. Bis dato war im Team nur wenig Erfahrung \u00fcber CI\/CD Pipelines vorhanden. Da Frontend &amp; Backend Team jeweils ein eigenes Repository hatten, wurden separate Pipelines aufgesetzt.&nbsp;<\/p>\n\n\n\n<p>Bevor n\u00e4her auf die einzelnen Pipelines eingegangen wird, wird nachfolgend kurz die Entscheidung f\u00fcr Github Actions zusammengefasst. Die CI\/CD Pipelines wurden mit Github Actions entwickelt, um von den Vorteilen der Open-Source Actions profitieren zu k\u00f6nnen. In dem Blogeintrag von Jonas Hecht [4] sind die f\u00fcr dieses Projekt ausschlaggebenden Argumente f\u00fcr die Nutzung von Github Actions genannt. In diesem werden drei Wellen von unterschiedlichen CI\/CD Pipeline beschrieben, angefangen mit der ersten Welle, welche von der Jenkins Plattform dominiert wurde. Heute noch ist die Open-Source-Software Jenkins weit verbreitet. Jedoch muss Jenkins selbst gehostet werden, und bedarf viel Konfigurationsaufwand, weshalb dies keine Option f\u00fcr dieses Projekt war.&nbsp;<\/p>\n\n\n\n<p>Zur Diskussion stand jedoch Gitlab CI\/CD, als Alternative f\u00fcr Github Actions. Beide nutzen das Pattern Pipeline-As-Code, durch welches laut dem Blogartikel von Jonas Hecht die zweite Welle von CI\/CD Plattformen eingeleitet wurde (auch Jenkins 2.0 nutzt dieses Pattern). Kurzgefasst beschreibt das Pattern das Definieren von Pipelines in einer Codedatei. Diese spezifiziert die verschiedenen Phasen, Jobs und Aktionen, welche von einer Pipeline ausgef\u00fchrt werden sollen. Diese Codedatei ist meist eine .yml Datei, kann aber auch in einer anderen Sprache geschrieben sein. Der Vorteil gegen\u00fcber Konfigurations-GUIs ist, dass sie im Code-Repository gespeichert wird und damit versioniert ist. Mehrere Entwickler k\u00f6nnen so an der Pipeline arbeiten und \u00c4nderungen leicht r\u00fcckg\u00e4ngig gemacht werden. Au\u00dferdem sind \u00c4nderungen an der Pipeline f\u00fcr andere Entwickler direkt sichtbar.&nbsp;<\/p>\n\n\n\n<p>Nach l\u00e4ngerer Internetrecherche kann man einige Meinungen lesen, welche entweder Pro Gitlab oder Pro Github sind. Im Blogartikel von Bruno Amaro Almeida [5] werden alle Konkurrenten aufgelistet und beschrieben. In seinem Fazit schreibt er, dass alle Anbieter genutzt werden k\u00f6nnen, um eine funktionierende Pipeline zu entwickeln, jedoch variiert der operative Overhead und der Aufwand. Dasselbe wird auch in [4] beschrieben. Dieser operative Overhead ist bei Github Actions geringer, weshalb zuletzt auch die Entscheidung auf diesen Pipeline-Anbieter fiel. Durch den Release von Github Action in 2020 kam ein neuer Konkurrent auf den Markt, durch welchen nun alle gro\u00dfen Git-Repository Anbieter wie Gitlab, Bitbucket und Github eigene L\u00f6sung f\u00fcr CI\/CD Pipelines anbieten. Der Vorteil von Github Actions sind zum einen die 2000 Runner Minuten im kostenlosen Plan (im Vergleich zu 400 Minuten beim kostenlosen Plan von Gitlab) und zum anderen der Marketplace der Actions. In [4] wird dies als die neue dritte Welle der CI\/CD Plattformen bezeichnet. Actions sind Open-Source-Software, welche wie Lego Bl\u00f6cke in der Pipeline-Beschreibung genutzt und kombiniert werden k\u00f6nnen. Will man beispielsweise eine bestimmte Aktion, wie das deployen eines Frontends in Firebase in der .yml Datei definieren, dann kann man eine von Google erstellte Action zum deployen in Firebase nutzen, welcher man lediglich die notwendigen Tokens und ID\u2019s \u00fcbergeben muss. Dadurch wird der Codeumfang reduziert und das Entwickeln von Pipelines vereinfacht. Durch Actions wird das Wiederverwenden von pipeline-spezifischem Code erm\u00f6glicht. Die Actions selbst k\u00f6nnen dabei entweder von der Community, Github oder von Firmen entwickelt und gepflegt werden. Dies war der ausschlaggebende Grund f\u00fcr die Nutzung von Github Actions. Wie auch schon in [4] erw\u00e4hnt, kann so in kleinen Teams eine Pipeline aufgesetzt werden, ohne dabei ein eigenes Team haben zu m\u00fcssen, welches nur f\u00fcr die Pflege von Pipelines zust\u00e4ndig ist. Allerdings entsteht so auch der Nachteil, dass das Wissen \u00fcber die genaue Funktionsweise der einzelnen Actions verloren geht. Baut man selbst eine Pipeline, so ist man sich im Klaren, welcher Schritt nach welchem ausgef\u00fchrt wird. Bei den einzelnen Actions fehlt das Wissen, bzw. muss man sich hierf\u00fcr die Beschreibung der Action genauer ansehen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Frontend<\/h3>\n\n\n\n<p>Im Frontend Team wurden CI\/CD Pipelines genutzt, um automatisiert Tests und den Build-Prozess durchzuf\u00fchren, mit anschlie\u00dfendem Deployment in den verschiedenen Environments. Dazu wurden folgende Environments festgelegt: Development, Staging und Production. In der folgenden Abbildung ist der Prozess von Development bis zu Production abgebildet.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh4.googleusercontent.com\/svV443XbT1qiR2r3h8IBp8MqNfGqjL7c9Oi9GFUr5N8YHuS1woMWLZVa9t96N0CaZItHlg_uJC6Jkx_z_C8ikI7-Kq2G7i7SA8sImT7jhAtgRgOOx0KhO6AdaEXRHaqYjABlT_eA\" alt=\"\" \/><\/figure>\n\n\n\n<p>Zu sehen ist, dass man sich w\u00e4hrend der Entwicklung im Environment Development befindet. Dort findet alles lokal auf dem eigenen Rechner statt und es wird in einem separatem Branch gearbeitet. Sobald die Entwicklung in diesem Branch abgeschlossen ist, wird ein Pull Request in den Master gestellt. Dieser Pull Request triggert dabei die erste Pipeline, welche in der Datei \u201cpull-request.yml\u201d (<a href=\"https:\/\/github.com\/Sharetopia\/Frontend\/blob\/main\/.github\/workflows\/pull-request.yml\">https:\/\/github.com\/Sharetopia\/Frontend\/blob\/main\/.github\/workflows\/pull-request.yml<\/a>) beschrieben ist. Diese f\u00fchrt zuerst die Tests aus, baut anschlie\u00dfend das Frontend und deployt es zum Schluss auf surge.sh.&nbsp;<\/p>\n\n\n\n<p>Die anderen Teamkollegen haben nun die M\u00f6glichkeit zum einen den Pull Request zu reviewen und zum anderen die Staging Version auf surge auszuprobieren. Anschlie\u00dfend, nachdem der Pull Request genehmigt und gemerged ist, wird die zweite Pipeline, welche in der Datei \u201cpush-main.yml\u201d (<a href=\"https:\/\/github.com\/Sharetopia\/Frontend\/blob\/main\/.github\/workflows\/push-main.yml\">https:\/\/github.com\/Sharetopia\/Frontend\/blob\/main\/.github\/workflows\/push-main.yml<\/a>) beschrieben ist, getriggert.&nbsp;<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Auswahl der Hostinganbieter<\/h4>\n\n\n\n<p>F\u00fcr das Staging-Hosting wurde surge.sh verwendet, aufgrund der einfachen und kostenlosen Nutzungsm\u00f6glichkeit. Surge.sh bietet einen kostenlosen Plan an, bei welchem man unbegrenzt Versionen ver\u00f6ffentlichen kann, was im Staging Fall sehr n\u00fctzlich ist [6]. Alternativ dazu w\u00e4re auch Github Pages infrage gekommen, jedoch m\u00fcsste man hierf\u00fcr entweder einen bezahlten Plan nutzen oder das Repository \u00f6ffentlich machen.&nbsp;<\/p>\n\n\n\n<p>Interessanter war die Auswahl des Production-Hostings. Dort standen drei unterschiedliche Hosting Anbieter zur Auswahl: AWS Amplify, Firebase und Netlify. Dabei wurden zuerst die Kosten verglichen.&nbsp;<\/p>\n\n\n\n<p>Wenn man davon ausgeht, dass man 30.000 Seitenaufrufe im Monat hat, wobei 1 Seitenaufruf ca. 2 MB Daten vom Hosting Anbieter (Bilder werden separat im S3 Bucket geladen) transferiert und 1 GB Hosting Speicher ben\u00f6tigt, ergibt sich folgendes Kostenmodell pro Monat:&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><\/td><td>AWS Amplify<\/td><td>Firebase<\/td><td>Netlify<\/td><\/tr><tr><td>Preismodell<\/td><td>Pay-as-you-go<\/td><td>Pay-as-you-go<\/td><td>Festbetrag (Starter<\/td><\/tr><tr><td>60 GB Datentransfer pro Monat<\/td><td>9,00$<\/td><td>7,50$<\/td><td>0,00$<\/td><\/tr><tr><td>1 GB Hosting<\/td><td>0,02$<\/td><td>0,00$<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Geht man von 200.000 Seitenaufrufen aus, ergibt sich folgendes Modell:&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><\/td><td>AWS Amplify<\/td><td>Firebase<\/td><td>Netlify<\/td><\/tr><tr><td>Preismodell<\/td><td>Pay-as-you-go<\/td><td>Pay-as-you-go<\/td><td>Festbetrag (Pro)<\/td><\/tr><tr><td>400 GB Datentransfer pro Monat<\/td><td>60$<\/td><td>58,50$<\/td><td>19$<\/td><\/tr><tr><td>1 GB Hosting<\/td><td>0,02$<\/td><td>0,00$<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Zum Veranschaulichen des Skalierungseffekt wird nun angenommen, dass man 1.000.000 Aufrufe im Monat hat:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><\/td><td>AWS Amplify<\/td><td>Firebase<\/td><td>Netlify<\/td><\/tr><tr><td>Preismodell<\/td><td>Pay-as-you-go<\/td><td>Pay-as-you-go<\/td><td>Festbetrag (Pro)<\/td><\/tr><tr><td>2000 GB Datentransfer pro Monat<\/td><td>300$<\/td><td>\u2248300$<\/td><td>339$<\/td><\/tr><tr><td>1 GB Hosting<\/td><td>0,02$<\/td><td>0,00$<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Vergleich [7] [8] [9]<\/p>\n\n\n\n<p>Zu sehen ist, dass die beiden Pay-as-you-go Anbieter sich in den Kosten relativ \u00e4hnlich sind, w\u00e4hrend Netlify zu Beginn deutlich g\u00fcnstiger ist, und erst bei extrem vielen Seitenaufrufen schnell teuer wird. Da es sehr unrealistisch ist, so viele Seitenaufrufe zu bekommen, w\u00e4re Netlify die richtige Wahl.&nbsp;<\/p>\n\n\n\n<p>Da dies jedoch ein Studentenprojekt ist und nicht zur wirklichen Ver\u00f6ffentlichung gedacht ist, reichen die beiden kostenlosen Pl\u00e4ne von Firebase und Netlify aus. Die Nutzung von Firebase w\u00fcrde die kostenlose Nutzung von Google Analytics mit sich bringen, was bei der Analyse der Seitennutzung wiederum sehr hilfreich sein kann. Leider wird f\u00fcr die kostenlose Nutzung von Amplify eine Hinterlegung der Kreditkarte vorausgesetzt, weshalb AWS nicht genutzt werden kann. F\u00fcr die Entscheidung zwischen Netlify und Firebase wurde zuletzt noch die Art und Weise des Deployments hinzugezogen. W\u00e4hrend Netlify darauf baut, selbst den Build durchzuf\u00fchren (wo wiederum nur 300 Minuten inklusive sind), kann bei Firebase eine Github Action genutzt werden (dort sind 2000 Minuten inklusive). Aus diesem Grund wurde Firebase als Production Hosting ausgew\u00e4hlt, da es erm\u00f6glicht, das Deployment in die CI\/CD Pipeline mit einzubauen und die kostenlose Nutzung von Google Analytics inbegriffen ist.&nbsp;<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Aufbau Pipeline<\/h4>\n\n\n\n<p>In der folgenden Abbildung ist die push-main.yml zu sehen. Diese beschreibt zwei Jobs: \u201ctest\u201d und \u201cbuild_and_deploy\u201d. Beide Jobs nutzen Github Actions aus dem Marketplace.&nbsp;<\/p>\n\n\n\n<p>In den beiden blauen K\u00e4sten wird zuerst der aktuelle Code mithilfe von \u201cactions\/checkout@v2\u201dausgecheckt und anschlie\u00dfend mithilfe von \u201cactions\/setup-node@v2\u201d eine Node Umgebung auf der Ubuntu Instanz aufgesetzt. Anschlie\u00dfend werden alle npm Packages mit dem Befehl \u201cnpm ci\u201d installiert.&nbsp;<\/p>\n\n\n\n<p>Der rote Kasten beschreibt das Ausf\u00fchren der Tests mit dem Befehl \u201cnpm run test:unit\u201d, nachdem alle Dependencies geladen sind.&nbsp;<\/p>\n\n\n\n<p>Der gelbe Kasten beschreibt das Ausf\u00fchren des Build Prozesses mit dem Befehl \u201cnpm run build\u201d.&nbsp;<\/p>\n\n\n\n<p>In dem gr\u00fcnen Kasten ist beschrieben, dass der fertige Build zu Firebase hochgeladen werden soll. Hierf\u00fcr wurde die Github Action \u201cFirebaseExtended\/action-hosting-deploy@v0\u201d von Firebase selbst genutzt. Diese ben\u00f6tigt als Konfiguration den \u201crepoToken\u201d und \u201cfirebaseServiceAccount\u201d, welche als Secrets im Repository hinterlegt sind.<\/p>\n\n\n\n<p><strong><img loading=\"lazy\" decoding=\"async\" width=\"602\" height=\"851\" src=\"https:\/\/lh3.googleusercontent.com\/vZYnFP2nB9nSc54FWOggKWKfByn8IeawFUg_Z2ltSfp3aTer8pbJ5n6-jdpc0OmlS7zU7Ip7Wm1ZR1WI3Y40AorLQBMcVkb8ykBGsPy8FR2HS1vpwgOzLG9HYafYOltbveUkNt87\"><\/strong><\/p>\n\n\n\n<p><em>main.yml File<\/em><\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Pipeline Optimierungen&nbsp;<\/h4>\n\n\n\n<p>Bei der Erstellung der Pipeline traten einige Fehler auf, wie beispielsweise, dass das \u201cworking-directory\u201d nicht richtig gesetzt wurde, oder falsche Testbefehle (ohne \u201c:unit\u201d) genutzt wurden. Diese Fehler zu beheben war recht simpel, jedoch war es zeitaufwendig, da die Pipeline wieder erneut gestartet werden musste. In der folgenden Abbildung ist ein Durchlauf vor der Optimierung der Pipeline zu sehen.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh5.googleusercontent.com\/4F_NEoRizBfTOQTK-lnbtexFiMNzxr2ILOboQDFfuP3Dg0P5WjPW9iKDWHWXwcyqx3QPbAxeDrL08U0X93KWxYvPoZlq4j8MphifT9GrUghx45RZiPdh-uTBRgll1DJ-fh63jCjO\" alt=\"\" \/><figcaption>Pipeline-Durchlauf ohne Optimierung<\/figcaption><\/figure>\n\n\n\n<p>Aufgefallen ist, dass besonders der Befehl \u201cnpm ci\u201d lange Zeit ben\u00f6tigt, da dieser bei jedem neuen Durchlauf erneut alle Node-Modules herunterl\u00e4dt. Um diesen Schritt zu optimieren, wurde f\u00fcr die Node-Modules ein Cache verwendet, um diese nicht immer alle erneut laden zu m\u00fcssen. Hierf\u00fcr bietet die Github Action \u201cactions\/setup-node@v2\u201d die M\u00f6glichkeit, die Node-Modules zu cachen. Durch Aktivieren des Caches (siehe blauer Kasten) wurde der Durchlauf der Pipeline um 1 Minute und 30 Sekunden beschleunigt, was in der folgenden Abbildung zu sehen ist.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh4.googleusercontent.com\/uR70iwZdiL4UoIVz_pBB4_LaaoTzMuqCBCM1LcMQHLVWRqMdnzS1RVws6KvdUPYgHSOX6o2dq1m1WXjBfTRYj0T_lNIYb71Zq4P0MpQw3ezdcccpuo8DyZwMCFDSp8p_lAeCcnkt\" alt=\"\" \/><figcaption>Pipeline-Durchlauf mit Optimierung&nbsp;<\/figcaption><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Backend<\/h3>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh5.googleusercontent.com\/LZXnuU6BgsggkXk0on4fKHXUM6EkiMbSj-ld7XIXDTIU7xHY4SzcqQBohjGrgU5gfZiFZHsxVNmeKy0BjTOmrOSO7Mdb2rgilnRrDdclNpUnDisJW6DE-TRsX5NnjIBTB7lci_aS\" alt=\"\" \/><\/figure>\n\n\n\n<p>Analog zum Frontend nutzen wir im Backend ebenfalls drei Environments (Development, Staging und Production).<\/p>\n\n\n\n<p>Das Development Environment kann entweder in einem lokalen Kubernetes Cluster minikube oder \u00fcber docker-compose gestartet werden. Das Staging und Production Environment wird in ein Kubernetes Cluster in der Cloud (EKS von AWS) deployed. Wir deployen f\u00fcr die unterschiedlichen Environments ein Cluster und trennen diese via Kubernetes Namespaces, anstatt unabh\u00e4ngige Cluster f\u00fcr jedes Environment zu nutzen. Vadim Eisenberg nennt in einem Blogartikel daf\u00fcr mehrere Gr\u00fcnde [11], die uns dazu bewegt haben auf ein Cluster zu setzen, darunter fallen reduzierter administrativer Aufwand und geringere Kosten. Er nennt aber auch Beweggr\u00fcnde, wieso man mehrere Cluster einsetzen sollte, darunter fallen verbesserte Sicherheit, dass man unterschiedliche Kubernetes Versionen in den Environments haben kann und dass Cluster (f\u00fcr uns sehr ferne) Limitierungen in der Skalierbarkeit haben.<\/p>\n\n\n\n<p>Ebenfalls analog zum Frontend sorgen Github Actions f\u00fcr das Continuous Deployment in das Kubernetes Cluster. Die Actions wechseln zuerst auf den f\u00fcr das Environment spezifischen Branch z. B. Production.&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh6.googleusercontent.com\/h8rEi61rGRm6fkWjykAs9DRJlcZt68jrWwwzA_ocNDGCxIhnN1DGzTBR0cX6CWIGWk-EsCi9em3UgTVDF_PXaJ5u3sFez_z6qUWiBy06wcCJBOxteLHH3McnYLDjKzpm2iYDckcD\" alt=\"\" \/><\/figure>\n\n\n\n<p>Anschlie\u00dfend wird mit docker build das Container Image gebaut und in das Amazon ECR (Elastic Container Registry) geschoben. ECR ist ein AWS Service, der sozusagen ein privates Docker Hub bereitstellt f\u00fcr die private Cloud Umgebung.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh4.googleusercontent.com\/-7YLoV55OqHSDl4SqdVBFVEnju7ydEQmsMDjktoTqPjzzFrarc1zYw1aNw2qfEc-pIfrpFINBmIiKnCsSyTC3gdz2Oj6ZCLXK72NOHKHm5LbKXhh8e4kRrXzlBHjnQKossVw9CvG\" alt=\"\" \/><\/figure>\n\n\n\n<p>Anschlie\u00dfend nutzen wir die \u201ckodermax\/kubectl-aws-eks\u201d Action zum deployen ins Kubernetes Cluster. Diese Action wrapped im Prinzip lediglich das Command Line Program kubectl konfiguriert auf das AWS EKS Cluster. Voraussetzung hierf\u00fcr ist die Konfiguration von f\u00fcr das Cluster essenziellen Credentials in den Github Secrets.<\/p>\n\n\n\n<p>Im ersten Step des Deployments wird das zuvor gebaute Image \u00fcber rollout deployed. Im zweiten Schritt wird das deployment verifiziert.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh3.googleusercontent.com\/cO8gHKFRvqYOI39MEScbt932cWrGclySWURF0RFjwUCbjyspIBye3m08OI1KqgrD7x4oKILIk2nm_jgmCPIv5qCU4p5Zb2LQRzBU8ayHso2b-jqWsozU6_RYHKpQoI_JDwgobdbt\" alt=\"\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Container &amp; Container Orchestrierung<\/h2>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh5.googleusercontent.com\/5vwy5Vj35ISNxWhKV7ojoLA66N9ZMxU4cnV7Pe3XrYAPoMlL8cqwfQjWxWitDmRXJxPhhCImgCqibH5upwBI5gHWd6UEiho8zI6h4O2G_q0krEMXuQCN6s9SZ8lgnMzU36x6V2c2\" alt=\"\" \/><figcaption>Kubernetes Deployment vom Productservice<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh5.googleusercontent.com\/zX8QiIpCFF5YN5XVZ6_gHxjw-igCxuVXtWvVqWe8eYKdjf0hlS1lTy44SiS0FLrZkb_uDzDWdrE0jocq_OR0kwqpTPw4Jdw_Cjj2f4_Yu2Y387aUB83Yv7ymgEW_0-uw4zsesVDe\" alt=\"\" \/><figcaption>Kubernetes Service vom Productservice<\/figcaption><\/figure>\n\n\n\n<p>F\u00fcr die lokale Entwicklung haben wir das Tool docker-compose und sp\u00e4ter minikube zum Orchestrieren der Container verwendet.<\/p>\n\n\n\n<p>Wir haben das Programm kompose-convert verwendet, um f\u00fcr jeden Container aus der docker-compose.yml eine deployment und service yml Datei f\u00fcr Kubernetes zu erzeugen. Die deployment Dateien beschreiben einen Satz an identischen Pods (Pods sind eine Einheit aus ein oder mehreren Containern in der Kubernetes API) und deren Konfiguration. Die service Datei beschreibt das Networking f\u00fcr jedes der deployments. Anschlie\u00dfend konnten wir mittels dieser Dateien unsere Anwendung einfach in einem lokalen minikube cluster deployen und die Github Actions zum deployen ins Kubernetes Cluster nutzen. Beim testweisen Deployen eines EKS Clusters mittels eksctl fiel auf, dass das default EKS Cluster Node Groups bestehend aus VMs aus der m5 Familie dem Cluster hinzuf\u00fcgen m\u00f6chte. Hier ist Obacht geboten, wenn man das Cluster preiswert f\u00fcr Test- und oder Bildungszwecke betreiben m\u00f6chte, dass die Kosten nicht au\u00dfer Kontrolle geraten.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Observability<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Metriken<\/h3>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh4.googleusercontent.com\/lsYSady6YqB-BQ6Gpr0Ok7U0sHvnGSxPU6nrd7pYgeocX0OUihl40suiczHH1D5_pl9hH_qFRQ-Ovi2sn0tQjav0BbiySvyxVfTwA0sX-Y1Ct8chGJhUMEtEzJE4Eg4XBvpgMq36\" alt=\"\" \/><figcaption><em>CloudWatch Metrics Application Load Balancer<\/em><\/figcaption><\/figure>\n\n\n\n<p>Zum \u00dcberwachen der User Experience der Services im Cluster haben wir CloudWatch Metrics verwendet. CloudWatch Metrics kann wichtige Metriken von AWS Ressourcen wie z. B. dem AWS ALB (Application Load Balancer) speichern und anschaulich darstellen.&nbsp;<\/p>\n\n\n\n<p>F\u00fcr die User Experience wichtig sind hierbei Metriken wie zum Beispiel die Response Times oder auch HTTP Errors (hier besonders der 500 Internal Server Error), denn die User Experience kann unter langwierigen API Requests und Bugs stark leiden. Bei den Response Times sollte beim Beobachten vor allem auf die Ausrei\u00dfer (hohe Perzentile z. B. p95, p99, \u2026) geachtet werden.<\/p>\n\n\n\n<p>Auch interessant zu betrachten ist, ob und wie weit zuvor antizipierte Request Counts und deren Verteilung sich mit realen Zahlen decken.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Logging<\/h3>\n\n\n\n<p>Unsere Spring-Anwendung verwendet Logback als Logging-Framework, welches auch durch den Default Logger von Spring Boot verwendet wird. Als Facade f\u00fcr Logback nutzen wir SLF4J (Simple Logging Facade for Java) in unserem Code. Dadurch erhalten wir eine generische API, die unabh\u00e4ngig vom spezifischen Logging-Framework ist. Die log-spring.xml Datei f\u00fcr die Konfiguration von Logback sieht wie folgt aus:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh4.googleusercontent.com\/EDdV2Q2AOVuDcR1cl6yxH2_wztsUn6_c8MFVkw2tJSnKUHkvq502FskVJKB33YRlbIAJPVJrRv_OCs7VZ_5kfKWubKmmM6xYcDY76xJFeiQajQ0Srbqyz82O0-bRwh1xKxFdkLQz\" alt=\"\" \/><figcaption>logback-spring.xml Konfigurationsdatei<\/figcaption><\/figure>\n\n\n\n<p>Hier definieren wir zwei Appender, welche jeweils die Aufgabe haben, Logs auf gewisse Weise auszugeben. Der erste Appender definiert dabei, wie die Logs auf der Standardausgabe ausgegeben werden sollen. Hierf\u00fcr wird ein Pattern definiert, welchem diese Logs entsprechen sollen. Der zweite Appender definiert, dass die Logs in eine Datei namens application.log geschrieben werden sollen. Durch die Referenz auf diese beiden Appender innerhalb von &lt;root&gt; geben wir an, dass diese an den Root-Logger angeh\u00e4ngt werden.<\/p>\n\n\n\n<p>Wie zuvor erw\u00e4hnt, wollen wir unsere Logs mit einer Logstash Pipeline weiterverarbeiten. Zusammengefasst soll diese dabei den folgenden Ablauf unterst\u00fctzen:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh5.googleusercontent.com\/2skBjBiPbFBC_m36QPje4qNUaR-Lzot78ewgYQVS8zFHfrWhKmM6ViX18JywWqj9VXghMsgsGCsd8Aoi2dp_Ibqr1XNjMZ_6OchCVMGYkTnyKmcREj5Es-1otNfOxSDV1VuNehdU\" alt=\"\" \/><\/figure>\n\n\n\n<p>Dieser Aufbau l\u00e4sst sich grob wie folgt erkl\u00e4ren: Im ersten Teil werden, wie in der gezeigten log-spring.xml definiert, die Logs in die entsprechende Datei geschrieben. Wird eine Zeile an diese Datei angeh\u00e4ngt, wird diese als Input an Logstash weitergegeben. Logstash verarbeitet diese Logs dann nach bestimmten definierten Regeln und speichert diese als Output in Elasticsearch. Die in Elasticsearch gespeicherten Daten k\u00f6nnen dann mit Kibana n\u00e4her analysiert werden. Im folgenden Teil soll dieser Ablauf noch etwas detaillierter erkl\u00e4rt werden.<\/p>\n\n\n\n<p>In der unteren Abbildung ist die logstash.config dargestellt, welche zur Konfiguration der Logstash-Pipeline dient. Der erste wichtige Teil dieser Datei ist in der folgenden Abbildung dargestellt:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh4.googleusercontent.com\/gS9oCUa5n14MLgVhWbqCK6thxYGizAX_hOe4CGUq7jzymqYCSTUbyWLSoryPZ5vVvObLgZSTMDcdKsVhUC6x4NqAnSm5qv-E3Xkn51NhPwaNw4LIF8ddvoIQwSkJMnoWBULitnKS\" alt=\"\" \/><figcaption>Ausschnitt aus logstash.conf Konfigurationsdatei<\/figcaption><\/figure>\n\n\n\n<p>Hier sieht man die Definition des Input-Filters, der festlegt, welche Daten den Input der Pipeline bilden. Hier geben wir also den Pfad unserer application.log-Datei an.<br>Der n\u00e4chste Schritt w\u00e4re jetzt eigentlich, diese gesammelten Logs in Elasticsearch zu speichern, um diese anschlie\u00dfend mit Kibana visualisieren zu k\u00f6nnen. Vorher sind aber noch gewisse Vorverarbeitungsschritte f\u00fcr die einzelnen Logs innerhalb unserer Pipeline notwendig. Eine Log-Zeile der Spring Anwendung nach dem Start der Anwendung sieht z.B. so aus:<\/p>\n\n\n\n<p>11-02-2022 00:47:32.952 [main] INFO&nbsp; d.s.p.ProductServiceApplicationKt.logStarted &#8211; Started ProductServiceApplicationKt in 9.947 seconds (JVM running for 10.945)<\/p>\n\n\n\n<p>Da wir sp\u00e4ter die einzelnen Felder wie z.B. das Log-Level (hier INFO) analysieren k\u00f6nnen wollen, m\u00fcssen wir ein solches Statement in seine einzelnen Bestandteile zerlegen. Hierbei kommt erschwerend hinzu, dass beispielsweise unsere eigenen Log-Statements im Code beim Aufruf unserer Routen einen leicht ver\u00e4nderten Aufbau haben:<\/p>\n\n\n\n<p>16-02-2022 16:59:13.308 [http-nio-8080-exec-9] INFO&nbsp; d.s.p.p.controller.ProductController.getAllProducts &#8211; Fetched all products. {method=GET, endpoint=\/products, requesterUserId=204e1304-26f0-47b5-b353-cee12f4c8d34}<\/p>\n\n\n\n<p>Wie man sehen kann enthalten unsere eigenen Log-Statements zus\u00e4tzlich noch Schl\u00fcssel-Werte Paare in spitzen Klammern, die wir auch separat erfassen m\u00f6chten. Um diese Informationen aus Logs parsen zu k\u00f6nnen, stellt Logstash den sogenannten grok parser zur Verf\u00fcgung. In unserer Config-Datei haben wir hierf\u00fcr folgende Definitionen eingef\u00fcgt:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh5.googleusercontent.com\/Lu3hfo4V60_EZcNrouzKXiElvXfKtdPqePYUwOJk6uiIYx4AmNHbfvYMtPfsXuuI3Ye80Q6hF7QTyW5GP_sx7wEv22dkVwN9aOTovlPMyPIeOz7UJ9gCdUBM0fLMgCY_LVg0Kpx5\" alt=\"\" \/><figcaption>Ausschnitt aus logstash.conf Konfigurationsdatei<\/figcaption><\/figure>\n\n\n\n<p><em>filter<\/em> gibt hierbei an, dass Definitionen bez\u00fcglich der Verarbeitung der eingehenden Log-Daten folgen. Mit <em>grok<\/em> definieren wir den erw\u00e4hnten grok-parser. In diesem definieren wir wiederum unsere zwei Patterns, in die der String zerlegt werden soll. Durch die Reihenfolge wird festgelegt, dass zuerst auf unser eigenes spezifischeres Pattern \u00fcberpr\u00fcft und falls dieses nicht geparst werden kann, das generischere Pattern angewendet werden soll. Erw\u00e4hnenswert ist au\u00dferdem in der obigen Abbildung die Definition von <em>kv.<\/em><strong> <\/strong>Dies ist ein Filter-Plugin, mit dem wir den RequestParam parameter (sofern vorhanden) in key-value Paare splitten k\u00f6nnen. Der gro\u00dfe Vorteil hierdurch ist, dass es keine Rolle spielt wie viele oder welche key-value Paare in <em>RequestParam<\/em> vorhanden sind. So k\u00f6nnen wir je nach der Funktion oder Route, in der wir unsere Log-Statements einf\u00fcgen, unterschiedliche zus\u00e4tzliche Parameter loggen. Die Zerlegung des obigen Logs aufgebaut nach dem spezifischeren Pattern sieht nach dem Anwenden der gezeigten Parsing-Regeln wie folgt aus:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh4.googleusercontent.com\/VrvK7WGFnHwvtzLhcgmBizuDW3maiVejqfJf-UTRgrMVbkBnp5faZnT4fiNGxDjNnq4m1Ldcj57Lfk4YqdsZLWhmY_SxlzFxHHYc9J6ncwbWdOemOSHcYskiULd8mcZw2D-cTzLv\" alt=\"\" \/><figcaption>Beispiel eines durch Logstash geparsten Logs<\/figcaption><\/figure>\n\n\n\n<p>Im letzten Teil unserer Config-Datei definieren wir anschlie\u00dfend noch, wo der Output unserer Logstash-Pipeline hingeleitet werden soll:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh6.googleusercontent.com\/-BCPiQGIWJSuOkfo7wvP87hhe1JMGFy2y4YPp1dVepR2kq423v71Fywg4E26SeXw7NfaTibYF--5LPXt0eFfyYrxrULoIs1tEBz0bRaw4vFeI4e3gGXyVw-9_3J3qwEZE-BXo62p\" alt=\"\" \/><figcaption>Ausschnitt aus logstash.conf Konfigurationsdatei<\/figcaption><\/figure>\n\n\n\n<p>Der obige Screenshot zeigt, dass der Output zum einen an die Standardausgabe von Logstash weitergeleitet wird. Zus\u00e4tzlich werden die Daten in einem tagesaktuellen Elasticsearch Index gespeichert. Insgesamt war dieses Einrichten der Pipeline auch ohne Vorerfahrung unkompliziert und ohne allzu gro\u00dfen Aufwand m\u00f6glich. Die gr\u00f6\u00dfte Herausforderung war, mit den verschiedenen Patterns der Logs umzugehen.<\/p>\n\n\n\n<p>Die durch Logstash in Elasticsearch gespeicherten Logs konnten wir anschlie\u00dfend mit Kibana weiter analysieren. Daf\u00fcr haben wir ein Index-Pattern in Kibana erstellt, welches alle Indizes mit dem Pr\u00e4fix \u201esharetopia-logs-\u201c einbezieht. Basierend auf unseren geparsten Feldern lassen sich dann verschiedene hoch individualisierbare Visualisierungen erstellen und einem Dashboard hinzuf\u00fcgen. Im folgenden Screenshot ist Teile eines solchen Dashboards zu sehen. Dies enth\u00e4lt z. B. ein Kreis-Diagramm, welches die Aufteilung der Logs auf die verschiedenen Log-Levels darstellt oder die Menge der Logs pro Tag in Form eines Balkendiagramms. Da die Anwendung allerdings nie wirklich im Live-Betrieb verwendet wurde, h\u00e4lt sich die Menge der gesammelten Daten und damit die Aussagekraft nat\u00fcrlich in Grenzen.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh5.googleusercontent.com\/rHpg7jSxzODZI1iEVyClxFca6nrCYyzhcqEbM7nkUvyM_uZUfKdxcIctTMtghSPFxTJzvvcWrc8aVfF6xQlE32bEjwd7wJoYdyEIBHZGeGaEH3tLvBscQ5Cb0CbTvdsDRkRMTfis\" alt=\"\" \/><figcaption><em>Beispiel eines erstellten Kibana-Dashboards<\/em><\/figcaption><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Fazit &amp; Lessons Learned<\/h1>\n\n\n\n<p>Aus der Vorlesung und dem dazugeh\u00f6rigen praktischen Projekt haben wir einiges an Wissen und Erfahrungswerten mitgenommen, so viel, dass wir nat\u00fcrlich kaum alles in diesem Fazit festhalten k\u00f6nnen. Im Folgenden wollen wir jedoch gerne auf ein paar Punkte eingehen, die in ihrer Wirkung einen besonderen Aha-Effekt auf uns hatten und die wir deshalb besonders hervorheben m\u00f6chten.<\/p>\n\n\n\n<p>Ein wichtiger Punkt war Napkin Math zum ungef\u00e4hren \u00dcberschlagen ben\u00f6tigter Ressourcen (Compute Power, Network, Storage, etc.) und Vergleichen von Anbietern hinsichtlich Leistung, finanziellen Aspekten und Soft Faktoren. Hierbei fiel uns vor allem auch auf, dass die Preisgestaltung der Cloud-Anbieter sehr intransparent ist und man sehr genau hinsehen muss, um eine halbwegs akkurate Sch\u00e4tzung der Kosten treffen zu k\u00f6nnen. Dabei haben wir auch festgestellt, dass unser mentales Modell von Cloud-Preisen \u00f6fters nicht der Realit\u00e4t entsprach. Leistungen, die man zuerst f\u00fcr g\u00fcnstig h\u00e4lt, sind in der Realit\u00e4t teurer und wiederum sind andere Leistungen, die man f\u00fcr teuer h\u00e4lt, eigentlich verh\u00e4ltnism\u00e4\u00dfig g\u00fcnstig.<\/p>\n\n\n\n<p>Wir haben viel gelernt \u00fcber Kubernetes und managed Kubernetes Services in der Cloud sowie dem Aspekt Observability und Logging. Dies sind Aspekte, die bis jetzt \u00f6fters bei Studienprojekten in der Vergangenheit eher vernachl\u00e4ssigt wurden.<\/p>\n\n\n\n<p>Wir haben auch einige Learnings aus diesem Projekt mitgenommen, die wir gerne in unser n\u00e4chstes Projekt mitnehmen wollen und Punkte, die wir in unserem n\u00e4chsten Projekt gerne anders machen wollen. W\u00e4hrend der Entwicklung fiel uns auf, dass unser Code relativ abh\u00e4ngig vom Spring Framework ist und die Einbettung des Codes in ein anderes Framework vermutlich mit einem relativ hohem Aufwand verbunden w\u00e4re. In der realen Welt kann es jedoch erforderlich sein, das Framework zu wechseln (oder den Code ganz ohne Framework zu verwenden). Gr\u00fcnde hierf\u00fcr k\u00f6nnten sein, dass ein Framework nicht mehr weiterentwickelt wird, das Framework gr\u00f6\u00dfere API-breaking Versionsspr\u00fcnge macht oder neue Features ben\u00f6tigt werden, die nicht Bestandteil des Frameworks sind. Weitere m\u00f6gliche Gr\u00fcnde w\u00e4ren, dass der Code in einer anderen Anwendung ben\u00f6tigt wird oder dass der Business Case es erforderlich macht auf ein anderes Framework zu setzen. Ein Weg den Code im n\u00e4chsten Projekt Framework-unabh\u00e4ngiger zu machen, w\u00e4re zum Beispiel, die gesamte Business Logik stattdessen in einer Library zu implementieren und dann aus dem Framework nur noch diese Library aufzurufen.<\/p>\n\n\n\n<p>In der begleitenden Vorlesung haben wir die TDD (Test Driven Development) Methode zum Entwickeln von Code kennengelernt, die wir gerne in unserem n\u00e4chsten Projekt anwenden w\u00fcrden. Wir denken, dass TDD uns dabei helfen k\u00f6nnte schneller kleine funktionale Code Bausteine zu entwickeln. Au\u00dferdem macht man sich vor der Implementierung schon Gedanken \u00fcber die Schnittstellen f\u00fcr die Implementierung, was auch beim kollaborativen Arbeiten im Team von Vorteil sein kann, da Kollaborateure wissen, gegen welche Schnittstellen sie programmieren m\u00fcssen.<\/p>\n\n\n\n<p>Wir haben in unserem Projekt nicht jeden Tag auf den Master committet, was nat\u00fcrlich in der Realit\u00e4t in einem Studienprojekt auch schwer ist, da man nicht jeden Tag daran arbeitet, jedoch h\u00e4tten wir in unserem Projekt durchaus \u00f6fter auf den Master committen k\u00f6nnen. TDD h\u00e4tte hier sicherlich auch gest\u00fctzt. Eigentlich ist es nach Martin Fowler [15] auch Teil von Continuous Integration jeden Tag auf den Master zu committen.<\/p>\n\n\n\n<p>Ein weiterer Punkt f\u00fcr unser n\u00e4chstes Projekt w\u00e4re, unsere Infrastruktur als Code zu implementieren (Infrastructure as Code). Hierf\u00fcr k\u00f6nnten wir unser Kubernetes Cluster und dessen Node Groups, sowie andere verwendete Cloud Ressourcen z. B. \u00fcber Terraform definieren.<\/p>\n\n\n\n<p>Es w\u00e4re auch von Vorteil, wenn wir f\u00fcr unser n\u00e4chstes Projekt einen Cloud Playground von der Uni z. B. in Form von Educational Accounts gestellt bekommen w\u00fcrden. In diesem Projekt mussten wir mit privaten Accounts (die unsere privaten Kreditkarten potenziell belasten) arbeiten, was die kollaborative Arbeit am Projekt erschwert hat und weshalb wir unsere Infrastruktur immer schnell wieder heruntergefahren haben, um keinen bzw. nur einen geringen Kostenaufwand zu haben. Wir h\u00e4tten auch gerne mehr Fokus auf die Hochverf\u00fcgbarkeit der Services gelegt (z. B. durch Replikation von Services), eine Implementierung h\u00e4tte hier jedoch den Kosten- und Zeitrahmen f\u00fcr dieses Studienprojekt gesprengt. Wir haben in diesem Projekt auch keinen gr\u00f6\u00dferen Fokus auf die Security der Infrastruktur und Software-Implementierungen gelegt. Beides w\u00e4ren interessante Ankn\u00fcpfpunkte f\u00fcr ein Folgeprojekt, basierend auf der existierenden Arbeit.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Quellen<\/h1>\n\n\n\n<p>[1]: <a href=\"https:\/\/www.redhat.com\/de\/topics\/devops\/what-is-ci-cd\">https:\/\/www.redhat.com\/de\/topics\/devops\/what-is-ci-cd<\/a><\/p>\n\n\n\n<p>[2]: <a href=\"https:\/\/martinfowler.com\/bliki\/ContinuousIntegrationCertification.html\">https:\/\/martinfowler.com\/bliki\/ContinuousIntegrationCertification.html<\/a>&nbsp;<\/p>\n\n\n\n<p>[3]: <a href=\"https:\/\/dev.to\/flippedcoding\/difference-between-development-stage-and-production-d0p\">https:\/\/dev.to\/flippedcoding\/difference-between-development-stage-and-production-d0p<\/a>&nbsp;<\/p>\n\n\n\n<p>[4]: <a href=\"https:\/\/blog.codecentric.de\/en\/2021\/03\/github-actions-nextgen-cicd\/\">https:\/\/blog.codecentric.de\/en\/2021\/03\/github-actions-nextgen-cicd\/<\/a>&nbsp;<\/p>\n\n\n\n<p>[5]: <a href=\"https:\/\/blog.thundra.io\/the-ci-cd-war-of-2021-a-look-at-the-most-popular-technologies\">https:\/\/blog.thundra.io\/the-ci-cd-war-of-2021-a-look-at-the-most-popular-technologies<\/a>&nbsp;<\/p>\n\n\n\n<p>[6]: <a href=\"https:\/\/surge.sh\/\">https:\/\/surge.sh\/<\/a>&nbsp;<\/p>\n\n\n\n<p>[7]: <a href=\"https:\/\/firebase.google.com\/pricing\">https:\/\/firebase.google.com\/pricing<\/a>&nbsp;<\/p>\n\n\n\n<p>[8]: <a href=\"https:\/\/aws.amazon.com\/de\/amplify\/pricing\/?nc=sn&amp;loc=4\">https:\/\/aws.amazon.com\/de\/amplify\/pricing\/?nc=sn&amp;loc=4<\/a>&nbsp;<\/p>\n\n\n\n<p>[9]: <a href=\"https:\/\/www.netlify.com\/pricing\/\">https:\/\/www.netlify.com\/pricing\/<\/a>&nbsp;<\/p>\n\n\n\n<p>[10]: <a href=\"https:\/\/martinfowler.com\/articles\/practical-test-pyramid.html#TheTestPyramid\">https:\/\/martinfowler.com\/articles\/practical-test-pyramid.html#TheTestPyramid<\/a><\/p>\n\n\n\n<p>[11]: <a href=\"http:\/\/vadimeisenberg.blogspot.com\/2019\/03\/multicluster-pros-and-cons.html\">http:\/\/vadimeisenberg.blogspot.com\/2019\/03\/multicluster-pros-and-cons.ht<\/a>ml<\/p>\n\n\n\n<p>[12]: <a href=\"https:\/\/aws.amazon.com\/ec2\/pricing\/on-demand\/\">https:\/\/aws.amazon.com\/ec2\/pricing\/on-demand\/<\/a><\/p>\n\n\n\n<p>[13]: <a href=\"https:\/\/cloud.google.com\/compute\/vm-instance-pricing\">https:\/\/cloud.google.com\/compute\/vm-instance-pricing<\/a><\/p>\n\n\n\n<p>[14]: <a href=\"https:\/\/cloud.google.com\/vpc\/network-pricing\">https:\/\/cloud.google.com\/vpc\/network-pricing<\/a><\/p>\n\n\n\n<p>[15]: <a href=\"https:\/\/martinfowler.com\/articles\/continuousIntegration.html\">https:\/\/martinfowler.com\/articles\/continuousIntegration.html<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>ein Artikel von Nicolas Wyderka, Niklas Schildhauer, Lucas Cr\u00e4mer und Jannik Smidt Projektbeschreibung In diesem Blogeintrag wird die Entwicklung der Applikation- und Infrastruktur des Studienprojekts sharetopia beschrieben. Als Teil der Vorlesung System Engineering and Management wurde besonders darauf geachtet, die Anwendung nach heutigen Best Practices zu entwickeln und dabei kosteneffizient zu agieren<\/p>\n","protected":false},"author":1043,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[649,262,2],"tags":[150,7,50,154],"ppma_author":[855],"class_list":["post-22395","post","type-post","status-publish","format-standard","hentry","category-interactive-media","category-rich-media-systems","category-system-engineering","tag-ci-cd","tag-cloud","tag-infrastructure","tag-kubernetes"],"aioseo_notices":[],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":27369,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2025\/02\/26\/cloudy-mit-aussicht-auf-worter-unser-weg-mit-crowdcloud\/","url_meta":{"origin":22395,"position":0},"title":"Cloudy mit Aussicht auf W\u00f6rter: Unser Weg mit CrowdCloud","author":"Simon Wimmer","date":"26. February 2025","format":false,"excerpt":"Willkommen zu unserem Erfahrungsbericht aus der Vorlesung \u201eSystem Engineering and Management\u201c. In den letzten Monaten haben wir uns an ein Projekt gewagt, das uns sowohl technisch als auch pers\u00f6nlich herausgefordert hat \u2013 CrowdCloud. Anstatt uns in trockene Theorien zu verlieren, m\u00f6chten wir euch in diesem Blog-Beitrag erz\u00e4hlen, wie aus einer\u2026","rel":"","context":"In &quot;System Engineering&quot;","block_context":{"text":"System Engineering","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/system-engineering\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/System_Engineering.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/System_Engineering.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/System_Engineering.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/System_Engineering.png?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/System_Engineering.png?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/System_Engineering.png?resize=1400%2C800&ssl=1 4x"},"classes":[]},{"id":27142,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2025\/02\/27\/entwicklung-eines-skalierbaren-file-share-services-mit-aws\/","url_meta":{"origin":22395,"position":1},"title":"Entwicklung eines skalierbaren File-Share-Services mit AWS","author":"Max Tyrchan","date":"27. February 2025","format":false,"excerpt":"tl;dr: Unser Semester-Projekt bestand im Aufbau einer skalierbaren File-Share-L\u00f6sung auf AWS auf Basis von NextCloud. Unsere Motivation bestand darin die volle Kontrolle \u00fcber die eigenen Daten zu erlangen, individuelle Anpassbarkeit zu erm\u00f6glichen und eine Kosteneffizienz zu erreichen. Es wurden klare Ziele in den Bereichen Verf\u00fcgbarkeit, Performanz, Sicherheit und Skalierbarkeit definiert,\u2026","rel":"","context":"In &quot;System Engineering&quot;","block_context":{"text":"System Engineering","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/system-engineering\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/logo_nextcloud_blue-2.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/logo_nextcloud_blue-2.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/logo_nextcloud_blue-2.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/logo_nextcloud_blue-2.png?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/logo_nextcloud_blue-2.png?resize=1050%2C600&ssl=1 3x"},"classes":[]},{"id":26064,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2024\/02\/03\/das-neue-javascript-framework-qwik-js-mit-resumability-zur-optimalen-performance-im-web\/","url_meta":{"origin":22395,"position":2},"title":"Das neue JavaScript Framework Qwik.js &#8211; Mit Resumability zur optimalen Performance im Web?","author":"Tim Peters","date":"3. February 2024","format":false,"excerpt":"Aufgrund des mittlerweile riesigen Angebots und der Menge an Mitbewerbern in nahezu jeder Branche im Web sind die Konsumenten der Inhalte anspruchsvoller als je zuvor und der Page Speed ist ein entscheidender Erfolgsfaktor f\u00fcr jedes Online-Unternehmen. Webseiten, die schnell laden und ohne Verz\u00f6gerung auf Nutzerinput reagieren, halten die User nicht\u2026","rel":"","context":"In &quot;Interactive Media&quot;","block_context":{"text":"Interactive Media","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/interactive-media\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/vm6o42zSi0mK7OntVhP16W6OzSt7pjZZCVNu7KsyoI5nngqyiFVAO6FaCRb6nCvnr61gi8Fw-un2XKSeEMNoShtb-gKJMInIJbMFb3yRUnB51fadHVk9S_8Vx9Dn2qmQ7OIW4i7A0-nBtZ6JOw-XWZE.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/vm6o42zSi0mK7OntVhP16W6OzSt7pjZZCVNu7KsyoI5nngqyiFVAO6FaCRb6nCvnr61gi8Fw-un2XKSeEMNoShtb-gKJMInIJbMFb3yRUnB51fadHVk9S_8Vx9Dn2qmQ7OIW4i7A0-nBtZ6JOw-XWZE.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/vm6o42zSi0mK7OntVhP16W6OzSt7pjZZCVNu7KsyoI5nngqyiFVAO6FaCRb6nCvnr61gi8Fw-un2XKSeEMNoShtb-gKJMInIJbMFb3yRUnB51fadHVk9S_8Vx9Dn2qmQ7OIW4i7A0-nBtZ6JOw-XWZE.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/vm6o42zSi0mK7OntVhP16W6OzSt7pjZZCVNu7KsyoI5nngqyiFVAO6FaCRb6nCvnr61gi8Fw-un2XKSeEMNoShtb-gKJMInIJbMFb3yRUnB51fadHVk9S_8Vx9Dn2qmQ7OIW4i7A0-nBtZ6JOw-XWZE.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":12512,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2021\/02\/27\/progressive-web-apps-wer-braucht-noch-native-apps\/","url_meta":{"origin":22395,"position":3},"title":"Progressive Web Apps &#8211; Wer braucht noch native Apps?","author":"Fabiola Dums","date":"27. February 2021","format":false,"excerpt":"Progressive Web Apps sollen es erm\u00f6glichen die Vorteile des Webs und die nativer Apps zu nutzen, um so f\u00fcr jeden, \u00fcberall und auf jedem Ger\u00e4t, nutzbar zu sein. Was Progressive Web Apps eigentlich sind, welche Vor- und Nachteile sie mit sich bringen und ob sie in Zukunft native Apps komplett\u2026","rel":"","context":"In &quot;Interactive Media&quot;","block_context":{"text":"Interactive Media","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/interactive-media\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/02\/Bildschirmfoto-2021-02-26-um-12.21.05.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/02\/Bildschirmfoto-2021-02-26-um-12.21.05.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/02\/Bildschirmfoto-2021-02-26-um-12.21.05.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/02\/Bildschirmfoto-2021-02-26-um-12.21.05.png?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/02\/Bildschirmfoto-2021-02-26-um-12.21.05.png?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/02\/Bildschirmfoto-2021-02-26-um-12.21.05.png?resize=1400%2C800&ssl=1 4x"},"classes":[]},{"id":26254,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2024\/03\/21\/docker-security-hands-on-guide\/","url_meta":{"origin":22395,"position":4},"title":"Docker security: Hands-on guide","author":"Maximilian Tellmann","date":"21. March 2024","format":false,"excerpt":"Absichern von Docker Containern, durch die Nutzung von Best Practices in DockerFiles und Docker Compose. Einf\u00fchrung Es ist sehr wahrscheinlich im Alltag mit containerisierten Anwendungen in Ber\u00fchrung zu kommen, ohne sich dessen bewusst zu sein. In einer Zeit, in der sich der Trend der Unternehmen weiterhin stark in Richtung Cloud\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":26208,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2024\/02\/29\/die-meere-der-systemtechnik-navigieren-eine-reise-durch-die-bereitstellung-einer-aktien-webanwendung-in-der-cloud\/","url_meta":{"origin":22395,"position":5},"title":"Die Meere der Systemtechnik navigieren: Eine Reise durch die Bereitstellung einer Aktien-Webanwendung in der Cloud","author":"mk306","date":"29. February 2024","format":false,"excerpt":"Auf zu neuen Ufern: Einleitung Die Cloud-Computing-Technologie hat die Art und Weise, wie Unternehmen Anwendungen entwickeln, bereitstellen und skalieren, revolutioniert. In diesem Beitrag, der im Rahmen der Vorlesung \u201c143101a System Engineering und Management\u201d entstanden ist, werden wir uns darauf konzentrieren, wie eine bereits bestehende Webanwendung zur Visualisierung und Filterung von\u2026","rel":"","context":"In &quot;Cloud Technologies&quot;","block_context":{"text":"Cloud Technologies","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/scalable-systems\/cloud-technologies\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/Dashboard2-Kopie-1.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/Dashboard2-Kopie-1.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/Dashboard2-Kopie-1.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/Dashboard2-Kopie-1.png?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/Dashboard2-Kopie-1.png?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/Dashboard2-Kopie-1.png?resize=1400%2C800&ssl=1 4x"},"classes":[]}],"jetpack_sharing_enabled":true,"authors":[{"term_id":855,"user_id":1043,"is_guest":0,"slug":"js444","display_name":"Jannik Smidt","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/4fc0f10d53e9ed0e2510bbcda65170bb2462ed0da81dca543f01ddfb81154c2d?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/22395","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/users\/1043"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/comments?post=22395"}],"version-history":[{"count":13,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/22395\/revisions"}],"predecessor-version":[{"id":22440,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/22395\/revisions\/22440"}],"wp:attachment":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/media?parent=22395"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/categories?post=22395"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/tags?post=22395"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/ppma_author?post=22395"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}