Features: - Backend: Node.js/Express mit MySQL/MariaDB - Frontend: Vue.js 3 mit Composition API - UTC-Zeithandling für korrekte Zeiterfassung - Timewish-basierte Überstundenberechnung - Wochenübersicht mit Urlaubs-/Krankheits-/Feiertagshandling - Bereinigtes Arbeitsende (Generell/Woche) - Überstunden-Offset für historische Daten - Fixed Layout mit scrollbarem Content - Kompakte UI mit grünem Theme
7.4 KiB
Backend-Architektur
Übersicht
Das Backend folgt einer modernen, klassenbasierten Architektur mit klarer Trennung der Verantwortlichkeiten (Separation of Concerns).
Architektur-Schichten
┌─────────────────────────────────────┐
│ Routes (Express) │ ← HTTP-Endpunkte definieren
├─────────────────────────────────────┤
│ Controller-Klassen │ ← HTTP Request/Response Handling
├─────────────────────────────────────┤
│ Service-Klassen │ ← Business-Logik
├─────────────────────────────────────┤
│ Models │ ← Datenmodelle & Validierung
├─────────────────────────────────────┤
│ Datenspeicher │ ← In-Memory / Datenbank
└─────────────────────────────────────┘
Komponenten
1. Routes (src/routes/)
Verantwortlichkeit: Definition der HTTP-Endpunkte
// src/routes/timeEntries.js
router.get('/', timeEntryController.getAllEntries.bind(timeEntryController));
- Definiert URL-Pfade und HTTP-Methoden
- Bindet Endpunkte an Controller-Methoden
- Keine Business-Logik
2. Controller (src/controllers/)
Verantwortlichkeit: HTTP Request/Response Handling
// src/controllers/TimeEntryController.js
class TimeEntryController {
async getAllEntries(req, res) {
try {
const entries = timeEntryService.getAllEntries();
res.json(entries);
} catch (error) {
res.status(500).json({ error: 'Fehler...' });
}
}
}
Controller sind zuständig für:
- ✅ Request-Parameter extrahieren
- ✅ Service-Methoden aufrufen
- ✅ HTTP-Statuscodes setzen
- ✅ Response formatieren
- ✅ Error-Handling (HTTP-spezifisch)
Controller sind NICHT zuständig für:
- ❌ Business-Logik
- ❌ Datenvalidierung (außer HTTP-Parameter)
- ❌ Datenbankzugriff
- ❌ Komplexe Berechnungen
3. Services (src/services/)
Verantwortlichkeit: Business-Logik
// src/services/TimeEntryService.js
class TimeEntryService {
createEntry(entryData) {
// Validierung
const runningEntry = this.timeEntries.find(e => e.isRunning);
if (runningEntry) {
throw new Error('Es läuft bereits ein Timer...');
}
// Business-Logik
const newEntry = new TimeEntry({ ... });
newEntry.validate();
// Speichern
this.timeEntries.push(newEntry);
return newEntry;
}
}
Services sind zuständig für:
- ✅ Komplette Business-Logik
- ✅ Datenvalidierung
- ✅ Datenzugriff
- ✅ Berechnungen
- ✅ Business-Rules
- ✅ Transaktionen
Services sind NICHT zuständig für:
- ❌ HTTP-spezifische Logik
- ❌ Response-Formatierung
- ❌ HTTP-Statuscodes
4. Models (src/models/)
Verantwortlichkeit: Datenmodelle und Validierung
// src/models/TimeEntry.js
class TimeEntry {
constructor(data) { ... }
validate() {
if (!this.startTime) {
throw new Error('Startzeit ist erforderlich');
}
}
calculateDuration() { ... }
getFormattedDuration() { ... }
}
Models sind zuständig für:
- ✅ Datenstruktur definieren
- ✅ Datenvalidierung
- ✅ Hilfsmethoden für Daten
- ✅ Formatierung
Vorteile dieser Architektur
1. Separation of Concerns
Jede Schicht hat eine klar definierte Verantwortlichkeit:
- Routes → Routing
- Controller → HTTP-Handling
- Services → Business-Logik
- Models → Datenmodelle
2. Testbarkeit
// Services können isoliert getestet werden
const service = new TimeEntryService();
const entry = service.createEntry({ project: 'Test' });
3. Wiederverwendbarkeit
Services können von verschiedenen Controllern oder anderen Services verwendet werden.
4. Wartbarkeit
Änderungen an der Business-Logik betreffen nur Services, nicht Controller oder Routes.
5. Skalierbarkeit
- Services können einfach auf Microservices aufgeteilt werden
- Einfache Integration von Datenbanken
- Caching-Schichten hinzufügen
Datenfluss
Beispiel: Neuen Zeiteintrag erstellen
1. HTTP POST Request
↓
2. Route: POST /api/time-entries
↓
3. Controller.createEntry()
- Extrahiert req.body
- Ruft Service auf
↓
4. Service.createEntry()
- Validiert Daten
- Prüft Business-Rules
- Erstellt Model
- Speichert Daten
↓
5. Controller.createEntry()
- Setzt Status 201
- Sendet JSON Response
↓
6. HTTP Response
Singleton-Pattern
Sowohl Controller als auch Services werden als Singleton exportiert:
// Am Ende der Datei
module.exports = new TimeEntryService();
Vorteile:
- Gleicher Datenspeicher über alle Requests
- Keine Instanziierung bei jedem Request
- Shared State (für In-Memory Storage)
Hinweis: Bei Datenbank-Integration kann auf Singleton verzichtet werden.
Erweiterte Features
Service-Features
Die TimeEntryService-Klasse bietet erweiterte Funktionen:
// Statistiken mit Zeitfilter
getStatistics() → {
totalEntries,
projectStats,
timeStats: { today, week, month }
}
// Laufenden Timer abrufen
getRunningEntry()
// Nach Projekt filtern
getEntriesByProject(projectName)
// Datumsbereich filtern
getEntriesByDateRange(startDate, endDate)
Controller-Features
Der TimeEntryController bietet zusätzliche Endpunkte:
// GET /api/time-entries/running
getRunningEntry()
// GET /api/time-entries/project/:projectName
getEntriesByProject()
Best Practices
1. Error Handling
In Services:
throw new Error('Beschreibende Fehlermeldung');
In Controllern:
catch (error) {
if (error.message.includes('nicht gefunden')) {
return res.status(404).json({ error: '...' });
}
res.status(500).json({ error: '...' });
}
2. Async/Await
Controller-Methoden sind async, auch wenn Services aktuell synchron sind:
async getAllEntries(req, res) {
// Vorbereitet für asynchrone Service-Calls (z.B. Datenbank)
}
3. Binding
Bei Klassen-Methoden als Express-Handler muss bind() verwendet werden:
router.get('/', controller.method.bind(controller));
Migration auf Datenbank
Die Architektur ist vorbereitet für Datenbank-Integration:
class TimeEntryService {
async createEntry(entryData) {
// Statt In-Memory:
// return await TimeEntryModel.create(entryData);
}
}
Nur die Service-Schicht muss angepasst werden, Controller bleiben unverändert!
Zusammenfassung
| Schicht | Datei | Verantwortlichkeit | Beispiel |
|---|---|---|---|
| Routes | timeEntries.js |
URL-Mapping | router.get('/', ...) |
| Controller | TimeEntryController.js |
HTTP-Handling | res.json(data) |
| Service | TimeEntryService.js |
Business-Logik | validateData() |
| Model | TimeEntry.js |
Datenmodell | validate() |
Diese Architektur ermöglicht eine professionelle, wartbare und skalierbare Backend-Anwendung! 🚀