Enterprise Application Development und Evolutionäre Informationssysteme - Exam
Aufgabe 1)
Schichtenarchitekturen und deren AnwendungArchitekturmuster für Software, bei dem die Anwendung in aufeinander aufbauende Schichten unterteilt wird.
- Basis: Jede Schicht hat bestimmte Verantwortlichkeiten
- Vorteile: Verbesserung der Modularität, Wiederverwendbarkeit und Wartbarkeit
- Typische Schichten: Präsentation, Geschäftslogik, Datenzugriff
- Anwendung in kaskadierenden Modellen und verteilten Systemen
- Kommunikation zwischen Schichten über definierte Schnittstellen
a)
Stelle ein dreischichtiges System dar, das eine einfache E-Commerce-Anwendung (z.B. Online-Shop) beschreibt. Identifiziere und erkläre die Verantwortlichkeiten jeder Schicht (Präsentation, Geschäftslogik, Datenzugriff) in diesem System.
- Welche Technologien oder Frameworks könnten in jeder Schicht typischerweise verwendet werden?
- Erkläre, wie der Datenfluss in diesem System von einer Kundenanfrage bis zur Antwort verläuft.
Lösung:
Schichtenarchitekturen und deren Anwendung in einem dreischichtigen E-Commerce-System (Online-Shop)
- Präsentationsschicht (Presentation Layer):
- Verantwortlichkeiten: Diese Schicht ist für die Darstellung der Daten und die Benutzerinteraktion verantwortlich. Sie verarbeitet die Anfragen der Benutzeroberfläche und zeigt die Daten, die von der Geschäftslogik bereitgestellt werden, an.
- Typische Technologien oder Frameworks: HTML, CSS, JavaScript, React, Angular, Vue.js
- Geschäftslogikschicht (Business Logic Layer):
- Verantwortlichkeiten: Diese Schicht implementiert die Geschäftsregeln und Prozesse der Anwendung. Sie verarbeitet die Daten, die von der Präsentationsschicht empfangen werden, und kommuniziert mit der Datenzugriffsschicht, um die erforderlichen Daten abzurufen oder zu speichern.
- Typische Technologien oder Frameworks: Java (Spring Boot), C# (.NET), Node.js (Express), Python (Django)
- Datenzugriffsschicht (Data Access Layer):
- Verantwortlichkeiten: Diese Schicht ist für den Zugriff auf die Datenbank und andere persistente Speicherungssysteme verantwortlich. Sie führt CRUD-Operationen (Create, Read, Update, Delete) aus und stellt der Geschäftslogikschicht die erforderlichen Daten bereit.
- Typische Technologien oder Frameworks: SQL (MySQL, PostgreSQL), MongoDB, Hibernate (ORM), Entity Framework
Datenfluss in einem dreischichtigen E-Commerce-System:- Ein Kunde sendet eine Anfrage (z.B. eine Produktabfrage) über die Benutzeroberfläche, die in der Präsentationsschicht verarbeitet wird.
- Die Präsentationsschicht sendet diese Anfrage an die Geschäftslogikschicht.
- Die Geschäftslogikschicht verarbeitet die Anfrage gemäß den Geschäftsregeln und sendet eine Anfrage an die Datenzugriffsschicht, um die erforderlichen Daten abzurufen.
- Die Datenzugriffsschicht führt eine Datenbankabfrage durch und sendet die Ergebnisse zurück an die Geschäftslogikschicht.
- Die Geschäftslogikschicht verarbeitet die Daten, führt ggf. Berechnungen oder Transformationen durch und sendet die verarbeiteten Daten an die Präsentationsschicht zurück.
- Die Präsentationsschicht zeigt die resultierenden Daten dem Kunden an.
b)
Angenommen, dass die E-Commerce-Anwendung zur Spitzenzeit eine unerwartet hohe Anzahl von Anfragen verarbeiten muss. Diskutiere auf Grundlage der Schichtenarchitektur, wie das System skaliert werden könnte, um diese Last effizient zu bewältigen.
- Erkläre sowohl horizontale als auch vertikale Skalierungstechniken für jede Schicht.
- Welche Herausforderungen könnten dabei auftreten und wie können diese überwunden werden?
Lösung:
Skalierung einer dreischichtigen E-Commerce-Anwendung zur Spitzenzeit
- Horizontale Skalierung:
- Präsentationsschicht: Bei der horizontalen Skalierung werden zusätzliche Server oder Instanzen hinzugefügt, um die Belastung über mehrere Maschinen zu verteilen. Ein Lastenausgleich (Load Balancer) kann hier verwendet werden, um eingehende Anfragen gleichmäßig auf mehrere Server zu verteilen.
- Geschäftslogikschicht: Auch hier können zusätzliche Instanzen der Anwendung hinzugefügt werden, um die Bearbeitung der Geschäftslogik auf mehrere Server zu verteilen. Microservices können eingesetzt werden, um bestimmte Geschäftslogiken getrennt und unabhängig voneinander zu skalieren.
- Datenzugriffsschicht: Datenbank-Replikation und Sharding können verwendet werden, um die Datenlast auf mehrere Datenbankserver zu verteilen. Bei Replikation werden Daten auf mehrere Server kopiert, bei Sharding wird die Datenbank in kleinere, handhabbare Segmente aufgeteilt, die auf verschiedenen Servern gespeichert werden.
- Vertikale Skalierung:
- Präsentationsschicht: Die Hardware-Ressourcen (z.B. CPU, RAM) der bestehenden Server werden erhöht, um mehr Anfragen zu verarbeiten.
- Geschäftslogikschicht: Auch hier können die Hardware-Ressourcen der bestehenden Server erhöht werden, um mehr Verarbeitungskapazität für die Geschäftslogik bereitzustellen.
- Datenzugriffsschicht: Die Datenbankserver können durch leistungsstärkere Hardware ersetzt oder aufgerüstet werden, um größere Datenmengen effizienter zu handhaben.
Herausforderungen und Lösungen:- Synchronisation von Daten: Bei der horizontalen Skalierung, insbesondere auf der Datenzugriffsschicht, kann die Synchronisation von Daten zwischen mehreren Servern schwierig sein. Dies kann durch konsistente Replikationsstrategien und den Einsatz von Datenbanken, die speziell für verteilte Systeme entwickelt wurden (z.B. Cassandra, MongoDB), bewältigt werden.
- Load Balancing und Session Management: Zuverlässige Lastverteilung und Sitzungsverwaltung (Session Management) sind kritisch. Load Balancer (z.B. Nginx, HAProxy) und zustandslose Sitzungen (stateless sessions), bei denen Sitzungsdaten im Cache (z.B. Redis, Memcached) gespeichert werden, können helfen, diese Herausforderungen zu überwinden.
- Komplexität und Kosten: Skalierung führt zu erhöhter Systemkomplexität und möglicherweise höheren Kosten. Hier ist eine genaue Planung erforderlich, um sicherzustellen, dass die Skalierungsstrategien sowohl technisch als auch wirtschaftlich sinnvoll sind. Automatisierungstools zur Bereitstellung und Überwachung (z.B. Kubernetes, Docker) können dabei unterstützen, die Komplexität zu managen und Kosten zu kontrollieren.
Aufgabe 2)
Du entwickelst eine Webanwendung zur Verwaltung von Produkten in einem Online-Shop. Diese Anwendung verwendet das MVC-Muster. Die Anwendung ermöglicht es Benutzern, Produkte hinzuzufügen, zu bearbeiten und zu löschen, sowie Produkte in einer Liste anzuzeigen. Die Daten für die Produkte werden in einer relationalen Datenbank gespeichert.
a)
Beschreibe die Implementierungsschritte für jede der drei Schichten (Model, View, und Controller) in der Webanwendung. Erläutere insbesondere, wie Datenbankoperationen in das Model integriert werden und wie der Datenfluss von Benutzereingaben über den Controller zum Model und von dort zur View erfolgt.
Lösung:
Implementierungsschritte für jede der drei Schichten (Model, View, und Controller) in der Webanwendung:
- Model:
- Definition der Datenstruktur: Erstelle Klassen, die die Produktdaten repräsentieren. Diese Klassen sollten Attribute wie ID, Name, Beschreibung, Preis und Lagerbestand enthalten.
- Integration von Datenbankoperationen: Implementiere Methoden für CRUD-Operationen (Create, Read, Update, Delete). Diese Methoden sollten SQL-Abfragen zur Interaktion mit der relationalen Datenbank enthalten. Beispielmethoden:
class ProductModel { public function createProduct($name, $description, $price, $stock) { // SQL-Abfrage, um ein neues Produkt zu erstellen } public function getAllProducts() { // SQL-Abfrage, um alle Produkte abzurufen } public function updateProduct($id, $name, $description, $price, $stock) { // SQL-Abfrage, um ein Produkt zu aktualisieren } public function deleteProduct($id) { // SQL-Abfrage, um ein Produkt zu löschen }}
- View:
- Controller:
- Annahme und Verarbeitung von Benutzereingaben: Implementiere Funktionen, die Benutzereingaben entgegennehmen und aufrufen, basierend auf der Aktion (z. B. Produkt hinzufügen, bearbeiten, löschen). Diese Funktionen rufen entsprechende Methoden im Model auf.
- Datenweitergabe an die View: Hol die Daten aus dem Model und übergib sie an die View, damit sie dem Benutzer angezeigt werden können. Beispiel für einen Controller:
class ProductController { public function addProduct() { if ($_SERVER['REQUEST_METHOD'] == 'POST') { // Benutzereingaben aus dem Formular ophalen $name = $_POST['name']; $description = $_POST['description']; $price = $_POST['price']; $stock = $_POST['stock']; // Methode im Model aufrufen, um das Produkt zu erstellen $productModel = new ProductModel(); $productModel->createProduct($name, $description, $price, $stock); // Umleitung zur Produktliste header('Location: /productlist'); } } public function showProductList() { // Produkte aus dem Model holen $productModel = new ProductModel(); $products = $productModel->getAllProducts(); // Daten an die View übergeben include 'views/productlist.php'; }}
Datenfluss in der Anwendung:
- Benutzereingabe: Der Benutzer füllt ein Formular in der View aus und sendet es ab (z. B. zum Hinzufügen eines neuen Produkts).
- Controller: Der Controller nimmt die Eingaben entgegen, validiert sie gegebenenfalls und ruft eine passende Methode im Model auf (z. B. createProduct).
- Model: Das Model führt die entsprechende Datenbankoperation durch (z. B. Einfügen eines neuen Datensatzes in die Datenbank) und gibt ein Ergebnis zurück.
- Controller: Der Controller verarbeitet das Ergebnis und bestimmt die nächste Aktion (z. B. Weiterleitung zur Produktliste).
- View: Die View wird aktualisiert und zeigt die geänderten Daten an (z. B. die neue Produktliste).
b)
Implementiere ein kleines Codebeispiel für jede der drei MVC-Komponenten in einer Programmiersprache Deiner Wahl (z.B. Python mit Django). Das Beispiel soll die Logik zum Hinzufügen eines neuen Produkts in die Datenbank zeigen.
Hinweis: Beschränke die Komplexität auf das Wesentliche, aber stelle sicher, dass alle relevanten Teile (Datenbankmodell, Formulareingabe, und Anzeige nach dem Hinzufügen) enthalten sind.
Lösung:
Hier ist ein kleines Codebeispiel für jede der drei MVC-Komponenten in Python mit Django. Das Beispiel zeigt die Logik zum Hinzufügen eines neuen Produkts in die Datenbank.
Model: Definiere das Datenbankmodell für ein Produkt.
from django.db import modelsclass Product(models.Model): name = models.CharField(max_length=255) description = models.TextField() price = models.DecimalField(max_digits=10, decimal_places=2) stock = models.IntegerField() def __str__(self): return self.name
View: Erstelle ein Formular und eine Ansicht für das Hinzufügen eines Produkts sowie die Anzeige nach dem Hinzufügen.
from django import formsfrom django.shortcuts import render, redirectfrom .models import Productclass ProductForm(forms.ModelForm): class Meta: model = Product fields = ['name', 'description', 'price', 'stock']def add_product(request): if request.method == 'POST': form = ProductForm(request.POST) if form.is_valid(): form.save() return redirect('product_list') else: form = ProductForm() return render(request, 'add_product.html', {'form': form})def product_list(request): products = Product.objects.all() return render(request, 'product_list.html', {'products': products})
Controller: Füge die URL-Muster für die neuen Ansichten hinzu.
from django.urls import pathfrom . import viewsurlpatterns = [ path('add/', views.add_product, name='add_product'), path('', views.product_list, name='product_list'),]
Template: Erstelle die HTML-Templates für die Formulareingabe und die Anzeige der Produktliste.
add_product.html:
<h1>Neues Produkt hinzufügen</h1><form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Speichern</button></form>
product_list.html:
<h1>Produktliste</h1><ul> {% for product in products %} <li>{{ product.name }} - {{ product.price }}</li> {% endfor %}</ul><a href="{% url 'add_product' %}">Neues Produkt hinzufügen</a>
Durch diese Implementierungsschritte wird eine vollständige Lösung zum Hinzufügen eines neuen Produkts in die Datenbank innerhalb einer Django-basierten Webanwendung bereitgestellt.
c)
Diskutiere die Vorteile der Verwendung des MVC-Musters in der Anwendung. Gehe dabei insbesondere darauf ein, wie die Trennung der Verantwortlichkeiten die Wartbarkeit und Erweiterbarkeit des Systems beeinflusst. Führe auch aus, wie die Testbarkeit im Vergleich zu einer monolithischen Architektur verbessert wird.
Lösung:
Vorteile der Verwendung des MVC-Musters in der Anwendung:
- Trennung der Verantwortlichkeiten: Das MVC-Muster teilt die Anwendung in drei getrennte Komponenten auf:
- Model (Daten und Geschäftslogik): Diese Schicht kümmert sich um die Daten und die zugrunde liegende Geschäftslogik. Sie ist für die Interaktion mit der Datenbank verantwortlich.
- View (Präsentationslogik): Diese Schicht ist für die Darstellung der Daten und die Benutzeroberfläche verantwortlich. Sie zeigt die Daten an, die vom Model bereitgestellt werden.
- Controller (Benutzereingaben und Steuerungslogik): Diese Schicht nimmt die Benutzereingaben entgegen, verarbeitet sie und ruft entsprechende Methoden im Model auf. Anschließend wählt der Controller die passende View zur Darstellung der Ergebnisse.
- Vorteile der Trennung der Verantwortlichkeiten:
- Wartbarkeit: Da jede Komponente klar definierte Aufgaben hat, kann der Code leichter verwaltet und gewartet werden. Änderungen in einer Schicht (z. B. das Aktualisieren der Geschäftslogik im Model) haben minimale Auswirkungen auf die anderen Schichten.
- Erweiterbarkeit: Neue Funktionen und Module können einfacher hinzugefügt werden, da Änderungen isoliert in einer der drei Schichten vorgenommen werden können. Zum Beispiel kann eine neue Benutzeroberfläche erstellt werden, ohne die Geschäftslogik zu ändern.
- Verbesserte Testbarkeit:
- Unit-Tests: Jede Schicht kann separat getestet werden. Models können unabhängig von der Benutzeroberfläche getestet werden, um sicherzustellen, dass die Geschäftslogik korrekt funktioniert. Views können isoliert getestet werden, um sicherzustellen, dass sie die Daten korrekt darstellen.
- Integrationstests: Da die Komponenten getrennt sind, lassen sich Integrationstests leichter durchführen, um sicherzustellen, dass die verschiedenen Schichten korrekt zusammenarbeiten.
- Vergleich zur monolithischen Architektur:
- Monolithische Architektur: In einer monolithischen Architektur sind Geschäftslogik, Präsentationslogik und Eingabelogik oft stark miteinander verflochten. Dies kann die Wartung und Erweiterung erschweren, da Änderungen an einer Stelle unvorhergesehene Auswirkungen auf andere Teile der Anwendung haben können.
- MVC-Architektur: Die klare Trennung der Schichten in der MVC-Architektur reduziert die Abhängigkeiten zwischen den Komponenten. Dies führt zu einem modulareren, leichter wartbaren und erweiterbaren System. Darüber hinaus ermöglicht die Trennung der Schichten die parallele Entwicklung durch verschiedene Entwicklerteams, was die Entwicklungszeit verkürzen kann.
- Zusammenfassung:
- Die Verwendung des MVC-Musters in einer Webanwendung zur Verwaltung von Produkten in einem Online-Shop bietet klare Vorteile in Bezug auf Wartbarkeit, Erweiterbarkeit und Testbarkeit. Die Trennung der Verantwortlichkeiten erleichtert es, Änderungen vorzunehmen und neue Funktionen hinzuzufügen, ohne die gesamte Anwendung zu beeinträchtigen. Die Testbarkeit wird durch die Möglichkeit verbessert, jede Schicht separat zu testen, was zu einer insgesamt stabileren und zuverlässigeren Anwendung führt.
Aufgabe 3)
Design Patterns: Repository und FactoryIn diesem Kontext werden die Design Patterns Repository und Factory analysiert. Das Repository-Muster dient als Schnittstelle für gespeicherte Daten und fördert die Testbarkeit, indem es den Zugriff auf den Datenspeicher abstrahiert. Dies trennt die Geschäftslogik vom Datenzugriff.Das Factory-Muster hingegen ist ein Erzeugungsmuster, das die Objekterstellung ohne direkte Nutzung des Konstruktors ermöglicht. Es fördert die Entkopplung und die Flexibilität in der Objekterstellung.
a)
Erkläre anhand eines konkreten Beispiels, wie Du die Repository- und die Factory-Design-Patterns in einer Anwendung für die Verwaltung einer Bücherbibliothek implementieren würdest. Gehe dabei auf die folgenden Aspekte ein:
- Die Struktur der Klassen, die das Repository darstellen (einschließlich der Schnittstelle und der Implementierung)
- Die Implementierung der Factory zum Erstellen von Buchobjekten. Verwende dabei Pseudocode oder eine Programmiersprache Deiner Wahl.
Lösung:
Beispiel zur Implementierung der Repository- und Factory-Design-Pattern für die Verwaltung einer Bücherbibliothek
- Struktur der Klassen, die das Repository darstellen
Schnittstelle- Zuerst benötigen wir eine Schnittstelle, die die grundlegenden Methoden für den Zugriff auf die Bücher definiert:
interface BookRepository { void addBook(Book book); Book getBookById(int id); List getAllBooks(); void updateBook(Book book); void deleteBook(int id);}
- Dadurch wird sichergestellt, dass alle Implementierungen des Repositories diese Methoden bereitstellen.
Implementierung- Nun wird eine konkrete Implementierung dieser Schnittstelle erstellt. Diese Klasse wird beispielsweise eine in-memory Speicherung nutzen:
import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;public class InMemoryBookRepository implements BookRepository { private Map bookStorage = new HashMap<>(); private int currentId = 1; @Override public void addBook(Book book) { book.setId(currentId++); bookStorage.put(book.getId(), book); } @Override public Book getBookById(int id) { return bookStorage.get(id); } @Override public List getAllBooks() { return new ArrayList<>(bookStorage.values()); } @Override public void updateBook(Book book) { bookStorage.put(book.getId(), book); } @Override public void deleteBook(int id) { bookStorage.remove(id); }}
- Diese Implementierung speichert die Bücher in einer HashMap und verwendet eine laufende ID, um jedes Buch eindeutig zu identifizieren.
- Implementierung der Factory zum Erstellen von Buchobjekten
- Die Factory-Methode wird zum Erstellen von Buchobjekten verwendet. Die Buchklasse ist einfach gehalten:
public class Book { private int id; private String title; private String author; // Getter und Setter public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; }}
- Die Factory-Klasse zur Erstellung von Buchobjekten könnte beispielsweise wie folgt aussehen:
public class BookFactory { public static Book createBook(String title, String author) { Book book = new Book(); book.setTitle(title); book.setAuthor(author); return book; }}
- Diese Factory-Methode vereinfacht die Erstellung von Buchobjekten, sodass der Konstruktor der Buchklasse nicht direkt aufgerufen werden muss.
- Mit dem Repository-Pattern trennen wir die Geschäftslogik vom Datenzugriff durch die Implementierung einer Schnittstelle und deren konkreter Implementierung.
- Mit dem Factory-Pattern ermöglichen wir eine flexible und entkoppelte Erstellung von Buchobjekten.
- Dies verbessert sowohl die Modularität als auch die Testbarkeit der Anwendung.
b)
Nehmen wir an, die Performance und Skalierbarkeit der Anwendung muss verbessert werden. Analysiere, wie das Repository- und das Factory-Muster dabei helfen können, diese Verbesserungen zu erreichen. Beziehe Dich auf die Konzepte der Abstraktion, Entkopplung und Flexibilität in Deiner Erklärung.
- Beispielhafte Situation: Mehrere Datenspeicher (Relationale Datenbank, NoSQL-Datenbank)
- Wie kann das Repository-Muster dafür genutzt werden, die Anwendung modular und erweiterbar zu halten?
- Wie hilft das Factory-Muster dabei, die Effizienz in der Objekterstellung zu steigern?
Lösung:
Verbesserung der Performance und Skalierbarkeit durch Repository- und Factory-MusterDie Design Patterns Repository und Factory spielen eine wesentliche Rolle bei der Erhöhung der Performance und Skalierbarkeit einer Anwendung. Hier ist eine detaillierte Analyse, wie diese Muster helfen:
- Beispielhafte Situation: Mehrere Datenspeicher (Relationale Datenbank, NoSQL-Datenbank)
- Wie kann das Repository-Muster dafür genutzt werden, die Anwendung modular und erweiterbar zu halten?
Das Repository-Muster kann erheblich zur Modularität und Erweiterbarkeit der Anwendung beitragen, insbesondere in einer Situation mit mehreren Datenspeichern:
- Abstraktion: Das Repository-Muster bietet eine Abstraktionsschicht zwischen der Geschäftslogik und den Datenspeichern. Diese Abstraktion ermöglicht es, die zugrundeliegende Speicherung in der Geschäftslogik zu verbergen, wodurch der Wechsel oder die gleichzeitige Nutzung von mehreren Datenspeichern erleichtert wird.
- Entkopplung: Durch die Definition einer Schnittstelle für das Repository können verschiedene Implementierungen erstellt werden, die unterschiedliche Datenspeicher verwenden. Hier sind zum Beispiel eine Implementierung für relationale Datenbanken und eine für NoSQL-Datenbanken:
interface BookRepository { void addBook(Book book); Book getBookById(int id); List getAllBooks(); void updateBook(Book book); void deleteBook(int id);}
public class RelationalBookRepository implements BookRepository { // Implementierung der Methoden für relationale DB @Override public void addBook(Book book) { // SQL-Logik } @Override public Book getBookById(int id) { // SQL-Logik return null; } @Override public List getAllBooks() { // SQL-Logik return null; } @Override public void updateBook(Book book) { // SQL-Logik } @Override public void deleteBook(int id) { // SQL-Logik }}
public class NoSQLBookRepository implements BookRepository { // Implementierung der Methoden für NoSQL DB @Override public void addBook(Book book) { // NoSQL-Logik } @Override public Book getBookById(int id) { // NoSQL-Logik return null; } @Override public List getAllBooks() { // NoSQL-Logik return null; } @Override public void updateBook(Book book) { // NoSQL-Logik } @Override public void deleteBook(int id) { // NoSQL-Logik }}
- Flexibilität: Durch die Entkopplung der Geschäftslogik vom spezifischen Datenspeicher kann die Anwendung flexibel auf unterschiedliche Anforderungen reagieren. Es ist möglich, zur Laufzeit zwischen verschiedenen Repositories zu wechseln oder beide parallel zu verwenden, um eine höhere Verfügbarkeit zu gewährleisten.
- Wie hilft das Factory-Muster dabei, die Effizienz in der Objekterstellung zu steigern?
Das Factory-Muster kann dabei helfen, die Effizienz in der Objekterstellung zu steigern, und zwar auf mehrere Arten:- Abstraktion: Das Factory-Muster abstrahiert die Komplexität der Objekterstellung. Dies bedeutet, dass Änderungen an der Art und Weise, wie Objekte erstellt werden, zentral in der Factory vorgenommen werden können und nicht an mehreren Stellen im Code.
- Entkopplung: Die Geschäftslogik muss sich nicht um die genauen Details der Objekterstellung kümmern. Dies fördert eine saubere Trennung der Verantwortlichkeiten und verringert die Abhängigkeiten im Code.
public class BookFactory { public static Book createBook(String title, String author) { Book book = new Book(); book.setTitle(title); book.setAuthor(author); return book; }}
Flexibilität: Das Factory-Muster ermöglicht es, die Objekterstellung zu optimieren, indem beispielsweise Caches oder Pooling-Mechanismen integriert werden. Dadurch können häufig benötigte Objekte wiederverwendet werden, anstatt sie jedes Mal neu zu erstellen. Dies spart Rechenressourcen und erhöht die Geschwindigkeit der Anwendung.Durch die Anwendung des Repository- und Factory-Musters können die folgenden Verbesserungen erreicht werden:
- Erhöhung der Modularität und Erweiterbarkeit der Anwendung
- Flexibler Einsatz mehrerer Datenspeicher durch klare Abstraktions- und Entkopplungsschichten
- Effiziente Objekterstellung durch den Einsatz von Factories, wodurch Ressourcen gespart und die Performance verbessert werden kann
Aufgabe 4)
Architekturstil für die Entwicklung von Softwareanwendungen als Sammlung kleiner, unabhängiger, modularer Dienste, die jeweils für eine spezifische Geschäftsaufgabe verantwortlich sind.
- Entkopplung: Jeder Microservice ist eigenständig und kommuniziert über APIs.
- Skalierbarkeit: Microservices können unabhängig voneinander skaliert werden.
- Technologieunabhängigkeit: Verschiedene Technologien/Programmiersprachen je Microservice erlaubt.
- Continuous Deployment: Ermöglicht häufige und unabhängige Releases.
- Fault Isolation: Fehler in einem Service beeinträchtigen nicht das gesamte System.
a)
Erkläre, wie die Entkopplung von Microservices zur Erhöhung der Skalierbarkeit und Verfügbarkeit eines Systems beiträgt. Illustriere Deine Antwort mit einem Beispiel aus der Praxis, bei dem ein hoch frequentierter Microservice skaliert werden muss, ohne dass dies andere Teile des Systems beeinträchtigt.
Lösung:
Entkopplung von Microservices:
- Erhöhung der Skalierbarkeit: Jeder Microservice ist unabhängig und modular, was bedeutet, dass sie unabhängig voneinander skaliert werden können. Wenn ein bestimmter Dienst mehr Ressourcen benötigt, weil er stark genutzt wird, kann dieser Dienst vergrößert werden, ohne dass die anderen Dienste beeinflusst werden. Dies ermöglicht eine effizientere Nutzung der Ressourcen und reduziert die Kosten, da nur die Komponenten skaliert werden müssen, die die zusätzliche Kapazität benötigen.
- Erhöhung der Verfügbarkeit: Da jeder Microservice isoliert läuft, führt ein Fehler in einem einzelnen Dienst nicht zwangsläufig zum Ausfall des gesamten Systems. Dies verbessert die Gesamtverfügbarkeit des Systems, da unabhängige Dienste weiterhin funktionieren können, auch wenn ein Dienst vorübergehend außer Betrieb ist oder gewartet wird.
Beispiel aus der Praxis:Stell Dir ein E-Commerce-Unternehmen vor, das seine Anwendung als Sammlung von Microservices organisiert hat. Ein besonders wichtiger Microservice ist der „Warenkorb-Service“, auf den viele Kunden gleichzeitig zugreifen, insbesondere während Verkaufsaktionen oder Feiertagen.
- Herausforderung: Während eines Flash-Sales steigen die Anforderungen an den „Warenkorb-Service“ massiv an. Dies erfordert eine schnelle Skalierung des Dienstes, um die hohe Last zu bewältigen und um sicherzustellen, dass Kunden ihre Einkäufe ohne Verzögerung abschließen können.
- Skalierung: Da der „Warenkorb-Service“ entkoppelt von anderen Diensten wie dem „Katalog-Service“ oder „Bezahlungs-Service“ ist, kann der „Warenkorb-Service“ unabhängig skaliert werden. Das Unternehmen könnte zusätzliche Instanzen des „Warenkorb-Service“ hinzufügen oder die vorhandenen Instanzen mit mehr Ressourcen versehen.
- Resultat: Diese unabhängige Skalierung stellt sicher, dass die erhöhte Last im „Warenkorb-Service“ abgefangen wird, ohne die Leistung oder Verfügbarkeit der anderen Dienste zu beeinträchtigen. Dadurch bleibt das gesamte System stabil und funktionsfähig, selbst unter hoher Last.
b)
Implementiere einen einfachen Microservice in einer Programmiersprache Deiner Wahl, der Daten über eine RESTful API bereitstellt. Der Microservice soll eine Liste von Produkten verwalten, wobei jedes Produkt eine eindeutige ID, einen Namen und einen Preis hat. Der Microservice sollte grundlegende CRUD-Operationen (Create, Read, Update, Delete) unterstützen. Stell sicher, dass der Code klar strukturiert und gut dokumentiert ist. Beispiel einer Sprache könnte Python mit dem Flask Framework sein:
'from flask import Flask, request, jsonifyapp = Flask(__name__)products = []@app.route('/products', methods=['POST'])def create_product(): product = { 'id': request.json['id'], 'name': request.json['name'], 'price': request.json['price'] } products.append(product) return jsonify(product), 201@app.route('/products', methods=['GET'])def get_products(): return jsonify(products)@app.route('/products/', methods=['PUT'])def update_product(id): product = next((p for p in products if p['id'] == id), None) if product is None: return jsonify({ 'error': 'Product not found' }), 404 product['name'] = request.json['name'] product['price'] = request.json['price'] return jsonify(product)@app.route('/products/', methods=['DELETE'])def delete_product(id): global products products = [p for p in products if p['id'] != id] return '', 204if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)'
Lösung:
Implementierung eines einfachen Microservice mit Flask:Hier ist eine Beispielimplementierung eines Microservice mit dem Flask-Framework in Python. Dieser Service verwaltet eine Liste von Produkten und unterstützt grundlegende CRUD-Operationen (Create, Read, Update, Delete).
from flask import Flask, request, jsonifyapp = Flask(__name__)# Initiale Liste der Produkteproducts = []# Endpunkt zum Erstellen eines neuen Produkts@app.route('/products', methods=['POST'])def create_product(): product = { 'id': request.json['id'], 'name': request.json['name'], 'price': request.json['price'] } products.append(product) return jsonify(product), 201# Endpunkt zum Abrufen aller Produkte@app.route('/products', methods=['GET'])def get_products(): return jsonify(products)# Endpunkt zum Aktualisieren eines Produkts nach ID@app.route('/products/', methods=['PUT'])def update_product(id): product = next((p for p in products if p['id'] == id), None) if product is None: return jsonify({'error': 'Product not found'}), 404 product['name'] = request.json['name'] product['price'] = request.json['price'] return jsonify(product)# Endpunkt zum Löschen eines Produkts nach ID@app.route('/products/', methods=['DELETE'])def delete_product(id): global products products = [p for p in products if p['id'] != id] return '', 204if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)
Beschreibung der Endpunkte:- POST /products: Erstellt ein neues Produkt. Erwartet ein JSON-Objekt mit 'id', 'name' und 'price'.
- GET /products: Gibt die Liste aller Produkte zurück.
- PUT /products/<id>: Aktualisiert das Produkt mit der angegebenen ID. Erwartet ein JSON-Objekt mit 'name' und 'price'.
- DELETE /products/<id>: Löscht das Produkt mit der angegebenen ID.