type: article
keywords:
- 3-Schichten-Modell
- 3-Tier Architektur
- Präsentation Business Daten
- DTO
canonicalURL: https://www.irc-coding.de/3-schichten-modell-3-tier-architektur
Softwarearchitektur: 3-Schichten-Modell / 3-Tier
Dieser Beitrag ist eine Begriffserklärung zum 3-Schichten-Modell (3-Tier) – inklusive Prüfungsfragen, Merkpunkte und Tags.
In a Nutshell
Das 3-Schichten-Modell trennt Software logisch in Präsentation, Geschäftslogik und Datenzugriff – für klare Zuständigkeiten, Wartbarkeit und Testbarkeit.
Kompakte Fachbeschreibung
Die drei Schichten:
- Präsentation (UI)
- Business Logic (Services/Use Cases)
- Persistence (Repository/ORM/DB)
Kommunikation erfolgt typischerweise von oben nach unten. Jede Schicht kennt nur die direkt darunterliegende. Das macht Systeme lose gekoppelt.
Prüfungsrelevante Stichpunkte
- Trennung von Darstellung, Logik, Datenzugriff
- Klare Verantwortlichkeiten
- Bessere Testbarkeit/Wartbarkeit
- Standard in Java/.NET/Webprojekten (IHK-relevant)
- Komponenten austauschbar (UI-Wechsel)
- Schichten sind Sicherheitsbarrieren
- Modularisierung reduziert Folgekosten
- Architektur muss dokumentiert werden
Kernkomponenten
- Präsentationsschicht
- Logikschicht
- Datenzugriffsschicht
- Schnittstellen zwischen Schichten
- Logging/Fehlerbehandlung
- Unit Tests in Business-Schicht
- DTOs
- Security Layer
- Persistenz (SQL/NoSQL)
Praxisbeispiel: Task-Verwaltung mit Python
Was zeigt dieses Beispiel? Ein einfaches Task-Management-System in Python, das strikt nach dem 3-Schichten-Modell aufgebaut ist. Daten fließen von der Präsentation (CLI) durch die Business-Schicht (Validierung, Regeln) bis zur Persistence-Schicht (SQLite-Repository).
Warum ist das ein gutes Lernbeispiel? Jede Schicht ist einzeln testbar: Die Business-Schicht braucht keine Datenbank, die Präsentation braucht kein SQLite, sie kommunizieren nur über saubere Methodenaufrufe.
import sqlite3
from dataclasses import dataclass
from typing import List, Optional
# === Schicht 3: Persistence (Datenzugriff) ===
@dataclass
class TaskDTO:
id: int; title: str; description: str; status: str
class TaskRepository:
def __init__(self, db_path="tasks.db"):
self.db_path = db_path
with sqlite3.connect(db_path) as conn:
conn.execute("""
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL, description TEXT,
status TEXT DEFAULT 'open')""")
def create(self, task: TaskDTO) -> TaskDTO:
with sqlite3.connect(self.db_path) as conn:
c = conn.execute("INSERT INTO tasks (title,description,status) VALUES (?,?,?)",
(task.title, task.description, task.status))
task.id = c.lastrowid
return task
def get_all(self) -> List[TaskDTO]:
with sqlite3.connect(self.db_path) as conn:
rows = conn.execute("SELECT * FROM tasks").fetchall()
return [TaskDTO(*r) for r in rows]
def get_by_id(self, tid: int) -> Optional[TaskDTO]:
with sqlite3.connect(self.db_path) as conn:
r = conn.execute("SELECT * FROM tasks WHERE id=?", (tid,)).fetchone()
return TaskDTO(*r) if r else None
def update(self, task: TaskDTO) -> TaskDTO:
with sqlite3.connect(self.db_path) as conn:
conn.execute("UPDATE tasks SET title=?,description=?,status=? WHERE id=?",
(task.title, task.description, task.status, task.id))
return task
def delete(self, tid: int) -> bool:
with sqlite3.connect(self.db_path) as conn:
return conn.execute("DELETE FROM tasks WHERE id=?", (tid,)).rowcount > 0
# === Schicht 2: Business Logic (Anwendungslogik) ===
class TaskService:
def __init__(self, repo: TaskRepository):
self._repo = repo
def create_task(self, title: str, description="") -> TaskDTO:
if not title or len(title.strip()) == 0:
raise ValueError("Titel darf nicht leer sein")
if len(title) > 100:
raise ValueError("Titel max. 100 Zeichen")
return self._repo.create(TaskDTO(0, title.strip(), description, "open"))
def complete_task(self, task_id: int) -> TaskDTO:
task = self._repo.get_by_id(task_id)
if not task:
raise ValueError(f"Task {task_id} nicht gefunden")
task.status = "done"
return self._repo.update(task)
def list_open_tasks(self) -> List[TaskDTO]:
return [t for t in self._repo.get_all() if t.status != "done"]
# === Schicht 1: Präsentation (CLI) ===
class TaskCLI:
def __init__(self, service: TaskService):
self._service = service
def run(self):
while True:
print("\n1=Neuer Task 2=Offene Tasks 3=Abschließen 4=Beenden")
choice = input("Wahl: ")
if choice == "1":
title = input("Titel: ")
desc = input("Beschreibung: ")
try:
task = self._service.create_task(title, desc)
print(f"Task {task.id} erstellt")
except ValueError as e:
print(f"Fehler: {e}")
elif choice == "2":
for t in self._service.list_open_tasks():
print(f"[{t.id}] {t.title} ({t.status})")
elif choice == "3":
tid = int(input("Task-ID: "))
try:
self._service.complete_task(tid)
print("Abgeschlossen")
except ValueError as e:
print(f"Fehler: {e}")
elif choice == "4":
break
if __name__ == "__main__":
repo = TaskRepository()
service = TaskService(repo)
cli = TaskCLI(service)
cli.run()
Vorteile und Nachteile
Vorteile
- Strukturierte, wartbare Anwendung
- Leichter Austausch von Komponenten
- Gute Testbarkeit
Nachteile
- Initial mehr Aufwand
- Overhead bei sehr kleinen Projekten
Typische Prüfungsfragen (mit Kurzantwort)
- Was beschreibt das 3-Schichten-Modell? Präsentation, Logik, Datenzugriff.
- Welche Schicht validiert Eingaben? Business-Logik.
- Was gehört zur Datenzugriffsschicht? DB-Zugriff, SQL/ORM, Repositories.
FAQ — Häufige Fragen zum 3-Schichten-Modell
F: Was ist der Unterschied zwischen 3-Schichten-Modell und 3-Tier-Architektur? A: Das 3-Schichten-Modell beschreibt eine logische Trennung (Präsentation, Business, Persistence) —> alle Schichten können auf einem Server laufen. Die 3-Tier-Architektur beschreibt eine physische Trennung auf drei Maschinen: Client, Application-Server und Datenbank-Server. In der Prüfung werden beide Begriffe oft synonym verwendet.
F: Warum sollte man nicht direkt aus der UI auf die Datenbank zugreifen? A: Durch direkte DB-Zugriffe aus der UI entsteht eine starke Kopplung. Datenbankschema-Änderungen erfordern Anpassungen an allen UI-Stellen. Business-Regeln (Validierungen) können nicht zentral gesteuert werden. SQL-Injection-Angriffe sind schwerer abzuwehren. Die Business-Schicht fungiert als zentrale Kontroll- und Schutzinstanz.
F: Wo genau liegen die Grenzen zwischen den Schichten? A: Die Präsentation kennt nur die Business-Schicht und übergibt Rohdaten. Die Business-Schicht validiert und übergibt DTOs an die Persistence-Schicht. Die Persistence kennt nur SQL/ORM. Wenn in einem Repository Methodennamen wie “validiere” oder “prüfe” auftauchen, ist die Grenze verletzt.
F: Was ist ein DTO und warum braucht man es?
A: Ein DTO (Data Transfer Object) trägt nur Daten, enthält keine Logik. Im Python-Beispiel ist TaskDTO ein DTO. DTOs entkoppeln die Schichten, da jede Schicht nur das DTO-Format kennen muss — nicht die internen Strukturen der anderen Schichten.
F: Wann lohnt sich das 3-Schichten-Modell nicht? A: Bei sehr kleinen Skripten (wenige hundert Zeilen) ist der Overhead unnötig. Sobald Unit-Tests nötig sind oder die Datenbank austauschbar sein soll, lohnt sich die Trennung. Die Faustregel: Ein-Personen-Skript = flach, Team-Projekt oder wartbare Anwendung = 3 Schichten.
Freie Antwort
Für IHK-Projekte ist das Modell ideal, weil es leicht zu zeichnen und zu begründen ist. Wichtig: keine SQLs im Controller und keine DB-Zugriffe aus der UI.
Lernstrategie
- Modell für ein System zeichnen (Shop/Blog).
- CRUD-App strikt nach Schichten implementieren.
- Schichten in Projektdoku erklären.
- Trennung technisch durch Pakete/Namespaces umsetzen.