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

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