543 lines
15 KiB
Markdown
543 lines
15 KiB
Markdown
# GraphQL Middlelayer + Astro Frontend
|
|
|
|
Ein moderner, skalierbarer Onlineshop mit GraphQL-basiertem Middlelayer und Astro-Frontend. Der Middlelayer fungiert als flexible Abstraktionsschicht zwischen Frontend und verschiedenen Datenquellen.
|
|
|
|
## 🚀 Features
|
|
|
|
- **GraphQL API** mit Apollo Server v5
|
|
- **Astro Frontend** mit SSR und Client-Side Interaktivität
|
|
- **Adapter Pattern** für flexible Datenquellen (Mock, Headless CMS, Database)
|
|
- **Performance-Optimierungen**:
|
|
- Dataloader für Batch-Loading (verhindert N+1 Queries)
|
|
- Redis/In-Memory Caching
|
|
- Response Caching
|
|
- Query Complexity Limits
|
|
- **Monitoring & Observability**:
|
|
- Structured Logging (Winston)
|
|
- Prometheus Metrics
|
|
- Distributed Tracing
|
|
- **Type-Safe** mit TypeScript durchgängig
|
|
- **Modern UI** mit Tailwind CSS und Alpine.js
|
|
- **Internationalization (i18n)**:
|
|
- URL-basierte Locales (`/de`, `/en`)
|
|
- Übersetzungen aus Middlelayer (mit Default-Fallback)
|
|
- CMS-Inhalte mehrsprachig (vorbereitet für Contentful)
|
|
|
|
## 📋 Voraussetzungen
|
|
|
|
- Node.js 18+
|
|
- npm oder yarn
|
|
- (Optional) Redis für verteiltes Caching
|
|
|
|
## 🛠️ Installation
|
|
|
|
```bash
|
|
# Dependencies installieren
|
|
npm install
|
|
|
|
# (Optional) Redis installieren (macOS)
|
|
brew install redis
|
|
brew services start redis
|
|
```
|
|
|
|
## 🏃 Quick Start
|
|
|
|
```bash
|
|
# GraphQL Server + Astro Frontend starten
|
|
npm start
|
|
|
|
# Oder einzeln:
|
|
npm run mock:server # Nur GraphQL Server (Port 4000)
|
|
npm run dev # Nur Astro Frontend (Port 4321)
|
|
```
|
|
|
|
## 📁 Projektstruktur
|
|
|
|
```
|
|
/
|
|
├── middlelayer/ # GraphQL Middlelayer
|
|
│ ├── __cms/ # Legacy Contentful-Typen (nicht mehr verwendet)
|
|
│ ├── adapters/ # Datenquellen-Adapter (Mock, CMS, etc.)
|
|
│ │ ├── Mock/ # Mock-Adapter mit Mock-Daten
|
|
│ │ │ ├── _cms/ # CMS Mock-Daten
|
|
│ │ │ └── _i18n/ # i18n Mock-Daten
|
|
│ │ ├── config.ts # Adapter-Konfiguration
|
|
│ │ └── interface.ts # DataAdapter Interface
|
|
│ ├── auth/ # Authentication & Authorization
|
|
│ ├── config/ # Konfiguration (Cache, etc.)
|
|
│ ├── mappers/ # Data Mapper (CMS → Domain)
|
|
│ │ └── pageMapper.ts # Page-Transformationen
|
|
│ ├── monitoring/ # Logging, Metrics, Tracing
|
|
│ ├── plugins/ # Apollo Server Plugins
|
|
│ ├── types/ # TypeScript Typen
|
|
│ │ ├── cms/ # CMS-spezifische Typen (für Mapper)
|
|
│ │ ├── c_*.ts # Domain Content-Item-Typen
|
|
│ │ └── *.ts # Weitere Domain-Typen
|
|
│ ├── utils/ # Utilities
|
|
│ │ ├── cache.ts # Cache-Implementierung
|
|
│ │ ├── cacheKeys.ts # Cache-Key-Builder
|
|
│ │ ├── dataServiceHelpers.ts # Helper für DataService
|
|
│ │ ├── dataloaders.ts # Dataloader + GraphQLContext
|
|
│ │ └── errors.ts # Error-Klassen
|
|
│ ├── dataService.ts # DataService (Singleton)
|
|
│ ├── index.ts # Server Entry Point
|
|
│ ├── schema.ts # GraphQL Schema
|
|
│ └── resolvers.ts # GraphQL Resolvers
|
|
│
|
|
├── src/ # Astro Frontend
|
|
│ ├── components/ # Astro Komponenten
|
|
│ │ ├── cms/ # CMS Content-Komponenten
|
|
│ │ └── ... # Weitere Komponenten
|
|
│ ├── layouts/ # Layout Templates
|
|
│ ├── lib/ # Utilities & GraphQL Client
|
|
│ │ ├── graphql/ # GraphQL Client & Queries
|
|
│ │ ├── i18n/ # i18n-System
|
|
│ │ └── utils/ # Utilities (Layout, Markdown, etc.)
|
|
│ ├── pages/ # Astro Pages
|
|
│ │ └── [locale]/ # Locale-basierte Routen
|
|
│ └── styles/ # Global Styles
|
|
│
|
|
└── docs/ # Dokumentation
|
|
```
|
|
|
|
## ⚙️ Konfiguration
|
|
|
|
### Environment Variables
|
|
|
|
Erstelle eine `.env` Datei im Root-Verzeichnis:
|
|
|
|
```env
|
|
# Server Ports
|
|
PORT=4000 # GraphQL Server Port
|
|
METRICS_PORT=9090 # Metrics Server Port
|
|
|
|
# Logging
|
|
LOG_LEVEL=info # debug, info, warn, error
|
|
NODE_ENV=development # development, production
|
|
|
|
# GraphQL
|
|
MAX_QUERY_COMPLEXITY=1000 # Query Complexity Limit
|
|
|
|
# Redis (Optional)
|
|
REDIS_ENABLED=true # Redis Cache aktivieren
|
|
REDIS_HOST=localhost # Redis Host
|
|
REDIS_PORT=6379 # Redis Port
|
|
REDIS_PASSWORD= # Optional: Redis Password
|
|
|
|
# Cache TTLs (in Millisekunden)
|
|
CACHE_PAGES_TTL=60000 # 60 Sekunden
|
|
CACHE_PAGE_SEO_TTL=300000 # 5 Minuten
|
|
CACHE_NAVIGATION_TTL=300000 # 5 Minuten
|
|
CACHE_PRODUCTS_TTL=30000 # 30 Sekunden
|
|
```
|
|
|
|
### Cache-Konfiguration
|
|
|
|
Cache-TTLs können über Environment Variables oder direkt in `middlelayer/config/cache.ts` konfiguriert werden.
|
|
|
|
## 🌐 Endpoints
|
|
|
|
| Service | URL | Beschreibung |
|
|
|---------|-----|--------------|
|
|
| GraphQL API | `http://localhost:4000` | GraphQL Endpoint & Playground |
|
|
| Metrics | `http://localhost:9090/metrics` | Prometheus Metrics |
|
|
| Health Check | `http://localhost:9090/health` | Service Health Status |
|
|
| Astro Frontend | `http://localhost:4321` | Frontend Application |
|
|
|
|
## 📊 GraphQL Schema
|
|
|
|
### Queries
|
|
|
|
```graphql
|
|
# Produkte
|
|
query {
|
|
products(limit: 4) {
|
|
id
|
|
name
|
|
price
|
|
originalPrice
|
|
promotion {
|
|
category
|
|
text
|
|
}
|
|
}
|
|
|
|
product(id: "prod-123") {
|
|
id
|
|
name
|
|
description
|
|
price
|
|
}
|
|
}
|
|
|
|
# CMS (mit Locale-Support)
|
|
query {
|
|
pageSeo(locale: "de") {
|
|
title
|
|
description
|
|
}
|
|
|
|
page(slug: "/about", locale: "de") {
|
|
headline
|
|
subheadline
|
|
}
|
|
|
|
pages(locale: "en") {
|
|
slug
|
|
name
|
|
}
|
|
|
|
navigation(locale: "de") {
|
|
links {
|
|
name
|
|
slug
|
|
}
|
|
}
|
|
}
|
|
|
|
# Übersetzungen
|
|
query {
|
|
translations(locale: "de", namespace: "auth") {
|
|
locale
|
|
translations {
|
|
key
|
|
value
|
|
}
|
|
}
|
|
}
|
|
|
|
# Content-System (mit Content-Items)
|
|
query {
|
|
page(slug: "/components", locale: "de") {
|
|
slug
|
|
name
|
|
headline
|
|
row1 {
|
|
justifyContent
|
|
alignItems
|
|
content {
|
|
... on HTMLContent {
|
|
type
|
|
name
|
|
html
|
|
}
|
|
... on MarkdownContent {
|
|
type
|
|
name
|
|
content
|
|
alignment
|
|
}
|
|
... on HeadlineContent {
|
|
type
|
|
text
|
|
tag
|
|
align
|
|
}
|
|
... on ImageContent {
|
|
type
|
|
name
|
|
imageUrl
|
|
caption
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## 🏗️ Architektur
|
|
|
|
### Middlelayer
|
|
|
|
Der Middlelayer verwendet das **Adapter Pattern** für flexible Datenquellen:
|
|
|
|
```
|
|
Frontend (Astro)
|
|
↓
|
|
GraphQL API (Apollo Server)
|
|
↓
|
|
Resolvers (mit Error Handling Wrapper)
|
|
↓
|
|
DataService (Singleton, mit Cache + Metrics)
|
|
↓
|
|
PageMapper (CMS → Domain Transformation)
|
|
↓
|
|
DataAdapter (Interface)
|
|
↓
|
|
┌─────────────┬──────────────┬─────────────┐
|
|
│ Mock Adapter│ CMS Adapter │ DB Adapter │
|
|
└─────────────┴──────────────┴─────────────┘
|
|
```
|
|
|
|
**Vorteile:**
|
|
- Einfaches Wechseln zwischen Datenquellen
|
|
- Testbarkeit durch Mock-Adapter
|
|
- Zentrale Logik im DataService
|
|
- Caching auf Service-Ebene
|
|
- Type-safe Mapping von CMS zu Domain-Typen
|
|
- Konsistentes Error Handling
|
|
- Zentralisierte Cache-Keys
|
|
- Strategy Pattern für wartbaren Code
|
|
|
|
### Content-System
|
|
|
|
Das System verwendet eine **Zwei-Schichten-Typ-Architektur**:
|
|
|
|
1. **CMS-Typen** (`types/cms/`) - Wie Daten vom CMS kommen
|
|
- `*Skeleton` Wrapper mit `contentTypeId` und `fields`
|
|
- Verwendet `ComponentLayout` (Alias für `contentLayout`)
|
|
- Nur für Mapper und Mock-Daten
|
|
|
|
2. **Domain-Typen** (`types/c_*.ts`) - Wie Daten in der App verwendet werden
|
|
- `c_*` Content-Item-Typen mit `type`-Feld
|
|
- Verwendet `contentLayout` direkt
|
|
- Für GraphQL Schema, Astro Components, etc.
|
|
|
|
3. **PageMapper** - Konvertiert CMS-Typen zu Domain-Typen
|
|
- Strategy Pattern für wartbaren Code
|
|
- Automatische Transformation aller Content-Types
|
|
|
|
**Content-Komponenten:**
|
|
- HTML, Markdown, Iframe, ImageGallery, Image, Quote, YoutubeVideo, Headline
|
|
|
|
### Frontend
|
|
|
|
- **Astro** für Server-Side Rendering
|
|
- **Alpine.js** für Client-Side Interaktivität
|
|
- **Tailwind CSS** für Styling
|
|
- **GraphQL Client** für Datenabfragen
|
|
|
|
## 🔧 Entwicklung
|
|
|
|
### NPM Scripts
|
|
|
|
```bash
|
|
npm run mock:server # Startet GraphQL Server
|
|
npm run dev # Startet Astro Dev Server
|
|
npm start # Startet beide (concurrently)
|
|
npm run build # Production Build
|
|
npm run preview # Preview Production Build
|
|
```
|
|
|
|
### Neuen Adapter hinzufügen
|
|
|
|
1. Implementiere `DataAdapter` Interface:
|
|
```typescript
|
|
// middlelayer/adapters/myAdapter.ts
|
|
import type { DataAdapter } from './interface.js';
|
|
import type { Page, Product, PageSeo, Navigation } from '../types/index.js';
|
|
|
|
export class MyAdapter implements DataAdapter {
|
|
async getProducts(limit?: number): Promise<Product[]> {
|
|
// Implementierung
|
|
}
|
|
async getPage(slug: string, locale?: string): Promise<Page | null> {
|
|
// Implementierung - muss CMS-Typen zurückgeben
|
|
// PageMapper konvertiert automatisch zu Domain-Typen
|
|
}
|
|
// ... weitere Methoden
|
|
}
|
|
```
|
|
|
|
2. In `middlelayer/adapters/config.ts` registrieren:
|
|
```typescript
|
|
export function createAdapter(): DataAdapter {
|
|
const adapterType = process.env.ADAPTER_TYPE || 'mock';
|
|
|
|
if (adapterType === 'myAdapter') {
|
|
return new MyAdapter();
|
|
}
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### Content-Komponenten hinzufügen
|
|
|
|
1. CMS-Typ erstellen: `middlelayer/types/cms/MyComponent.ts`
|
|
2. Domain-Typ erstellen: `middlelayer/types/c_myComponent.ts`
|
|
3. Mapper erweitern: In `pageMapper.ts` Strategy Map erweitern
|
|
4. GraphQL Schema erweitern: In `schema.ts` neuen Type hinzufügen
|
|
5. Astro Component erstellen: `src/components/cms/C_myComponent.astro`
|
|
|
|
## 📈 Monitoring
|
|
|
|
### Prometheus Metrics
|
|
|
|
Metriken sind verfügbar unter `http://localhost:9090/metrics`:
|
|
|
|
- `graphql_queries_total` - Anzahl der Queries
|
|
- `graphql_query_duration_seconds` - Query-Dauer
|
|
- `cache_hits_total` / `cache_misses_total` - Cache-Statistiken
|
|
- `dataservice_calls_total` - DataService-Aufrufe
|
|
- `errors_total` - Fehler-Anzahl
|
|
|
|
### Logging
|
|
|
|
Structured Logging mit Winston:
|
|
- Console Output (Development)
|
|
- JSON Logs (Production)
|
|
- Log-Level konfigurierbar
|
|
|
|
### Tracing
|
|
|
|
Automatische Trace-ID-Generierung für Request-Tracking.
|
|
|
|
## 🚀 Deployment
|
|
|
|
### Production Build
|
|
|
|
```bash
|
|
npm run build
|
|
```
|
|
|
|
### Docker (Beispiel)
|
|
|
|
```dockerfile
|
|
FROM node:18-alpine
|
|
WORKDIR /app
|
|
COPY package*.json ./
|
|
RUN npm ci --production
|
|
COPY . .
|
|
RUN npm run build
|
|
CMD ["npm", "start"]
|
|
```
|
|
|
|
## 🧪 Testing
|
|
|
|
```bash
|
|
# GraphQL Query testen
|
|
curl -X POST http://localhost:4000 \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"query":"{ products(limit: 4) { id name price } }"}'
|
|
|
|
# Health Check
|
|
curl http://localhost:9090/health
|
|
|
|
# Metrics
|
|
curl http://localhost:9090/metrics
|
|
```
|
|
|
|
## 📚 Weitere Dokumentation
|
|
|
|
- [Type Structure](./middlelayer/types/README.md) - Detaillierte Typ-Struktur (CMS vs Domain)
|
|
- [Authentication](./middlelayer/auth/README.md) - Authentication & Authorization
|
|
- [Monitoring](./middlelayer/monitoring/README.md) - Monitoring & Observability
|
|
- [Redis Setup](./middlelayer/utils/REDIS_SETUP.md) - Redis Cache Konfiguration
|
|
- [Improvements](./middlelayer/IMPROVEMENTS.md) - Verbesserungsvorschläge & umgesetzte Änderungen
|
|
|
|
## 🐛 Bekannte Probleme
|
|
|
|
### Query Complexity Schema-Realm-Konflikt
|
|
|
|
**Problem:** `graphql-query-complexity` hat einen Schema-Realm-Konflikt mit Apollo Server.
|
|
|
|
**Workaround:** Das Plugin überspringt den Check bei Realm-Konflikten automatisch. Funktioniert, aber Complexity-Check wird manchmal übersprungen.
|
|
|
|
**Status:** Funktioniert, permanente Lösung in Arbeit.
|
|
|
|
## 🌍 Internationalization (i18n)
|
|
|
|
### URL-basierte Locales
|
|
|
|
Das System unterstützt URL-basierte Locales:
|
|
- `/de` - Deutsche Version
|
|
- `/en` - Englische Version
|
|
- `/` - Auto-Redirect zu Browser-Locale oder Cookie
|
|
|
|
### Übersetzungen
|
|
|
|
**Architektur:**
|
|
- **Defaults**: Fallback-Übersetzungen in `src/lib/i18n/defaults.ts`
|
|
- **Middlelayer**: Übersetzungen können vom GraphQL-Server geladen werden
|
|
- **Überschreibungen**: Middlelayer-Übersetzungen überschreiben Defaults
|
|
|
|
**Verwendung in React:**
|
|
```tsx
|
|
import { useI18n } from "../lib/i18n/useI18n.js";
|
|
|
|
function MyComponent() {
|
|
const { t } = useI18n("auth");
|
|
return <h1>{t("login.title")}</h1>;
|
|
}
|
|
```
|
|
|
|
**Verwendung in Alpine.js:**
|
|
```html
|
|
<div x-data="{ t: window.i18n?.t || ((key) => key) }">
|
|
<button x-text="t('nav.login')"></button>
|
|
</div>
|
|
```
|
|
|
|
### CMS-Locale-Support
|
|
|
|
**Contentful-Ansatz:**
|
|
Contentful verwendet ein Locale-System, bei dem:
|
|
- Jedes Content-Feld lokalisiert werden kann
|
|
- Standard-Locale wird definiert (z.B. `en-US`)
|
|
- Fallback-Locales können konfiguriert werden
|
|
- API-Aufrufe enthalten `locale`-Parameter: `fields.headline['en-US']`
|
|
|
|
**Unser System:**
|
|
- GraphQL-Schema unterstützt `locale`-Parameter für alle CMS-Queries
|
|
- Adapter-Interface ist vorbereitet für Locale-Parameter
|
|
- Mock-Adapter gibt aktuell alle Locales gleich zurück (TODO: Locale-spezifische Mock-Daten)
|
|
- Bei Contentful-Integration: Adapter würde `locale`-Parameter an Contentful API weitergeben
|
|
|
|
**Beispiel GraphQL Query:**
|
|
```graphql
|
|
query {
|
|
page(slug: "/about", locale: "de") {
|
|
headline # Deutsche Übersetzung
|
|
subheadline
|
|
}
|
|
|
|
page(slug: "/about", locale: "en") {
|
|
headline # Englische Übersetzung
|
|
subheadline
|
|
}
|
|
}
|
|
```
|
|
|
|
## ✨ Kürzlich implementierte Verbesserungen
|
|
|
|
- ✅ **Strategy Pattern** im PageMapper (statt 8 if-Statements)
|
|
- ✅ **Code-Duplikation reduziert** in DataService (Helper-Klassen)
|
|
- ✅ **Konsistentes Error Handling** (Wrapper-Funktion für alle Resolver)
|
|
- ✅ **Zentralisierte Cache-Keys** (CacheKeyBuilder)
|
|
- ✅ **Type Safety verbessert** (keine `any` Types mehr)
|
|
- ✅ **User-Formatierung zentralisiert** (Helper-Funktion)
|
|
|
|
## 🔮 Roadmap
|
|
|
|
- [ ] Query Complexity Schema-Realm-Problem dauerhaft lösen
|
|
- [ ] Database Adapter (PostgreSQL, MySQL)
|
|
- [ ] Headless CMS Adapter (Contentful, Strapi)
|
|
- [ ] Locale-spezifische Inhalte implementieren
|
|
- [ ] Fallback-Locales konfigurieren
|
|
- [ ] Rate Limiting
|
|
- [x] Authentication/Authorization ✅ (implementiert)
|
|
- [ ] GraphQL Subscriptions
|
|
- [ ] i18n: Weitere Sprachen hinzufügen
|
|
- [ ] i18n: Locale-spezifische Mock-Daten für CMS
|
|
- [ ] E2E Tests
|
|
|
|
## 🤝 Beitragen
|
|
|
|
1. Fork das Repository
|
|
2. Erstelle einen Feature Branch (`git checkout -b feature/AmazingFeature`)
|
|
3. Committe deine Änderungen (`git commit -m 'Add some AmazingFeature'`)
|
|
4. Push zum Branch (`git push origin feature/AmazingFeature`)
|
|
5. Öffne einen Pull Request
|
|
|
|
## 📝 Lizenz
|
|
|
|
Dieses Projekt ist privat.
|
|
|
|
## 👥 Autoren
|
|
|
|
- Entwickelt mit ❤️ für moderne E-Commerce-Lösungen
|
|
|
|
---
|
|
|
|
**Hinweis:** Dieses Projekt ist in aktiver Entwicklung. Für Fragen oder Probleme öffne bitte ein Issue.
|