diff --git a/Makefile b/Makefile index ba47421..175cd44 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/slides/dhbw/01_web_eng.md b/slides/dhbw/01_web_eng.md index fc7a5db..50feaf0 100644 --- a/slides/dhbw/01_web_eng.md +++ b/slides/dhbw/01_web_eng.md @@ -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 + + +``` + +✓ 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 ( + + ); +} +``` + +- **Component-Based** Architecture +- **Virtual DOM**, JSX, Hooks +- großes Ökosystem (React Router, Redux, …) + +--- + +# Vue 3 – Composition API + +```vue + + + +``` + +- **Reactive Data Binding** +- Single-File Components (`.vue`) +- Vue Router, Pinia (State) + +--- + +# Svelte – Counter + +```svelte + + + +``` + +- **Compiler-Based** – kein Runtime-Framework +- Reactive Assignments (`let`) +- Built-in Animationen + +--- + +# Astro – Content-First + +```astro +--- +const greeting = "Hallo DHBW"; +--- + + +

{greeting}

+ + + +``` + +- **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? diff --git a/slides/dhbw/02_css_extended.md b/slides/dhbw/02_css_extended.md index fa5f78d..0cd9185 100644 --- a/slides/dhbw/02_css_extended.md +++ b/slides/dhbw/02_css_extended.md @@ -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" --- + + + + + +# 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 +``` + +--- + + + + +# Fragen? + +**Hausaufgabe:** Eigene Projektidee mit mind. 1 Unit + 1 Integration Test starten. diff --git a/slides/dhbw/06_typescript.md b/slides/dhbw/06_typescript.md new file mode 100644 index 0000000..a19d88a --- /dev/null +++ b/slides/dhbw/06_typescript.md @@ -0,0 +1,422 @@ +--- +marp: true +theme: gaia +paginate: true +backgroundColor: #fff +header: "Web Engineering – DHBW Stuttgart" +footer: "Michael Czechowski – SoSe 2026" +title: TypeScript +--- + + + + + + + +# 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 { + private readonly value: T; + + constructor(value: T) { + this.value = value; + } + + getValue(): T { + return this.value; + } +} + +const numbers = new GenericContainer(10); +const strings = new GenericContainer("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 = (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. + +--- + + + + +# 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 = (target: T) => T | void; + +type PropertyDecorator = ( + target: Object, + propertyKey: string | symbol +) => void; + +type MethodDecorator = ( + target: Object, + propertyKey: string | symbol, + descriptor: TypedPropertyDescriptor +) => TypedPropertyDescriptor | 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. + +--- + + + + +# Fragen? + +**Hausaufgabe:** Bestehendes JS-Projekt auf TS migrieren – mind. 1 File + `tsconfig.json`. diff --git a/slides/dhbw/07_docker.md b/slides/dhbw/07_docker.md new file mode 100644 index 0000000..4008799 --- /dev/null +++ b/slides/dhbw/07_docker.md @@ -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 +--- + + + + + + + +# 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 + +--- + + + + +# Fragen? + +**Hausaufgabe:** Eigenes Projekt mit `Dockerfile` + `compose.yml` containerisieren. diff --git a/slides/dhbw/08_best_practices.md b/slides/dhbw/08_best_practices.md new file mode 100644 index 0000000..f412871 --- /dev/null +++ b/slides/dhbw/08_best_practices.md @@ -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 +--- + + + + + + + +# 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 + +``` +: + + + +