dhbw: import original web-eng inhalte (testing, typescript, docker, best practices) + framework-übersicht, native html, prüfungsleistung-detail, sose 2026

This commit is contained in:
2026-05-06 18:28:20 +02:00
parent 73be35ccb8
commit 96362e2959
9 changed files with 1889 additions and 9 deletions

View File

@@ -22,7 +22,7 @@ SLIDES_DIR = slides
# DHBW course settings
dhbw_NAME = Technik I Grundlagen IT
dhbw_KAPITEL = 01_web_eng 02_css_extended 03_nodejs_basics 04_nodejs_advanced
dhbw_KAPITEL = 01_web_eng 02_css_extended 03_nodejs_basics 04_nodejs_advanced 05_testing 06_typescript 07_docker 08_best_practices
# Deploy paths
HDM_DEPLOY_PATH = /home/tengo/html/hdm

View File

@@ -4,7 +4,7 @@ theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering DHBW Stuttgart"
footer: "Michael Czechowski SoSe 2025"
footer: "Michael Czechowski SoSe 2026"
title: Web Engineering
---
@@ -98,11 +98,52 @@ a {
---
# Prüfungsleistung
# Prüfungsleistung Übersicht
**LN** Lernnachweis (50%)
- Projekte / Hausaufgaben
- Präsentation
Eigenes Projekt: **Web-App** ODER **Backend** (API/CLI).
**Grundanforderungen (75 Punkte)**
| Punkte | Bereich |
|--------|---------|
| 20 | Idee, Konzeption, Planung |
| 5 | Plattformunabhängigkeit |
| 25 | Clean Code (KISS, SOLID, DI, Testing, Error Handling) |
| 15 | Präsentation |
| 10 | Dokumentation |
**Zusatzpunkte (max. 10):** TypeScript, Docker, Dev/Prod-Parity, `.env`, npm-Publish, Domain, HTTPS, Responsive Design.
---
# Prüfungsleistung Idee & Konzeption (20 P.)
- "Powerpoint"-Präsentation + Folien
- Aussagekräftiger Arbeitstitel + Beschreibung
- **Elevator Pitch** (max. 1 Min.)
- Repo-Link (GitHub / GitLab / Codeberg / BitBucket)
- **Logs**: wie hat sich Projekt verändert vs. Ursprungsidee?
- Schematischer Projektaufbau (UML o. Ä.)
---
# Prüfungsleistung Clean Code (25 P.)
- `README.md` (clone, start, contribute)
- **KISS** Keep It Simple, Stupid
- **SOLID** SRP, OCP, LSP, ISP, DIP
- **DI** Dependency Injection
- **Testing-Pyramide** (Unit, Integration, E2E)
- **Exception/Error Handling**
---
# Prüfungsleistung Abgabe & Termine
- **Code-Upload:** bis 27.07.
- **Präsentation:** 17.07. (Gruppen, ~10 Min.)
Details: https://git.dailysh.it/DHBW/pruefungsleistung
---
@@ -411,6 +452,205 @@ async function loadProducts() {
![bg fit](./assets/demos/web-ecosystem-rings.png)
---
# JavaScript Frameworks Kategorien
| Kategorie | Beispiele |
|-----------|-----------|
| **CSS Frameworks** | Tailwind, Bootstrap, shadcn/ui |
| **Frontend Frameworks** | React, Vue, Svelte, Astro |
| **Rendering / Meta** | Next.js, Nuxt, Gatsby |
| **Build Tools / Bundler** | Webpack, Vite, Parcel, esbuild |
| **Backend Frameworks** | Express, Fastify, NestJS |
---
# Vanilla JS Counter
```html
<button id="myButton">Clicked 0 times</button>
<script>
let count = 0;
const btn = document.getElementById('myButton');
btn.addEventListener('click', () => {
count++;
btn.textContent = `Clicked ${count} times`;
});
</script>
```
✓ hohe Kompatibilität, simpel
✗ State-Management, Re-Rendering wird schnell komplex
---
# React Counter (JSX)
```jsx
import { useState } from "react";
function MyButton() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
```
- **Component-Based** Architecture
- **Virtual DOM**, JSX, Hooks
- großes Ökosystem (React Router, Redux, …)
---
# Vue 3 Composition API
```vue
<script setup>
import { ref } from "vue";
const count = ref(0);
</script>
<template>
<button @click="count++">
Clicked {{ count }} times
</button>
</template>
```
- **Reactive Data Binding**
- Single-File Components (`.vue`)
- Vue Router, Pinia (State)
---
# Svelte Counter
```svelte
<script>
let count = 0;
</script>
<button on:click={() => count += 1}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
```
- **Compiler-Based** kein Runtime-Framework
- Reactive Assignments (`let`)
- Built-in Animationen
---
# Astro Content-First
```astro
---
const greeting = "Hallo DHBW";
---
<html>
<body>
<h1>{greeting}</h1>
<MyReactButton client:load />
</body>
</html>
```
- **Islands Architecture** Komponenten mehrerer Frameworks mischen
- Default: kein JS im Output (nur statisches HTML)
- Hydration on demand (`client:load`, `client:visible`)
---
# Rendering Frameworks
| | Frontend | SSR | SSG | File Routing |
|---|---|---|---|---|
| **Next.js** | React | ✓ | ✓ | `/app/page.js` |
| **Nuxt** | Vue | ✓ | ✓ | `/pages/*.vue` |
| **Gatsby** | React | | ✓ | GraphQL Data |
---
# Build Tools Webpack
- Module Bundling (JS, CSS, Images)
- Module Loaders + großes Plugin-Ökosystem
- Code Splitting, HMR
- Konfigurations-lastig (`webpack.config.js`)
→ Standard für viele Legacy-Setups.
---
# Build Tools Vite
```javascript
// vite.config.js
import { defineConfig } from "vite";
export default defineConfig({
server: {
proxy: {
"/api": {
target: "http://localhost:4567",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, "")
}
}
}
});
```
- Rollup unter der Haube, **HMR mit ES Modules**
- Tree-Shaking, Code Splitting, React + Vue out-of-the-box
---
# Build Tools Parcel
```json
{
"name": "my-project",
"source": "src/index.html",
"browserslist": "> 0.5%, last 2 versions, not dead",
"scripts": {
"start": "parcel",
"build": "parcel build"
},
"devDependencies": {
"parcel": "latest"
}
}
```
- **Zero-Config**
- Auto-Resolution, HMR
- TS / CSS-Preprocessors out-of-the-box
---
# Microfrontends
- Mehrere unabhängige Frontend-Apps in einer Page
- Module Federation (Webpack 5)
- **single-spa**: https://single-spa.js.org/
- Anwendungsfall: große Teams, unterschiedliche Tech-Stacks pro Domain
→ Komplexitätskosten: nur bei echtem Skalierungsbedarf.
---
# Demo-Repos
- https://github.com/nextlevelshit/dhbw-client-js (Counter, multi-framework)
- https://github.com/nextlevelshit/node-cache-api (API + Tests)
- https://github.com/nextlevelshit/dhbw-docker (Compose-Stack)
---
# Fragen?

View File

@@ -4,7 +4,7 @@ theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering DHBW Stuttgart"
footer: "Michael Czechowski SoSe 2025"
footer: "Michael Czechowski SoSe 2026"
---
<style>
@@ -259,6 +259,184 @@ transition: background 0.2s, transform 0.1s;
---
# CSS Specificity
Welcher Selektor gewinnt? **Punktezählung:**
```
ID 0,1,0,0
Class / Attr / Pseudo-Class 0,0,1,0
Tag / Pseudo-Element 0,0,0,1
Inline Style 1,0,0,0
!important setzt alles außer Kraft
```
```css
#main .btn:hover /* 0,1,2,0 */
.btn.primary /* 0,0,2,0 */
button[type="submit"] /* 0,0,1,1 */
```
→ Faustregel: möglichst flach. `!important` vermeiden.
---
# CSS Frameworks
| Framework | Stil |
|-----------|------|
| **Bootstrap** | Komponenten + Grid, opinionated |
| **Tailwind CSS** | Utility-First, atomar |
| **Bulma** | klassisch, ohne JS |
| **shadcn/ui** | kopierbare Komponenten (Tailwind-basiert) |
| **Flowbite** | Tailwind-Komponentenbib. |
---
# Tailwind CSS Beispiel
```html
<button class="bg-blue-600 hover:bg-blue-700
text-white font-semibold
px-4 py-2 rounded-lg
transition shadow-sm">
Klick mich
</button>
```
- Keine eigenen CSS-Klassen mehr
- JIT-Compiler erzeugt nur genutzte Klassen
- Design-System via `tailwind.config.js`
---
<!-- _class: invert -->
<!-- _backgroundColor: #001520 -->
# No JavaScript, No Cry
## Native HTML statt JS-Komponenten
---
# Native HTML Was früher JS brauchte
| Komponente | Native Alternative |
|------------|--------------------|
| Accordion / Collapsible | `<details>` + `<summary>` |
| Modal / Dialog | `<dialog>` |
| Date Picker | `<input type="date">` |
| Time Picker | `<input type="time">` |
| Color Picker | `<input type="color">` |
| Range Slider | `<input type="range">` |
| Progress Bar | `<progress>` |
| Auto-Complete | `<datalist>` |
→ Baseline Widely Available (Chrome, Firefox, Safari, Edge).
---
# `<details>` + `<summary>`
```html
<details>
<summary>Mehr Infos</summary>
<p>Hier steht der ausklappbare Inhalt.</p>
</details>
```
- Keine Zeile JS
- Tastatur-zugänglich
- Animierbar mit `::details-content` (CSS only)
---
# `<dialog>` Native Modal
```html
<dialog id="myDialog">
<form method="dialog">
<p>Wirklich löschen?</p>
<button value="cancel">Abbrechen</button>
<button value="ok">OK</button>
</form>
</dialog>
<button onclick="myDialog.showModal()">Öffnen</button>
```
- `showModal()` blockt Hintergrund + ESC schließt
- Backdrop via `dialog::backdrop`
- Form mit `method="dialog"` schließt + liefert `returnValue`
---
# `<input type="date">` & Co.
```html
<input type="date" min="2026-01-01" max="2026-12-31">
<input type="time" step="60">
<input type="color" value="#005f8a">
<input type="range" min="0" max="100" step="5" value="50">
<input type="number" min="1" max="300">
```
- Plattform-native UI (Touch-Datepicker auf Mobile)
- Validierung "for free"
- Locale-Awareness vom Browser
---
# `<progress>` Native Progress Bar
```html
<progress value="70" max="100">70%</progress>
<!-- indeterminate -->
<progress></progress>
```
```css
progress {
width: 100%;
height: 0.5rem;
}
progress::-webkit-progress-bar { background: #eee; }
progress::-webkit-progress-value { background: #005f8a; }
```
---
# Form Validation Native
```html
<form>
<input type="email" required
pattern=".+@dhbw\.de"
title="Bitte DHBW-Mail eingeben">
<input type="password" minlength="8" required>
<button>Login</button>
</form>
```
- `required`, `min`, `max`, `pattern`, `step`
- `:invalid` / `:valid` CSS-Pseudoklassen
- `form.checkValidity()` / `input.setCustomValidity()`
---
# Wann doch JS?
- Komplexe Interaktion über mehrere Komponenten
- State, der nicht ins DOM gehört
- Async / Server-Sync
- Animationen mit komplexer Logik
- Cross-Browser-Polyfills (selten heute)
**Default: HTML/CSS. JS nur wenn nötig.**
---
# Fragen?
1. Was bedeutet `div > p` vs `div p`?

View File

@@ -4,7 +4,7 @@ theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering DHBW Stuttgart"
footer: "Michael Czechowski SoSe 2025"
footer: "Michael Czechowski SoSe 2026"
---
<style>

View File

@@ -4,7 +4,7 @@ theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering DHBW Stuttgart"
footer: "Michael Czechowski SoSe 2025"
footer: "Michael Czechowski SoSe 2026"
---
<style>

356
slides/dhbw/05_testing.md Normal file
View File

@@ -0,0 +1,356 @@
---
marp: true
theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering DHBW Stuttgart"
footer: "Michael Czechowski SoSe 2026"
title: Testing
---
<style>
:root {
--color-foreground: #1a1a2e;
--color-highlight: #005f8a;
--color-dimmed: #4a6a7a;
}
section.invert {
--color-foreground: #fff;
}
section {
font-size: 1.7rem;
}
h1 {
color: #005f8a;
}
section.invert h1 {
color: #fff;
}
pre {
background: #0f1e2e;
color: #00b4d8;
border-radius: 8px;
border-left: 3px solid #005f8a;
}
pre code {
background: transparent;
color: inherit;
}
code {
background: #1a3a4a;
color: #00b4d8;
padding: 0.15em 0.4em;
border-radius: 4px;
}
a {
color: var(--color-highlight);
}
</style>
<!-- _class: invert -->
<!-- _header: '' -->
<!-- _backgroundColor: #001520 -->
# Testing
## Unit · Integration · End-to-End
---
# Inhalt
1. Test Automation Pyramid
2. Test Automation Trophy
3. Static Code Analysis
4. Unit Tests
5. Integration Tests
6. End-to-End Tests (API + Client)
7. Live Coding
---
# Test Automation Pyramid
```
▲ manual / exploratory
▲ ▲ E2E (UI + API)
▲▲▲▲ Integration (class + real deps)
▲▲▲▲▲▲ Unit (class + mocked deps)
▲▲▲▲▲▲▲▲ Static analysis (compiler, linter)
```
- Oben: wenige, langsam, teuer, brüchig
- Unten: viele, schnell, billig, stabil
---
# Test Automation V-Modell
```
Requirements ────────────► Acceptance Test
▼ ▲
Architecture ────────► Integration Test
▼ ▲
Modular Design ──────► Unit Test
▼ ▲
Implementation
```
Jede Design-Phase hat Testpendant. Testfälle früh ableiten.
---
# Test Automation Trophy (Kent C. Dodds)
```
🏆
┌─────────┐
│ E2E │ ← klein
├─────────┤
│ Integr. │ ← GROSS (Hauptfokus)
├─────────┤
│ Unit │ ← mittel
├─────────┤
│ Static │ ← Basis (TS, ESLint)
└─────────┘
```
Andere Gewichtung: **Integration im Zentrum**, Static als breite Basis.
---
# Testing Frameworks Übersicht
**Static Code Analysis**
- ESLint, Prettier, SonarQube, TypeScript
**Unit + Integration**
- jest, mocha, **vitest**
**End-to-End**
- **Cypress**, **Playwright**, supertest (API only)
**Visual / Interaction**
- Storybook, Chromatic
---
# Static Code Analysis
- Läuft in Millisekunden
- Findet Syntaxfehler, ungenutzte Variablen, undefined refs
- Kein Runtime-Overhead
- **Erste Verteidigungslinie**
```bash
npm i -D eslint prettier
npx eslint --init
npx prettier --write .
```
`SonarQube`: zentraler Server, Code Smells, Security, Coverage.
---
# Unit Tests Eigenschaften
- **Isolation** jede Dependency gemockt
- **Deterministisch** gleicher Input → gleicher Output
- **Schnell** kein I/O, kein Netzwerk, kein FS
- **Fokussiert** eine Funktion pro Test
---
# Unit Test Beispiel (vitest)
```javascript
// cache.unit.test.js
import { describe, it, expect } from "vitest";
import { Cache } from "./cache.js";
describe("Cache", () => {
it("creates and retrieves data with custom key", () => {
const cache = new Cache();
const data = { user: "wolfgang", city: "stuttgart" };
const key = cache.create(data, "custom-key");
expect(key).toBe("custom-key");
expect(cache.get("custom-key")).toEqual(data);
});
});
```
---
# Integration Tests Eigenschaften
- **Reale Komponenten** echte Cache + Express-Instanzen
- **Supertest-Magie** simuliert HTTP ohne Server-Startup
- **In-Process** alles im selben Node-Prozess
- **Kein Netzwerk** keine TCP-Connections
→ teste Zusammenspiel mehrerer Module ohne Deployment.
---
# Integration Test Beispiel (supertest)
```javascript
// app.integration.test.js
import request from "supertest";
import { app, cache } from "./app.js";
test("full CRUD lifecycle with real cache", async () => {
const res = await request(app)
.post("/api/entries")
.send({ name: "integration-test", status: "active" });
expect(res.status).toBe(201);
const { key } = res.body;
expect(cache.keys()).toContain(key);
});
```
---
# End-to-End Tests (API)
- **Echter Server** HTTP auf TCP-Port
- **Echtes Netzwerk** `fetch` über localhost
- **Echte Umgebung** CORS, Middleware-Stack
- **User-Perspektive** exakt was Clients sehen
---
# E2E API Test Beispiel
```javascript
// index.e2e.test.js
import { createServer } from "node:http";
import { app } from "./app.js";
let server;
const TEST_PORT = 8081;
const baseUrl = `http://localhost:${TEST_PORT}`;
beforeAll(async () => {
server = createServer(app);
await new Promise((r) => server.listen(TEST_PORT, r));
});
afterAll(() => server.close());
test("complete user journey", async () => {
const res = await fetch(`${baseUrl}/api`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: "e2e", timestamp: Date.now() }),
});
expect(res.status).toBe(201);
});
```
---
# End-to-End Tests (Client)
- **Echter Browser** Chromium / Firefox / WebKit
- **Echtes DOM** Klicks, Tippen, Scrollen
- **Echtes Rendering** CSS, async Operationen
- **Cross-Origin** Client ↔ Server-Kommunikation
Tools: **Cypress**, **Playwright**
---
# Cypress Minimalbeispiel
```javascript
// cypress/e2e/counter.cy.js
describe("Counter App", () => {
it("increments on click", () => {
cy.visit("http://localhost:3000");
cy.contains("button", "Clicked 0 times").click();
cy.contains("button", "Clicked 1 times");
});
});
```
```bash
npx cypress open # GUI
npx cypress run # headless / CI
```
---
# Playwright Minimalbeispiel
```javascript
// tests/counter.spec.js
import { test, expect } from "@playwright/test";
test("counter increments", async ({ page }) => {
await page.goto("http://localhost:3000");
await page.getByRole("button").click();
await expect(page.getByRole("button")).toContainText("Clicked 1 times");
});
```
```bash
npx playwright test
npx playwright test --ui
```
---
# Coverage Reports
```bash
npx vitest run --coverage
```
```
File | % Stmts | % Branch | % Funcs | % Lines
--------------|---------|----------|---------|--------
cache.js | 95.2 | 80.0 | 100.0 | 95.2
app.js | 88.7 | 75.0 | 90.0 | 88.7
```
- `lcov.info` → CI / SonarQube
- HTML Report → `coverage/index.html`
---
# Best Practices
- **AAA**: Arrange · Act · Assert
- Tests sind **Dokumentation** beschreibend benennen
- Ein Verhalten pro Test
- Keine Logik im Test (keine `if`/`for`)
- **Test Doubles**: stub, mock, spy, fake
- CI: Tests bei jedem Push
---
# Live Coding
Repo: `node-cache-api`
```bash
git clone https://github.com/nextlevelshit/node-cache-api
cd node-cache-api
npm install
npm test # unit + integration
npm run test:e2e # e2e
```
---
<!-- _class: invert -->
<!-- _backgroundColor: #001520 -->
# Fragen?
**Hausaufgabe:** Eigene Projektidee mit mind. 1 Unit + 1 Integration Test starten.

View File

@@ -0,0 +1,422 @@
---
marp: true
theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering DHBW Stuttgart"
footer: "Michael Czechowski SoSe 2026"
title: TypeScript
---
<style>
:root {
--color-foreground: #1a1a2e;
--color-highlight: #005f8a;
--color-dimmed: #4a6a7a;
}
section.invert {
--color-foreground: #fff;
}
section {
font-size: 1.7rem;
}
h1 {
color: #005f8a;
}
section.invert h1 {
color: #fff;
}
pre {
background: #0f1e2e;
color: #00b4d8;
border-radius: 8px;
border-left: 3px solid #005f8a;
}
pre code {
background: transparent;
color: inherit;
}
code {
background: #1a3a4a;
color: #00b4d8;
padding: 0.15em 0.4em;
border-radius: 4px;
}
a {
color: var(--color-highlight);
}
</style>
<!-- _class: invert -->
<!-- _header: '' -->
<!-- _backgroundColor: #001520 -->
# TypeScript
## JavaScript on Steroids
---
# Inhalt
1. TypeScript: was und warum
2. `tsc` + `ts-node`
3. `tsconfig.json`
4. Type Primitives
5. Union Types, Type Alias, Interface
6. Enum, Generics, Type Casting
7. Compilation: types stripped
8. Decorators + reflect-metadata
---
# TypeScript Was?
- **Superset von JavaScript** (Microsoft, 2012)
- Jede `.js` lässt sich zu `.ts` umbenennen
- **Type Errors zur Compile-/Transpile-Zeit**
- Neuere ECMAScript-Features verfügbar
- **Im Browser:** läuft nicht direkt wird zu JS kompiliert
---
# Setup: tsc + ts-node
```bash
npm i -D typescript ts-node @types/node
npx tsc --init # erzeugt tsconfig.json
```
**Run:**
```bash
npx ts-node index.ts # statt: node index.js
```
**Build:**
```bash
npx tsc # kompiliert nach ./dist
node dist/index.js
```
---
# tsconfig.json
```json
{
"compilerOptions": {
"target": "es2022",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
---
# Type Primitives
```typescript
let loading: boolean = false;
let count: number = 42;
let pi: number = 3.14;
let name: string = "DHBW ist fetzig!";
let empty: null = null;
let missing: undefined = undefined;
function log(msg: string): void {
console.log(msg);
}
```
`any`, `unknown`, `never`, `void` sind weitere TS-Spezialtypen.
---
# Union Types
```typescript
let id: number | string;
id = 101; // ok
id = "101"; // ok
id = true; // ✗ TypeError
```
---
# Type Alias
```typescript
type NumOrString = number | string;
let id: NumOrString;
id = 101;
id = "abc";
type UserId = string;
type Status = "active" | "inactive" | "pending";
```
---
# Interface
```typescript
interface User {
id: number;
name: string;
email?: string; // optional
readonly createdAt: Date; // schreibgeschützt
greet(msg: string): void;
}
const u: User = {
id: 1,
name: "Lisa",
createdAt: new Date(),
greet(msg) { console.log(msg); }
};
```
---
# Interface vs Type
**Interface**
- nur Objektformen
- kann mehrfach deklariert (Merging)
- bei perf-kritischen Checks oft schneller
**Type Alias**
- alles: Primitive, Union, Tuple, Function-Signaturen
- nicht erweiterbar nach Definition
→ Faustregel: **Interfaces für API-Shapes, Types für alles andere.**
---
# Enum
```typescript
enum Direction {
Up = 1,
Down, // 2
Left, // 3
Right // 4
}
function walk(d: Direction): string {
switch (d) {
case Direction.Up: return "bow";
case Direction.Down: return "stern";
case Direction.Left: return "windward";
case Direction.Right: return "leeward";
}
}
walk(Direction.Up); // "bow"
```
---
# Generics
```typescript
class GenericContainer<T> {
private readonly value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
const numbers = new GenericContainer<number>(10);
const strings = new GenericContainer<string>("Hello");
console.log(numbers.getValue()); // 10
console.log(strings.getValue()); // "Hello"
```
---
# Type Casting
```typescript
let someValue: any = "this is a string";
let len: number = (someValue as string).length;
console.log(len); // 16
// Alt-Syntax (nicht in TSX):
let len2: number = (<string>someValue).length;
```
`as unknown as T` für "double cast" nur Notbehelf!
---
# Compilation: Types werden gestripped
**TypeScript:**
```typescript
type Result = "pass" | "fail";
function verify(result: Result) {
if (result === "pass") console.log("Passed");
else console.log("Failed");
}
```
**JavaScript (nach `tsc`):**
```javascript
function verify(result) {
if (result === "pass") console.log("Passed");
else console.log("Failed");
}
```
**Types existieren nur zur Compile-Zeit.** Kein Runtime-Check.
---
<!-- _class: invert -->
<!-- _backgroundColor: #001520 -->
# Decorators
## Meta-Programmierung mit TypeScript
---
# TypeScript: Decorators
Vier Typen:
- **Class Decorators** auf ganze Klasse
- **Property Decorators** auf Felder
- **Method Decorators** auf Methoden
- **Parameter Decorators** auf Methoden-Parameter
Genutzt von: Angular, NestJS, TypeORM, class-validator.
---
# Decorators aktivieren
**`tsconfig.json`:**
```json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
```
**Root-File:**
```typescript
import "reflect-metadata";
```
---
# Decorator-Signaturen
```typescript
type ClassDecorator = <T extends Function>(target: T) => T | void;
type PropertyDecorator = (
target: Object,
propertyKey: string | symbol
) => void;
type MethodDecorator = <T>(
target: Object,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;
type ParameterDecorator = (
target: Object,
propertyKey: string | symbol | undefined,
parameterIndex: number
) => void;
```
---
# Decorator Beispiel
```typescript
function Log(target: any, key: string, desc: PropertyDescriptor) {
const original = desc.value;
desc.value = function (...args: any[]) {
console.log(`→ ${key}(${JSON.stringify(args)})`);
return original.apply(this, args);
};
}
class Calculator {
@Log
add(a: number, b: number) {
return a + b;
}
}
new Calculator().add(2, 3); // logs: → add([2,3])
```
---
# Reflect: Meta-Daten lesen
```typescript
import "reflect-metadata";
Reflect.defineMetadata("role", "admin", target);
Reflect.hasMetadata("role", target); // true
Reflect.getMetadata("role", target); // "admin"
```
Frameworks (NestJS, TypeORM) nutzen das, um zur **Runtime** Type-Informationen zu rekonstruieren z. B. für DI, ORM-Mapping, Validierung.
---
# Anwendung: NestJS-Beispiel
```typescript
@Controller("users")
export class UsersController {
constructor(private users: UsersService) {}
@Get(":id")
findOne(@Param("id") id: string) {
return this.users.find(+id);
}
}
```
`@Controller`, `@Get`, `@Param` sind Decorators Metadaten ⇒ Routing.
---
<!-- _class: invert -->
<!-- _backgroundColor: #001520 -->
# Fragen?
**Hausaufgabe:** Bestehendes JS-Projekt auf TS migrieren mind. 1 File + `tsconfig.json`.

338
slides/dhbw/07_docker.md Normal file
View File

@@ -0,0 +1,338 @@
---
marp: true
theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering DHBW Stuttgart"
footer: "Michael Czechowski SoSe 2026"
title: Docker und Service Orchestration
---
<style>
:root {
--color-foreground: #1a1a2e;
--color-highlight: #005f8a;
--color-dimmed: #4a6a7a;
}
section.invert {
--color-foreground: #fff;
}
section {
font-size: 1.7rem;
}
h1 {
color: #005f8a;
}
section.invert h1 {
color: #fff;
}
pre {
background: #0f1e2e;
color: #00b4d8;
border-radius: 8px;
border-left: 3px solid #005f8a;
}
pre code {
background: transparent;
color: inherit;
}
code {
background: #1a3a4a;
color: #00b4d8;
padding: 0.15em 0.4em;
border-radius: 4px;
}
a {
color: var(--color-highlight);
}
</style>
<!-- _class: invert -->
<!-- _header: '' -->
<!-- _backgroundColor: #001520 -->
# Docker
## Service Orchestration
---
# Inhalt
1. Architektur: Reverse Proxy + API + DB
2. Container vs VM
3. `Dockerfile`
4. `compose.yml`
5. Docker CLI / Compose Befehle
6. Live Demo: `dhbw-docker`
---
# Architektur
```
Browser (localhost:10007)
┌──────────────────┐
│ Reverse Proxy │ (nginx / traefik)
└─────────┬────────┘
┌────┴────┐
▼ ▼
┌─────────┐ ┌────────┐
│ Web │ │ API │
│ App │ │ (Node) │
└────┬────┘ └────┬───┘
└─────┬──────┘
┌────────┐
│ DB │ (Postgres)
└────────┘
```
---
# Container vs VM
| | VM | Container |
|---|----|-----------|
| Kernel | eigener | Host-Kernel |
| Boot | Minuten | Sekunden |
| Größe | GB | MB |
| Isolation | stark | namespaces, cgroups |
| Image | komplett | Layer (diff) |
**Docker** = Container-Engine + Image-Format + Registry.
---
# Dockerfile Express API
```dockerfile
FROM node:lts-slim AS builder
WORKDIR /app
COPY ./package*.json ./
RUN npm ci --no-audit --no-fund --no-progress --loglevel=error
COPY ./ ./
ENV NODE_ENV="production"
EXPOSE 8080
CMD ["npm", "start"]
```
**`npm ci`** statt `npm i` für reproduzierbare Builds.
---
# Multi-Stage Build
```dockerfile
FROM node:lts-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:lts-slim AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 8080
CMD ["node", "dist/index.js"]
```
→ kleines Final-Image, keine Build-Tools im Runtime.
---
# .dockerignore
```
node_modules
dist
.git
.env
*.log
coverage
```
Spart Build-Kontext und verhindert Geheimnis-Leaks.
---
# compose.yml Services
```yaml
name: dhbw-docker-app
services:
api:
build: ./api
container_name: dhbw-api
environment:
DATABASE_USERNAME: ${DATABASE_USERNAME}
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
DATABASE_NAME: ${DATABASE_NAME}
DATABASE_HOST: db
DATABASE_PORT: 5432
ports:
- "10000:8080"
networks: [net, data]
depends_on:
db:
condition: service_healthy
```
---
# compose.yml Datenbank + Proxy
```yaml
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${DATABASE_USERNAME}
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
POSTGRES_DB: ${DATABASE_NAME}
volumes:
- dbdata:/var/lib/postgresql/data
networks: [data]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$DATABASE_USERNAME"]
interval: 5s
retries: 5
proxy:
image: nginx:alpine
ports: ["10007:80"]
volumes: ["./nginx.conf:/etc/nginx/nginx.conf:ro"]
networks: [net]
depends_on: [api]
volumes:
dbdata:
networks:
net:
data:
```
---
# Docker Compose Befehle
```bash
docker compose -p "dhbw-docker-app" \
-f compose.yml \
--env-file .env \
up \
--build \
--remove-orphans \
--force-recreate
```
| Flag | Bedeutung |
|------|-----------|
| `-p` | Projektname |
| `-f` | mehrere Compose-Files möglich |
| `--env-file` | `.env` einlesen |
| `--build` | Images neu bauen |
| `--remove-orphans` | verwaiste Container weg |
| `--force-recreate` | Container neu starten |
---
# Compose Alltag
```bash
docker compose up -d # detached starten
docker compose ps # Status
docker compose logs -f api # Logs streamen
docker compose exec api sh # Shell im Container
docker compose down -v # Stop + Volumes löschen
docker compose restart api # Neustart einzelner Service
```
---
# Health Checks
```yaml
api:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
```
`depends_on: { condition: service_healthy }` wartet, bis Health passt.
---
# Networks: Segmentierung
- **`net`** öffentlich erreichbar (Proxy ↔ App)
- **`data`** nur intern (App ↔ DB)
- DB ist **nicht** vom Internet erreichbar
- Service-DNS: `api`, `db`, `proxy` (Container-Name = Hostname)
→ Defense in Depth.
---
# Volumes Daten persistent
```yaml
volumes:
dbdata: # benannt
- ./code:/app # bind mount (dev)
- /app/node_modules # anonymous (überschreibt bind)
```
`docker compose down` ohne `-v` lässt Volumes leben.
---
# Live Demo
Repo: https://github.com/nextlevelshit/dhbw-docker
```bash
git clone https://github.com/nextlevelshit/dhbw-docker
cd dhbw-docker
cp .env.example .env
docker compose up --build
```
→ http://localhost:10007
---
# 100 Concepts of Docker (Exkurs)
- Image · Container · Volume · Network
- Dockerfile · Layer Cache · BuildKit
- Compose · Stack · Swarm · Kubernetes
- Registry · Tag · Digest
- Dev/Prod-Parity (12-Factor)
- Secrets · Configs
- Logging Drivers
- Health Checks · Restart Policies
---
<!-- _class: invert -->
<!-- _backgroundColor: #001520 -->
# Fragen?
**Hausaufgabe:** Eigenes Projekt mit `Dockerfile` + `compose.yml` containerisieren.

View File

@@ -0,0 +1,346 @@
---
marp: true
theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering DHBW Stuttgart"
footer: "Michael Czechowski SoSe 2026"
title: Best Practices
---
<style>
:root {
--color-foreground: #1a1a2e;
--color-highlight: #005f8a;
--color-dimmed: #4a6a7a;
}
section.invert {
--color-foreground: #fff;
}
section {
font-size: 1.7rem;
}
h1 {
color: #005f8a;
}
section.invert h1 {
color: #fff;
}
pre {
background: #0f1e2e;
color: #00b4d8;
border-radius: 8px;
border-left: 3px solid #005f8a;
}
pre code {
background: transparent;
color: inherit;
}
code {
background: #1a3a4a;
color: #00b4d8;
padding: 0.15em 0.4em;
border-radius: 4px;
}
a {
color: var(--color-highlight);
}
</style>
<!-- _class: invert -->
<!-- _header: '' -->
<!-- _backgroundColor: #001520 -->
# Best Practices
## Wrap-up & Projektsupport
---
# Inhalt
1. Semantic Versioning
2. Git Commit Messages (Udacity Style)
3. 12 Factor App
4. Make it Work · Make it Right · Make it Fast
5. Präsentations-Tipps
6. Feedback (Sandwich-Methode)
7. Prüfungsleistung Recap
---
# Semantic Versioning (SemVer 2.0.0)
```
MAJOR.MINOR.PATCH
│ │ └─ Bugfix, abwärtskompatibel
│ └────── Neue Funktion, abwärtskompatibel
└──────────── Breaking Change
```
**Beispiele:**
- `1.4.2``1.4.3` Bugfix
- `1.4.3``1.5.0` neues Feature
- `1.5.0``2.0.0` Breaking Change
Pre-Release: `1.0.0-alpha.1`, `1.0.0-rc.2`
→ https://semver.org/
---
# package.json: Versionsranges
```json
{
"dependencies": {
"express": "^4.19.2", // ≥4.19.2 < 5.0.0 (gleiche MAJOR)
"lodash": "~4.17.21", // ≥4.17.21 < 4.18.0 (gleiche MINOR)
"react": "19.0.0" // exakt
}
}
```
`package-lock.json` pinnt die **tatsächlich installierten** Versionen.
---
# Git Commit Messages Udacity Style
```
<type>: <Subject>
<blank>
<Body>
<blank>
<Footer>
```
**Type:** `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
**Subject:** Imperativ, ≤50 Zeichen, klein, kein Punkt
**Body:** **Was** und **Warum** nicht **Wie**
**Footer:** Issue-Refs, Breaking Changes
---
# Commit-Beispiele
**Gut:**
```
feat: add user logout endpoint
Allows authenticated clients to invalidate their session token
on the server side instead of only deleting the cookie.
Closes #142
```
**Schlecht:**
```
fixed stuff
WIP
update
asdf
```
→ https://udacity.github.io/git-styleguide/
---
# Conventional Commits
```
feat(auth): add OAuth2 login
fix(api): return 404 instead of 500 on missing user
docs: update README install steps
chore(deps): bump express from 4.18 to 4.19
BREAKING CHANGE: drop Node 16 support
```
Vorteile: automatische Changelogs, automatische Version-Bumps (`semantic-release`).
---
# 12 Factor App
Methodik für moderne, skalierbare SaaS-Apps https://12factor.net/
| # | Faktor |
|---|--------|
| 1 | Codebase ein Repo, viele Deploys |
| 2 | Dependencies explizit deklariert (`package.json`) |
| 3 | Config via Environment-Variablen |
| 4 | Backing Services als Attached Resources |
| 5 | Build, Release, Run getrennt |
| 6 | Processes stateless |
---
# 12 Factor (2/2)
| # | Faktor |
|---|--------|
| 7 | Port Binding self-contained, exportiert HTTP |
| 8 | Concurrency horizontal skalieren via Prozesse |
| 9 | Disposability schneller Start, Graceful Shutdown |
| 10 | Dev/Prod Parity möglichst gleiche Umgebung |
| 11 | Logs als Event-Streams (`stdout`) |
| 12 | Admin Processes als One-Off |
---
# Config via ENV
```javascript
// schlecht
const db = "postgres://user:secret@prod-db:5432/app";
// gut
const db = process.env.DATABASE_URL;
```
```bash
# .env (NICHT committen!)
DATABASE_URL=postgres://user:secret@db:5432/app
NODE_ENV=production
PORT=8080
```
`.env.example` mit Dummy-Werten committen.
---
# Make It Work · Right · Fast
```
1. Make It Work → MVP, hässlich aber funktional
2. Make It Right → Refactor, Tests, Clean Code
3. Make It Fast → Profiling, Optimierung
```
Don't optimize what doesn't work.
Don't refactor what isn't working yet.
→ https://wiki.c2.com/?MakeItWorkMakeItRightMakeItFast
---
# KISS · DRY · YAGNI · SOLID
- **KISS** Keep It Simple, Stupid
- **DRY** Don't Repeat Yourself
- **YAGNI** You Aren't Gonna Need It
- **SOLID**:
- **S**ingle Responsibility
- **O**pen/Closed
- **L**iskov Substitution
- **I**nterface Segregation
- **D**ependency Inversion
→ Faustregeln, keine Dogmen.
---
# Präsentation Aufbau
- **Einstieg** mit aktuellem Bezug (optional)
- **Problemdefinition**: welches Bedürfnis / welches Problem?
- Was hat **blockiert**, wie gelöst?
- **Geschichte mit Spannungsbogen**
- Vorher klären: Fragen während oder nach der Präsentation?
---
# Präsentation Pitfalls
- Am Thema vorbeireden
- Folien vorlesen
- Live-Demo ohne Backup-Video
- "Sieht man hier nicht so gut, aber..."
- Zu viel Text pro Folie
- Keine klare Botschaft pro Slide
**1 Folie = 1 Aussage.**
---
# Feedback Sandwich-Methode
```
🍞 Lob / Positives
🥬 Kritik + konkreter Verbesserungsvorschlag
🍞 Lob / Abschluss
```
**Pitfalls:**
- Am Thema vorbeireden
- Bei Unklarheiten keine Fragen stellen
- Subjektives Feedback ("Farbe gefällt mir nicht")
**Konkret, sachlich, umsetzbar.**
---
# Prüfungsleistung Recap
**Grundanforderungen (75 Punkte):**
| Punkte | Kriterium |
|--------|-----------|
| 20 | Idee, Konzeption, Planung |
| 5 | Plattformunabhängigkeit |
| 25 | Clean Code (KISS, SOLID, DI, Testing, Error Handling) |
| 15 | Präsentation |
| 10 | Dokumentation (README, etc.) |
**Zusatzpunkte (max. 10):** TypeScript, Docker, Dev/Prod-Parity, `.env`, npm-Publish, Domain, HTTPS, Responsive Design.
→ https://git.dailysh.it/DHBW/pruefungsleistung
---
# Abgabe + Termine
- **Code-Upload:** bis 27.07.
- **Präsentation:** 17.07. (in Gruppen, ~10 Min)
- Pitch (~1 Min)
- Agenda
- Idee / Problem
- Learnings & Pitfalls
- "It is what it is" Doku
---
# 70 Things I Regret as a Developer (Auswahl)
- Nicht früher Tests geschrieben zu haben
- Zu viel Zeit mit "perfektem" Setup statt Code
- Nicht öfter `git commit`
- Nicht früher in Pull Requests reviewt
- Keine Doku gepflegt
- Eigene Lösungen für Standard-Probleme
- Frameworks gehyped, Grundlagen vernachlässigt
- "Funktioniert bei mir" akzeptiert
→ Lehre: **Disziplin > Talent.**
---
# Weiterlesen
- https://12factor.net/
- https://semver.org/
- https://udacity.github.io/git-styleguide/
- https://www.conventionalcommits.org/
- https://refactoring.guru/
- https://martinfowler.com/
- MDN Web Docs · web.dev · web.dev/learn
---
<!-- _class: invert -->
<!-- _backgroundColor: #001520 -->
# Viel Erfolg mit den Projekten!
Fragen? Probleme? → Telegram, Mail, Sprechstunde.