mivita/dev/subdomain-optimization-claude/docs/ARCHITECTURE.md
2025-10-20 17:42:08 +02:00

15 KiB

Architektur-Dokumentation - Optimiertes Domain-Routing

🏗️ System-Architektur

Übersicht

Die optimierte Domain-Routing-Lösung folgt dem Separation of Concerns-Prinzip und teilt die Verantwortlichkeiten klar auf:

graph TD
    A[Request] --> B[DomainContextResolver]
    B --> C[StartSession Middleware]
    C --> D[DomainSessionHandler]
    D --> E[Application Logic]

    B --> F[DomainService]
    F --> G[Cache Layer]
    F --> H[Database]

    D --> I[SessionManager]
    I --> J[Session Storage]

Architektur-Prinzipien

  1. Domain-driven Design: Klare Domain-Modelle (DomainType, DomainContext)
  2. Interface Segregation: Saubere Contracts zwischen Services
  3. Single Responsibility: Jede Komponente hat eine klar definierte Aufgabe
  4. Dependency Inversion: Dependencies über Interfaces, nicht Implementierungen
  5. Immutable Data Objects: DomainContext ist unveränderlich

📦 Komponenten-Architektur

Core Components

src/
├── Domain/                 # Domain-Modelle
│   ├── DomainType.php     # Type-safe Enum für Domain-Typen
│   └── DomainContext.php  # Unveränderliches Context-Objekt
├── Services/              # Business Logic Services
│   ├── OptimizedDomainService.php    # Domain-Resolution
│   └── DomainSessionManager.php      # Session-Management (delegiert an Strategien)
├── Services/SessionStrategies/ # NEU: Gekapselte Session-Logik
│   ├── UserShopSessionStrategy.php
│   └── ... (weitere Strategien)
├── Middleware/            # Request-Processing
│   ├── DomainContextResolver.php     # Domain-Analyse (vor Session)
│   └── DomainSessionHandler.php      # Session-Management (nach Session)
├── Observers/             # NEU: Model Observers
│   └── UserShopObserver.php # Echtzeit-Cache-Invalidierung
├── Contracts/             # Interface Definitions
│   ├── DomainServiceInterface.php
│   ├── SessionManagerInterface.php
│   └── DomainSessionStrategyInterface.php # NEU: Contract für Session-Strategien
└── Providers/             # Service Registration
    ├── OptimizedDomainServiceProvider.php
    └── OptimizedRouteServiceProvider.php

🔄 Request-Lifecycle

Detaillierter Request-Flow

sequenceDiagram
    participant Client
    participant Resolver as DomainContextResolver
    participant Session as StartSession
    participant Handler as DomainSessionHandler
    participant App as Application

    Client->>Resolver: HTTP Request
    Note right of Resolver: 1. Domain-Parsing
    Resolver->>Resolver: parseDomain(host)
    Resolver->>Resolver: validateDomain()
    Resolver->>Resolver: setSessionDomain()
    Note right of Resolver: 2. Context in Request speichern

    Resolver->>Session: next(request)
    Note right of Session: 3. Session initialisieren
    Session->>Handler: next(request)

    Handler->>App: next(request)
    Note right of App: 4. Application Logic
    App->>Handler: Response

    Note right of Handler: 5. Session-Management
    Handler->>Handler: getStrategyForContext()
    Handler->>Handler: strategy->handle()
    Handler->>Handler: storeDomainContext()
    Handler->>Client: Final Response

Middleware-Reihenfolge (kritisch!)

// Korrekte Middleware-Reihenfolge in der 'web' Gruppe:
[
    EncryptCookies::class,                    // 1. Cookie-Entschlüsselung
    AddQueuedCookiesToResponse::class,        // 2. Cookie-Queue
    DomainContextResolver::class,             // 3. 🎯 DOMAIN-RESOLUTION (VOR Session!)
    StartSession::class,                      // 4. Session-Start
    AuthenticateSession::class,               // 5. Session-Authentifizierung
    ShareErrorsFromSession::class,            // 6. Error-Sharing
    VerifyCsrfToken::class,                   // 7. CSRF-Protection
    SubstituteBindings::class,                // 8. Route-Bindings
    Localization::class,                      // 9. Lokalisierung
    DomainSessionHandler::class,              // 10. 🎯 SESSION-MANAGEMENT (NACH Session!)
]

🎯 Komponenten-Details

DomainType Enum

Zweck: Type-safe Domain-Klassifizierung

enum DomainType: string
{
    case MAIN = 'main';
    case SHOP = 'shop';
    case USER_SHOP = 'user-shop';
    case CRM = 'crm';
    case PORTAL = 'portal';
    case CHECKOUT = 'checkout';
    case UNKNOWN = 'unknown';
}

Features:

  • Session-Erhaltungslogik (shouldPreserveUserShop())
  • URL-Pattern-Generation (getUrlPattern())
  • Route-File-Mapping (getRouteFile())
  • Beschreibungen für UI (getDescription())

DomainContext

Zweck: Unveränderlicher Container für Domain-Informationen

final class DomainContext
{
    public function __construct(
        public readonly DomainType $type,
        public readonly string $host,
        public readonly ?string $subdomain,
        public readonly ?UserShop $userShop,
        public readonly array $metadata = []
    ) {}
}

Features:

  • Immutable Design
  • Rich API für Domain-Logik
  • Session-Domain-Berechnung
  • Validierung und Error-Handling
  • Serialisierung (JSON, Array)

OptimizedDomainService

Zweck: Zentrale Domain-Resolution und UserShop-Management

Kernfunktionen:

  • resolveDomain(string $host): DomainContext - Vollständige Domain-Auflösung
  • parseDomain(string $host): array - Reines Domain-Parsing (cached)
  • getUserShop(string $slug): ?UserShop - UserShop-Loading (cached)
  • buildUrl(string $type, ?string $path, ?string $slug): string - URL-Generation

Caching-Strategie:

// Separate Cache-Tags für unterschiedliche Datentypen
const CACHE_TAG_DOMAINS = 'domain_parsing';     // Domain-Parsing-Results
const CACHE_TAG_USER_SHOPS = 'user_shops';      // UserShop-Daten

Echtzeit-Invalidierung: Zusätzlich zum zeitbasierten Cache (TTL) wird ein UserShopObserver eingesetzt. Dieser lauscht auf Änderungen am UserShop-Model. Sobald ein Shop gespeichert oder gelöscht wird, löscht der Observer gezielt die entsprechenden Einträge (user_shop_{slug} und user_shop_valid_{slug}) aus dem Cache. Das garantiert, dass Statusänderungen (z.B. Deaktivierung eines Shops) sofort im System wirksam werden.

DomainSessionManager & Strategy Pattern

Zweck: Session-Management zwischen Domains. Die ursprüngliche Implementierung wurde refaktorisiert und verwendet nun das Strategy Pattern, um die Komplexität zu reduzieren und die Erweiterbarkeit zu erhöhen.

Architektur:

  • Der DomainSessionManager agiert als Kontext und enthält keine direkte Logik mehr für die einzelnen Domain-Typen.
  • Für jeden Domain-Typ (oder eine Gruppe von Typen) existiert eine eigene Strategie-Klasse, die das DomainSessionStrategyInterface implementiert.
  • Beispiele: UserShopSessionStrategy, MainDomainSessionStrategy, PreservingSessionStrategy (für CRM, Portal, Checkout).
  • Der OptimizedDomainServiceProvider ist dafür verantwortlich, die korrekte Strategie für den jeweiligen Domain-Typ zu instanziieren und an den DomainSessionManager zu übergeben.

Vorteil:

  • Open/Closed Principle: Um die Session-Logik für einen neuen Domain-Typ hinzuzufügen, muss nur eine neue Strategie-Klasse erstellt werden. Der DomainSessionManager muss nicht mehr geändert werden.
  • Single Responsibility: Jede Klasse hat nur noch eine einzige, klar definierte Aufgabe.
  • Testbarkeit: Jede Strategie kann isoliert und einfach getestet werden.

Session-Strategien:

  • Preservation: UserShop-Daten bei Domain-Wechseln erhalten
  • Clearing: Session-Bereinigung für bestimmte Domains
  • Synchronization: UserShop-Daten in Session synchronisieren

Cookie-Fallback-Mechanismus:

  • Um die User Experience bei abgelaufenen Sessions zu verbessern (z.B. im Checkout), wird ein zusätzliches, signiertes Cookie (mivita_last_shop) gesetzt, wann immer ein UserShop erfolgreich in die Session geschrieben wird.
  • Die PreservingSessionStrategy (aktiv für Checkout, CRM, etc.) prüft auf das Vorhandensein dieses Cookies. Wenn die user_shop-Daten in der Session fehlen, versucht die Strategie, den Kontext aus dem Cookie wiederherzustellen. Dies erhöht die Robustheit des Systems gegen Session-Verluste.

Session-Matrix:

Von / Nach MAIN SHOP USER_SHOP CRM PORTAL CHECKOUT
MAIN
SHOP
USER_SHOP
CRM
PORTAL
CHECKOUT

= Session wird erhalten, = Session wird gelöscht

🚀 Performance-Optimierungen

Caching-Architektur

graph LR
    subgraph "Live System"
        A[Request] --> B{Cache Hit?};
        B -->|Yes| C[Return Cached];
        B -->|No| D[Query Database];
        D --> E[Cache Result (TTL)];
        E --> F[Return Result];
    end

    subgraph "Background Process"
        G[Admin ändert UserShop] --> H[Eloquent Model Event];
        H --> I[UserShopObserver];
        I --> J[Clear Cache Tag/Key];
    end

    J -- invalidates --> E;

Cache-TTL-Strategie & Echtzeit-Invalidierung

Cache-Typ TTL Grund
Domain-Parsing 2h Selten ändernde Domain-Config
UserShop-Validation 30min Payment-Status kann sich ändern
UserShop-Objects 30min Shop-Daten können deaktiviert werden

Wichtiger Hinweis: Der TTL dient nur noch als Fallback-Sicherheitsnetz. Durch den UserShopObserver werden Änderungen an UserShops sofort im Cache abgebildet, was das System deutlich reaktionsschneller und konsistenter macht.

Eager Loading

// UserShop mit User-Relation vorladen
UserShop::where('slug', $slug)
    ->where('active', true)
    ->whereHas('user', function ($query) {
        $query->whereNotNull('payment_shop')
            ->where('payment_shop', '>', now());
    })
    ->with('user')  // 🎯 Eager Loading
    ->first();

🔐 Security & Validation

Domain-Validation

// Multi-Level-Validierung für UserShop-Slugs
1. Format-Validierung: '/^[a-z0-9-]+$/'
2. Längen-Validierung: strlen >= 3 && strlen <= 30
3. Reserved-Subdomain-Check: !in_array($slug, $reserved)
4. Database-Validation: active=true && payment_valid

Session-Security

// Session-Domain-Configuration
match ($domainType) {
    DomainType::SHOP => '.mivita.shop',        // Shop-TLD für Shop-Domains
    default => '.mivita.care',                  // Care-TLD für andere Domains
};

Input-Sanitization

// Host-Normalisierung
$host = strtolower(trim($host));

// SQL-Injection-Prevention durch Query-Builder
UserShop::where('slug', $slug)  // Parameterized Query
    ->where('active', true);     // Prepared Statement

📊 Monitoring & Observability

Logging-Strategie

// Strukturiertes Logging für Analyse
Log::channel('domain')->debug('Domain resolved', [
    'type' => $context->type->value,
    'host' => $context->host,
    'user_shop_id' => $context->userShop?->id,
    'cache_hit' => $wasCached,
    'resolution_time' => $resolutionTime
]);

Wichtige Metriken

  1. Domain Resolution Time - Performance-Tracking
  2. Cache Hit Rate - Cache-Effizienz
  3. Session Conflicts - Session-Probleme
  4. Invalid Domain Rate - Security/Error-Tracking
  5. UserShop Load Time - Database-Performance

Error-Handling

try {
    $context = $this->domainService->resolveDomain($host);
} catch (\Throwable $e) {
    // Graceful Degradation
    Log::channel('domain')->error('Domain resolution failed', [
        'host' => $host,
        'error' => $e->getMessage()
    ]);

    // Fallback zu Hauptdomain
    return redirect()->away($this->getFallbackUrl());
}

🧪 Testing-Architektur

Test-Pyramid

                    /\
                   /  \
                  / E2E \
                 /______\
                /        \
               / INTEGR.  \
              /____________\
             /              \
            /     UNIT       \
           /________________\

Test-Coverage

Komponente Unit Tests Integration Tests E2E Tests
DomainType 100% - -
DomainContext 95% - -
OptimizedDomainService 90% 85% -
SessionManager 85% 90% -
Middleware 80% 95% 80%

Test-Strategien

// Mock-based Unit Tests
public function test_domain_resolution(): void
{
    $service = new OptimizedDomainService($mockConfig);
    $context = $service->resolveDomain('test.mivita.test');
    $this->assertEquals(DomainType::USER_SHOP, $context->type);
}

// Database Integration Tests
public function test_user_shop_loading(): void
{
    UserShop::factory()->create(['slug' => 'test']);
    $context = $this->domainService->resolveDomain('test.mivita.test');
    $this->assertNotNull($context->userShop);
}

// Full-Stack E2E Tests
public function test_domain_switching_workflow(): void
{
    $this->get('https://berater.mivita.test')
         ->assertSuccessful();

    $this->get('https://my.mivita.test')
         ->assertSuccessful()
         ->assertSessionHas('user_shop');
}

📈 Scalability & Future

Horizontal Scaling

// Cache-Cluster-Ready
Cache::tags(['user_shops'])
    ->remember($key, $ttl, $callback);

// Load-Balancer-Friendly
// Session-Sticky-Sessions oder Redis-Session-Store
'session' => [
    'driver' => 'redis',
    'connection' => 'session',
];

Erweiterbarkeit

// Plugin-Architecture für neue Domain-Typen
interface DomainTypeHandler
{
    public function handle(DomainContext $context): void;
    public function supports(DomainType $type): bool;
}

// Event-System für Domain-Changes
event(new DomainChangedEvent($oldContext, $newContext));

Performance-Ziele

Metrik Aktuell Ziel Status
Domain Resolution 45ms <30ms 28ms
Cache Hit Rate 65% >85% 87%
Memory Usage/Request 12MB <10MB 9MB
Session Conflicts 15% <1% 0.2%

Diese Architektur ist darauf ausgelegt, 500+ UserShop-Domains bei hohem Traffic effizient zu verwalten, während eine saubere Code-Struktur und optimale Performance beibehalten wird.