Home

06 Softwarearchitektur Teil 2: Architekturmuster und Anwendung

lecture architecture design-patterns MVC layered-architecture microservices distributed-systems

1. Einführung: Von Verständnis zur Anwendung

In Teil 1 haben wir erkundet:

Jetzt bewegen wir uns vom Verstehen von Architektur zur Anwendung. Diese Vorlesung führt Architekturmuster ein — bewährte Lösungen, die Tausende von Systemen validiert haben — und zeigt, wie man sie auf Ihr Road Profile Viewer-Projekt anwendet.

1.1 Das Versprechen der Muster

Wenn ein Entwickler sagt “wir verwenden MVC”, verstehen andere Entwickler sofort:

Muster sind nicht nur Code-Strukturen — sie sind gemeinsames Vokabular, das effiziente Kommunikation und vorhersagbares Systemverhalten ermöglicht.


2. Lernziele

Am Ende dieser Vorlesung werden Sie in der Lage sein:

  1. Zu erklären, was Architekturmuster sind und warum sie als bewährte Lösungen für wiederkehrende Probleme wertvoll sind
  2. MVC und Schichtenarchitektur-Muster anzuwenden, um interaktive Anwendungen mit angemessener Trennung der Verantwortlichkeiten zu strukturieren
  3. Zu bewerten, wann verteilte Muster wie Client-Server und Microservices basierend auf Projektanforderungen eingesetzt werden sollten
  4. Eine weiterentwickelte Architektur für den Road Profile Viewer zu entwerfen, die Einfachheit mit zukünftiger Skalierbarkeit ausbalanciert

3. Architekturmuster: Bewährte Lösungen für wiederkehrende Probleme

3.1 Was ist ein Architekturmuster?

Ein Architekturmuster ist eine Beschreibung einer Systemorganisation, die erfolgreich in verschiedenen Softwaresystemen verwendet wurde.

Muster sind keine Erfindungen — sie sind Entdeckungen. Architekten bemerkten, dass bestimmte Strukturen in erfolgreichen Systemen immer wieder auftauchen und dokumentierten sie, damit andere davon profitieren können.

Warum Muster verwenden?

  1. Bewährt: Tausende von Systemen haben diese Ansätze validiert
  2. Kommunizierbar: “Wir verwenden MVC” vermittelt anderen Entwicklern sofort die Struktur
  3. Analysierbar: Bekannte Muster haben bekannte Kompromisse
  4. Wiederverwendbar: Framework-Unterstützung, Dokumentation und Community-Wissen

Musterkategorien:

Kategorie Zweck Beispielmuster
Strukturierung interaktiver Systeme Organisation von Mensch-Computer-Interaktionen MVC, MVP, MVVM
Organisation der Systemstruktur Organisation von Komponenten und Objekten Schichten, Pipes und Filter, Repository
Unterstützung verteilter Systeme Ermöglichung verteilter Ressourcen und Dienste Client-Server, Microservices, Event-Driven

3.2 Kategorie 1: Strukturierung interaktiver Systeme

3.2.1 Model-View-Controller (MVC)

MVC trennt eine Anwendung in drei miteinander verbundene Komponenten:

interagiert Benutzereingabe aktualisiert benachrichtigt
👤 Benutzer
🖼️ View UI-Anzeige
🎮 Controller Eingabeverarbeitung
💾 Model Daten & Logik
Komponente Verantwortlichkeit Road Profile Viewer Beispiel
Model Daten und Geschäftslogik; weiß nichts über UI RoadProfile-Klasse, GeometryCalculator
View Zeigt Daten dem Benutzer an; empfängt Benutzereingaben Dash-Layout, Plotly-Diagramme
Controller Verarbeitet Benutzereingaben; aktualisiert Model und View Dash-Callbacks

Warum MVC?

3.2.2 MVC in Web-Frameworks

Die meisten Web-Frameworks implementieren MVC (oder Varianten wie MVP, MVVM):

Django (Python):

Terminologie-Hinweis: Django verwendet MTV (Model-Template-View) anstelle von MVC. Das sorgt für Verwirrung, da Djangos “View” eigentlich der Controller in MVC-Terminologie ist:

MVC-Begriff Django-Begriff Django-Datei
Model Model models.py
View (Präsentation) Template templates/*.html
Controller (Logik) View views.py
# models.py — Model (gleich in MVC und MTV)
class RoadProfile(models.Model):
    name = models.CharField(max_length=100)
    data = models.JSONField()

# views.py — Django nennt das "View", aber es fungiert als MVC Controller
# Es verarbeitet HTTP-Anfragen und koordiniert Model ↔ Template
def profile_detail(request, profile_id):
    profile = RoadProfile.objects.get(id=profile_id)
    return render(request, 'profile.html', {'profile': profile})

# templates/profile.html — Django "Template" = MVC View (Präsentation)
<h1></h1>
<div id="chart"></div>

Flask (Python):

# models.py
@dataclass
class RoadProfile:
    id: str
    name: str
    coordinates: list[tuple[float, float]]

# routes.py (Controller)
@app.route('/profiles/<profile_id>')
def show_profile(profile_id):
    profile = ProfileRepository.get(profile_id)
    return render_template('profile.html', profile=profile)

# templates/profile.html (View)
# ... Jinja2-Template

3.2.3 Road Profile Viewer: MVC anwenden

Lassen Sie uns den Road Profile Viewer mit MVC refaktorieren:

Model (models/profile.py):

from pydantic import BaseModel

class RoadProfile(BaseModel):
    """Domain-Modell - weiß nichts über UI oder Speicherung."""
    id: str
    name: str
    x_coordinates: list[float]
    y_coordinates: list[float]

    def get_elevation_at(self, x: float) -> float:
        """Geschäftslogik lebt im Model."""
        # Interpoliere Höhe an gegebener x-Koordinate
        ...

    def max_slope(self) -> float:
        """Berechne maximale Steigung - reine Domain-Logik."""
        ...

View (presentation/charts.py):

import plotly.graph_objects as go
from models.profile import RoadProfile

def create_profile_figure(profile: RoadProfile) -> go.Figure:
    """Reine Visualisierung - keine Geschäftslogik."""
    return go.Figure(
        data=go.Scatter(
            x=profile.x_coordinates,
            y=profile.y_coordinates,
            mode='lines',
            name=profile.name
        ),
        layout=go.Layout(
            title=f"Road Profile: {profile.name}",
            xaxis_title="Distance (m)",
            yaxis_title="Elevation (m)"
        )
    )

Controller (presentation/callbacks.py):

from dash import callback, Input, Output
from repositories.profile_repository import ProfileRepository
from presentation.charts import create_profile_figure

repository = ProfileRepository()

@callback(
    Output('profile-chart', 'figure'),
    Input('profile-dropdown', 'value')
)
def update_chart(profile_id: str):
    """Controller: koordiniert Model und View."""
    if not profile_id:
        return go.Figure()  # Leere Figur

    # Hole Daten vom Model
    profile = repository.get(profile_id)

    # Erstelle View-Repräsentation
    return create_profile_figure(profile)

Erreichte Vorteile:

3.3 Kategorie 2: Organisation der Systemstruktur

3.3.1 Schichtenarchitektur

Schichtenarchitektur organisiert Code in horizontale Schichten, wobei jede Schicht nur von der darunterliegenden Schicht abhängt.

🖥️ Präsentationsschicht Benutzeroberfläche
hängt ab von
⚙️ Geschäftslogikschicht Domain-Logik
hängt ab von
📁 Datenzugriffsschicht Datenbankoperationen
verbindet mit
🗄️ Datenbank Persistente Speicherung

Schlüsselregeln:

  1. Nur Abhängigkeiten nach unten: Präsentation hängt von Geschäftslogik ab; Geschäftslogik hängt von Daten ab
  2. Keine Schichten überspringen: Präsentation sollte Daten nicht direkt aufrufen
  3. Schichten sind kohäsiv: Aller UI-Code in Präsentation, alle Domain-Logik in Geschäftslogik
Schicht Verantwortlichkeit Typische Komponenten
Präsentation Benutzerinteraktion verarbeiten, Daten anzeigen Webseiten, API-Endpunkte, CLI-Befehle
Geschäftslogik Domain-Logik, Validierung, Berechnungen Services, Domain-Modelle, Validatoren
Datenzugriff Daten persistieren und abrufen Repositories, ORM-Modelle, Cache-Clients

3.3.2 Netflix: Ein Beispiel für Schichtenarchitektur

Die Architektur von Netflix demonstriert Schichtenprinzipien im großen Maßstab:

🎬
Präsentationsschicht
Web UI
React SPA
Mobile Apps
iOS & Android
TV Apps
Smart TVs, Roku
API Gateway
Zuul
API-Aufrufe
⚙️
Geschäftslogikschicht (Microservices)
User Service
Auth & Profile
Content Service
Filme & Serien
Recommendation
ML-Vorschläge
Playback Service
Streaming-Logik
Datenabfragen
💾
Datenschicht
Cassandra
Benutzerdaten & Verlauf
MySQL
Abrechnung & Konten
ElasticSearch
Suchindizierung
S3
Videodateien

Weiterführende Lektüre: Der Netflix Technology Blog bietet detaillierte Einblicke in ihre Architektur. Besonders relevant:

Netflix veröffentlicht auch viele ihrer Architektur-Tools als Open Source unter netflix.github.io, einschließlich Zuul (API Gateway), Eureka (Service Discovery) und Hystrix (Fehlertoleranz).

Jede Schicht kann unabhängig skaliert werden:

3.3.3 Road Profile Viewer: Schichten anwenden

Aktuelle Struktur (vermischte Verantwortlichkeiten):

# Alles in einer Datei - schwer zu warten
app = Dash(__name__)

@app.callback(...)
def update_chart(profile_id):
    # UI-Logik
    if not profile_id:
        return {}

    # Datenzugriff
    conn = sqlite3.connect('profiles.db')
    cursor = conn.execute('SELECT * FROM profiles WHERE id = ?', (profile_id,))
    row = cursor.fetchone()

    # Geschäftslogik
    x_coords = json.loads(row[1])
    y_coords = json.loads(row[2])
    max_slope = calculate_max_slope(x_coords, y_coords)

    # Zurück zur UI
    return create_figure(x_coords, y_coords, max_slope)

Refaktoriert mit Schichten:

# data_access/repositories.py (Datenschicht)
class ProfileRepository:
    def __init__(self, db_path: str = 'profiles.db'):
        self.db_path = db_path

    def get(self, profile_id: str) -> Profile | None:
        conn = sqlite3.connect(self.db_path)
        cursor = conn.execute(
            'SELECT * FROM profiles WHERE id = ?',
            (profile_id,)
        )
        row = cursor.fetchone()
        if row:
            return Profile.from_row(row)
        return None

# business/services.py (Geschäftslogikschicht)
class ProfileService:
    def __init__(self, repository: ProfileRepository):
        self.repository = repository

    def get_profile_with_analysis(self, profile_id: str) -> dict:
        profile = self.repository.get(profile_id)
        if not profile:
            raise ProfileNotFoundError(profile_id)

        return {
            'profile': profile,
            'max_slope': self._calculate_max_slope(profile),
            'total_distance': self._calculate_distance(profile),
        }

    def _calculate_max_slope(self, profile: Profile) -> float:
        # Geschäftslogik hier
        ...

# presentation/callbacks.py (Präsentationsschicht)
service = ProfileService(ProfileRepository())

@app.callback(...)
def update_chart(profile_id):
    if not profile_id:
        return empty_figure()

    try:
        data = service.get_profile_with_analysis(profile_id)
        return create_figure(data)
    except ProfileNotFoundError:
        return error_figure("Profile not found")

Vorteile:

3.4 Kategorie 3: Verteilte Systeme

3.4.1 Client-Server-Architektur

Das grundlegendste verteilte Muster: Clients fordern Dienste von Servern an.

graph LR
    C1[Client 1<br>Web Browser] --> S[Server<br>Web Application]
    C2[Client 2<br>Mobile App] --> S
    C3[Client 3<br>Desktop App] --> S
    S --> DB[(Database)]

Eigenschaften:

Road Profile Viewer ist bereits Client-Server:

3.4.2 Microservices-Architektur

Microservices zerlegen ein System in kleine, unabhängig deploybare Dienste.

graph TB
    API[API Gateway]

    API --> US[User Service]
    API --> PS[Profile Service]
    API --> AS[Analysis Service]

    US --> UDB[(User DB)]
    PS --> PDB[(Profile DB)]
    AS --> ADB[(Analysis Cache)]

vs. Monolith:

Aspekt Monolith Microservices
Deployment Alles oder nichts; gesamte App wird redeployed Unabhängig; ein Service nach dem anderen deployen
Skalierung Alles zusammen skalieren Einzelne Services basierend auf Bedarf skalieren
Technologie Eine Sprache/Framework für alles Jeder Service kann das beste Werkzeug für den Job verwenden
Ausfall Ein Bug kann das gesamte System zum Absturz bringen Ausfälle sind auf einen Service isoliert
Komplexität Einfaches Deployment, komplexe Codebasis Komplexes Deployment, einfachere Codebasen

Amazons Transformation:

Im Jahr 2002 verordnete Jeff Bezos, dass alle Teams ihre Funktionalität über Service-Schnittstellen bereitstellen müssen. Diese “Zwei-Pizza-Team”-Regel (Teams, die klein genug sind, um mit zwei Pizzen gefüttert zu werden) führte zu Amazon Web Services — ursprünglich interne Infrastruktur, jetzt ein über 80 Milliarden Dollar schweres Geschäft.

Wann Microservices verwenden:

Wann KEINE Microservices verwenden:

Road Profile Viewer: Ein Monolith ist die richtige Wahl. Sie haben 3 Entwickler und eine einfache Domäne. Microservices würden erhebliche Komplexität ohne Nutzen hinzufügen.

3.4.3 AWS und Cloud-Architektur

Cloud-Plattformen wie AWS bieten Bausteine für verteilte Architekturen:

👥 Benutzer (Browser, Apps)
Webbrowser, Mobile Apps, API-Clients
🌐 CloudFront (CDN) AWS
Cached statische Inhalte an Edge-Standorten weltweit
⚖️ Application Load Balancer AWS
Leitet Anfragen an gesunde Instanzen weiter
📦 ECS/Fargate
Container 1
📦 ECS/Fargate
Container 2
🗄️ RDS (PostgreSQL) AWS
Verwaltete Datenbank mit automatischen Backups

Weiterführende Lektüre: AWS bietet umfangreiche Dokumentation und Architekturanleitungen:

Für Studierende: AWS bietet AWS Educate mit kostenlosen Credits und Lernressourcen.

Für Road Profile Viewer Deployment (Zukunft):

Ein einfaches Cloud-Deployment könnte verwenden:

Sie brauchen keine AWS-Komplexität für ein Studentenprojekt!


4. Architektur auf den Road Profile Viewer anwenden

4.1 Aktueller Zustand: Vom Monolithen zu Modulen

In Vorlesung 4: Refactoring haben wir eine monolithische main.py in Module aufgeteilt:

road-profile-viewer/
├── src/
│   ├── geometry.py       # Ray-Intersection-Berechnungen
│   ├── road.py           # Profil-Generierung
│   ├── visualization.py  # Diagramm-Erstellung
│   └── main.py           # Anwendungs-Einstiegspunkt
└── tests/

Das war ein guter Anfang! Aber wir können mit einer ordentlichen Schichtenarchitektur weitergehen.

4.2 Vorgeschlagene weiterentwickelte Architektur

road-profile-viewer/
├── src/
│   ├── __init__.py
│   │
│   ├── domain/                    # Kern-Geschäftskonzepte
│   │   ├── __init__.py
│   │   ├── models.py              # RoadProfile, Measurement
│   │   └── services.py            # GeometryCalculator, ProfileAnalyzer
│   │
│   ├── infrastructure/            # Externe Systeme
│   │   ├── __init__.py
│   │   ├── database.py            # Datenbankverbindung
│   │   └── repositories.py        # ProfileRepository
│   │
│   ├── presentation/              # Benutzeroberfläche
│   │   ├── __init__.py
│   │   ├── app.py                 # Dash-Anwendungs-Setup
│   │   ├── layouts.py             # Seitenlayouts
│   │   ├── callbacks.py           # Dash-Callbacks (Controller)
│   │   └── charts.py              # Diagramm-Builder (Views)
│   │
│   ├── api/                       # Optional: REST API
│   │   ├── __init__.py
│   │   ├── routes.py              # FastAPI-Endpunkte
│   │   └── schemas.py             # API-Request/Response-Modelle
│   │
│   └── main.py                    # Einstiegspunkt
│
├── tests/
│   ├── domain/
│   │   ├── test_models.py
│   │   └── test_services.py
│   ├── infrastructure/
│   │   └── test_repositories.py
│   └── presentation/
│       └── test_callbacks.py
│
├── pyproject.toml
└── README.md

4.3 Hinzufügen einer REST-API-Schicht

Warum eine API hinzufügen, wenn Dash bereits funktioniert?

Einfache FastAPI-Ergänzung (api/routes.py):

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

from domain.models import RoadProfile
from infrastructure.repositories import ProfileRepository

app = FastAPI(title="Road Profile Viewer API")
repository = ProfileRepository()

class ProfileResponse(BaseModel):
    id: str
    name: str
    x_coordinates: list[float]
    y_coordinates: list[float]
    max_slope: float | None = None

@app.get("/api/profiles")
def list_profiles() -> list[ProfileResponse]:
    """Liste alle verfügbaren Profile auf."""
    profiles = repository.list_all()
    return [ProfileResponse(**p.dict()) for p in profiles]

@app.get("/api/profiles/{profile_id}")
def get_profile(profile_id: str) -> ProfileResponse:
    """Hole ein spezifisches Profil nach ID."""
    profile = repository.get(profile_id)
    if not profile:
        raise HTTPException(status_code=404, detail="Profile not found")
    return ProfileResponse(**profile.dict())

@app.post("/api/profiles")
def create_profile(profile: RoadProfile) -> ProfileResponse:
    """Erstelle ein neues Profil."""
    saved = repository.save(profile)
    return ProfileResponse(**saved.dict())

Jetzt können sowohl Dash als auch die API dieselben ProfileRepository- und RoadProfile-Modelle verwenden.

4.4 Zukünftige Skalierbarkeit berücksichtigen

Falls Sie den Road Profile Viewer jemals skalieren müssen:

  1. Datenbank: SQLite durch PostgreSQL ersetzen (nur infrastructure/database.py ändern)
  2. Caching: Redis-Cache in infrastructure/cache.py hinzufügen (keine Domain-Änderungen)
  3. Deployment: Mit Docker containerisieren, in die Cloud deployen
  4. Lastverteilung: Mehrere Instanzen hinter einem Load Balancer hinzufügen

Die Schichtenarchitektur macht jede dieser Änderungen lokalisiert. Sie müssen nicht die gesamte Anwendung neu schreiben.

Aber denken Sie daran: Vorzeitige Optimierung ist die Wurzel allen Übels. Bauen Sie zuerst die einfache Version. Skalieren Sie, wenn Sie Beweise haben, dass Sie es brauchen.


5. Architektur und Agile: Zusammenarbeit

5.1 Der scheinbare Konflikt

Einige Entwickler glauben, Agile und Architektur stünden im Konflikt:

“Agile bedeutet kein Vorab-Design — einfach coden und refaktorieren!”

“Architektur erfordert Monate der Planung, bevor man Code schreibt!”

Beide Extreme sind falsch.

5.2 Emergente Architektur in Scrum

Der agile Ansatz zur Architektur ist emergentes Design mit beabsichtigter Struktur:

  1. Genug Entscheidungen zum Start treffen: Sprache, Framework, Grundstruktur wählen
  2. Funktionierende Software implementieren: Echte Features bauen, nicht hypothetische Infrastruktur
  3. Refaktorieren, wenn Muster entstehen: Bei Duplikation oder Komplexität umstrukturieren
  4. Entscheidungen regelmäßig überprüfen: Sprint-Retrospektiven können architektonische Schulden ansprechen

In Scrum:

5.3 Technische Spikes für Architekturentscheidungen

Ein technischer Spike ist eine zeitlich begrenzte Untersuchung, um Risiko oder Unsicherheit zu reduzieren.

Beispiel Spike-Story:

Als Entwickler möchte ich PostgreSQL vs SQLite für unsere Datenbank untersuchen damit wir eine fundierte Entscheidung über die Datenspeicherung treffen können

Akzeptanzkriterien:

  • Vor- und Nachteile jeder Option dokumentieren
  • Proof-of-Concept mit PostgreSQL erstellen
  • Performance mit realistischem Datenvolumen messen
  • Empfehlung dem Team präsentieren

Zeitrahmen: Maximal 2 Tage

Nach dem Spike kann das Team eine fundierte architektonische Entscheidung treffen, keine Vermutung.


6. Zusammenfassung

Konzept Kernpunkt Road Profile Viewer Anwendung
Architekturmuster Bewährte Lösungen für wiederkehrende Designprobleme Muster wie MVC für vorhersagbare Struktur verwenden
MVC-Muster Model (Daten), View (Anzeige), Controller (Koordination) trennen Pydantic-Modelle, Diagramm-Builder, Dash-Callbacks
Schichtenarchitektur Präsentation → Geschäftslogik → Datenzugriff presentation/, domain/, infrastructure/ Pakete
Verteilte Muster Client-Server, Microservices für Skalierung und Unabhängigkeit Bereits Client-Server; vorerst monolithisch bleiben
Agile + Architektur Emergentes Design mit beabsichtigter Struktur Technische Spikes für unsichere Entscheidungen verwenden
Weiterentwickelte Struktur domain/, infrastructure/, presentation/ Pakete Klare Trennung ermöglicht zukünftiges Wachstum

6.1 Wichtige Erkenntnisse

  1. Muster sind bewährte Lösungen — MVC, Schichtenarchitektur und Client-Server lösen wiederkehrende Probleme mit bekannten Kompromissen
  2. MVC trennt Verantwortlichkeiten — Model kennt Daten, View kennt Anzeige, Controller koordiniert
  3. Schichten schaffen Grenzen — Präsentation hängt von Geschäftslogik ab, die von Daten abhängt, niemals umgekehrt
  4. Microservices sind nicht immer die Antwort — Kleine Teams profitieren mehr von gut strukturierten Monolithen
  5. Architektur entwickelt sich mit Agile — Einfach anfangen, refaktorieren wenn Muster entstehen, Spikes für große Entscheidungen verwenden
  6. Struktur ermöglicht Veränderung — Gute Architektur macht zukünftige Änderungen lokalisiert und vorhersagbar

7. Reflexionsfragen

  1. Musteridentifikation: Betrachten Sie Ihren aktuellen Road Profile Viewer-Code, können Sie identifizieren, welchem Muster (falls überhaupt) er derzeit folgt? Was müsste sich ändern, damit er klar MVC folgt?

  2. Schichtenzuordnung: Listen Sie jede Python-Datei in Ihrem Road Profile Viewer-Projekt auf. Zu welcher Schicht (Präsentation, Geschäftslogik, Datenzugriff) gehört jede Datei? Gibt es Dateien, die mehrere Schichten vermischen?

  3. Abhängigkeitsrichtung: Zeichnen Sie ein Diagramm, das zeigt, wie Ihre Python-Module sich gegenseitig importieren. Zeigen alle Abhängigkeiten “nach unten” (Präsentation → Geschäftslogik → Daten)? Falls nicht, welche Imports verletzen die Schichtung?

  4. Framework-Passung: Sowohl Dash als auch Django implementieren MVC-ähnliche Muster. Wie passt Dashs Callback-System zur Controller-Rolle? Was ist mit Djangos View-Funktionen?

  5. Skalierungsauslöser: Welches spezifische Ereignis oder welche Metrik würde Ihnen sagen, dass es Zeit ist, den Road Profile Viewer von SQLite auf PostgreSQL umzustellen? Schreiben Sie einen konkreten Schwellenwert (z.B. “Wenn täglich aktive Benutzer X überschreiten” oder “Wenn die Datenbankdateigröße Y MB überschreitet”).

  6. Technischer Spike: Schreiben Sie eine 2-Tage-Spike-Story zur Untersuchung einer architektonischen Frage zum Road Profile Viewer (z.B. “Sollten wir eine REST API hinzufügen?” oder “Sollten wir async Callbacks verwenden?”).


8. Weiterführende Literatur

8.1 Bücher

8.2 Artikel und Ressourcen

8.3 Werkzeuge


9. Was kommt als Nächstes

Mit beiden Teilen dieser Vorlesung haben Sie jetzt ein vollständiges Verständnis von Softwarearchitektur:

Teil 1 behandelte:

Teil 2 behandelte:

Auf den Road Profile Viewer anwenden:

  1. Identifizieren Sie Ihr aktuelles Muster: Welchem Muster folgt Ihr Code derzeit (falls überhaupt)?
  2. Ordnen Sie Ihre Schichten zu: Welcher Code ist Präsentation? Geschäftslogik? Datenzugriff?
  3. Refaktorieren Sie eine Schicht: Wählen Sie den unordentlichsten Bereich und wenden Sie ordentliche Trennung an
  4. Dokumentieren Sie Ihre Architektur: Erstellen Sie eine einfache ARCHITECTURE.md mit den vier Sichten
  5. Erwägen Sie eine API: Würde eine REST API Ihrem Projekt nutzen?

Das Ziel ist nicht perfekte Architektur — es ist bewusste Architektur. Treffen Sie bewusste Entscheidungen über die Struktur, dokumentieren Sie sie und entwickeln Sie sie weiter, während Sie lernen.

© 2026 Dominik Mueller   •  Powered by Soopr   •  Theme  Moonwalk