Files
stechuhr3/backend/ARCHITECTURE.md
Torsten Schulz (local) e95bb4cb76 Initial commit: TimeClock v3 - Node.js/Vue.js Zeiterfassung
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
2025-10-17 14:11:28 +02:00

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! 🚀