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
299 lines
7.4 KiB
Markdown
299 lines
7.4 KiB
Markdown
# 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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**
|
|
```javascript
|
|
// 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:
|
|
|
|
```javascript
|
|
// 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:
|
|
|
|
```javascript
|
|
// 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:
|
|
|
|
```javascript
|
|
// GET /api/time-entries/running
|
|
getRunningEntry()
|
|
|
|
// GET /api/time-entries/project/:projectName
|
|
getEntriesByProject()
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Error Handling
|
|
|
|
**In Services:**
|
|
```javascript
|
|
throw new Error('Beschreibende Fehlermeldung');
|
|
```
|
|
|
|
**In Controllern:**
|
|
```javascript
|
|
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:
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```javascript
|
|
router.get('/', controller.method.bind(controller));
|
|
```
|
|
|
|
## Migration auf Datenbank
|
|
|
|
Die Architektur ist vorbereitet für Datenbank-Integration:
|
|
|
|
```javascript
|
|
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! 🚀
|
|
|
|
|
|
|