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:
2
Makefile
2
Makefile
@@ -22,7 +22,7 @@ SLIDES_DIR = slides
|
|||||||
|
|
||||||
# DHBW course settings
|
# DHBW course settings
|
||||||
dhbw_NAME = Technik I – Grundlagen IT
|
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
|
# Deploy paths
|
||||||
HDM_DEPLOY_PATH = /home/tengo/html/hdm
|
HDM_DEPLOY_PATH = /home/tengo/html/hdm
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ theme: gaia
|
|||||||
paginate: true
|
paginate: true
|
||||||
backgroundColor: #fff
|
backgroundColor: #fff
|
||||||
header: "Web Engineering – DHBW Stuttgart"
|
header: "Web Engineering – DHBW Stuttgart"
|
||||||
footer: "Michael Czechowski – SoSe 2025"
|
footer: "Michael Czechowski – SoSe 2026"
|
||||||
title: Web Engineering
|
title: Web Engineering
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -98,11 +98,52 @@ a {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Prüfungsleistung
|
# Prüfungsleistung – Übersicht
|
||||||
|
|
||||||
**LN** – Lernnachweis (50%)
|
Eigenes Projekt: **Web-App** ODER **Backend** (API/CLI).
|
||||||
- Projekte / Hausaufgaben
|
|
||||||
- Präsentation
|
**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() {
|
|||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 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?
|
# Fragen?
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ theme: gaia
|
|||||||
paginate: true
|
paginate: true
|
||||||
backgroundColor: #fff
|
backgroundColor: #fff
|
||||||
header: "Web Engineering – DHBW Stuttgart"
|
header: "Web Engineering – DHBW Stuttgart"
|
||||||
footer: "Michael Czechowski – SoSe 2025"
|
footer: "Michael Czechowski – SoSe 2026"
|
||||||
---
|
---
|
||||||
|
|
||||||
<style>
|
<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?
|
# Fragen?
|
||||||
|
|
||||||
1. Was bedeutet `div > p` vs `div p`?
|
1. Was bedeutet `div > p` vs `div p`?
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ theme: gaia
|
|||||||
paginate: true
|
paginate: true
|
||||||
backgroundColor: #fff
|
backgroundColor: #fff
|
||||||
header: "Web Engineering – DHBW Stuttgart"
|
header: "Web Engineering – DHBW Stuttgart"
|
||||||
footer: "Michael Czechowski – SoSe 2025"
|
footer: "Michael Czechowski – SoSe 2026"
|
||||||
---
|
---
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ theme: gaia
|
|||||||
paginate: true
|
paginate: true
|
||||||
backgroundColor: #fff
|
backgroundColor: #fff
|
||||||
header: "Web Engineering – DHBW Stuttgart"
|
header: "Web Engineering – DHBW Stuttgart"
|
||||||
footer: "Michael Czechowski – SoSe 2025"
|
footer: "Michael Czechowski – SoSe 2026"
|
||||||
---
|
---
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
356
slides/dhbw/05_testing.md
Normal file
356
slides/dhbw/05_testing.md
Normal 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.
|
||||||
422
slides/dhbw/06_typescript.md
Normal file
422
slides/dhbw/06_typescript.md
Normal 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
338
slides/dhbw/07_docker.md
Normal 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.
|
||||||
346
slides/dhbw/08_best_practices.md
Normal file
346
slides/dhbw/08_best_practices.md
Normal 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.
|
||||||
Reference in New Issue
Block a user