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
This commit is contained in:
298
backend/ARCHITECTURE.md
Normal file
298
backend/ARCHITECTURE.md
Normal file
@@ -0,0 +1,298 @@
|
||||
# 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! 🚀
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user