feat: CI/CD pipeline setup complete - Ansible playbooks updated, secrets configured, workflow ready

This commit is contained in:
2025-10-31 01:39:24 +01:00
parent 55c04e4fd0
commit e26eb2aa12
601 changed files with 44184 additions and 32477 deletions

View File

@@ -464,23 +464,937 @@ $schema->table('users', function (Blueprint $table) {
## EntityManager Usage
TODO: Document EntityManager and UnitOfWork pattern
### Overview
Der EntityManager ist das zentrale Interface für alle Datenbank-Operationen. Er kombiniert mehrere Patterns:
- **Unit of Work**: Change Tracking und Transaction Management
- **Identity Map**: Vermeidung von Duplikaten
- **Lazy Loading**: Performance-Optimierung durch verzögertes Laden
- **Batch Loading**: N+1 Query Prevention
### Core Features
**Service Classes** (interne Organisation):
- `EntityFinder`: Entity-Suche und Lazy Loading
- `EntityPersister`: Insert, Update, Delete Operationen
- `EntityQueryManager`: QueryBuilder Integration
- `EntityUtilities`: Hilfsmethoden und Profiling
### Basic Operations
#### Finding Entities
```php
use App\Framework\Database\EntityManager;
final readonly class UserService
{
public function __construct(
private EntityManager $entityManager
) {}
public function getUserById(UserId $id): ?User
{
// Lazy Loading (Standard)
return $this->entityManager->find(User::class, $id->value);
}
public function getUserByIdEager(UserId $id): ?User
{
// Eager Loading - lädt Relations sofort
return $this->entityManager->findEager(User::class, $id->value);
}
public function getAllUsers(): array
{
return $this->entityManager->findAll(User::class);
}
public function findUserByEmail(Email $email): ?User
{
return $this->entityManager->findOneBy(
User::class,
['email' => $email->value]
);
}
}
```
#### Saving Entities
```php
public function createUser(CreateUserCommand $command): User
{
$user = User::create(
id: $this->entityManager->generateId(),
email: $command->email,
name: $command->name
);
// save() erkennt automatisch INSERT vs UPDATE
return $this->entityManager->save($user);
}
public function updateUser(User $user, UpdateUserCommand $command): User
{
// Explizites UPDATE
$user->updateProfile($command->name);
return $this->entityManager->update($user);
}
```
#### Batch Operations
```php
public function createMultipleUsers(array $commands): array
{
$users = [];
foreach ($commands as $command) {
$users[] = User::create(
id: $this->entityManager->generateId(),
email: $command->email,
name: $command->name
);
}
// Batch-Insert für bessere Performance
return $this->entityManager->saveAll(...$users);
}
```
#### Deleting Entities
```php
public function deleteUser(User $user): void
{
$this->entityManager->delete($user);
}
```
### N+1 Query Prevention
**Problem**: Lazy Loading führt zu N+1 Queries
```php
// ❌ N+1 Problem
$users = $this->entityManager->findAll(User::class);
foreach ($users as $user) {
// Jede Iteration führt eine separate Query aus (N+1)
echo $user->getProfile()->bio;
}
```
**Solution**: Batch Loading mit Relations
```php
// ✅ Single Query mit Batch Loading
$users = $this->entityManager->findWithRelations(
User::class,
criteria: [],
relations: ['profile', 'posts'] // Lädt Relations in Batches
);
foreach ($users as $user) {
// Kein zusätzlicher Query - Profile bereits geladen
echo $user->getProfile()->bio;
}
```
### Lazy Loading vs Eager Loading
**Lazy Loading** (Standard):
- Relations werden bei Zugriff geladen
- Performance-Vorteil bei ungenutzten Relations
- Potentielles N+1 Problem
```php
$user = $this->entityManager->find(User::class, $id);
// Profile wird erst bei Zugriff geladen:
$bio = $user->getProfile()->bio; // Separate Query
```
**Eager Loading**:
- Relations werden sofort geladen
- Kein N+1 Problem
- Overhead bei ungenutzten Relations
```php
$user = $this->entityManager->findEager(User::class, $id);
// Profile ist bereits geladen - kein zusätzlicher Query
$bio = $user->getProfile()->bio;
```
### Identity Map
Der EntityManager nutzt eine Identity Map zur Vermeidung von Duplikaten:
```php
// Beide Aufrufe liefern die GLEICHE Instanz
$user1 = $this->entityManager->find(User::class, '123');
$user2 = $this->entityManager->find(User::class, '123');
var_dump($user1 === $user2); // true
```
**Vorteile**:
- Keine Duplikate im Speicher
- Konsistente Object Identity
- Automatisches Change Tracking
**Utility Methods**:
```php
// Detach Entity (aus Identity Map entfernen)
$this->entityManager->detach($user);
// Clear Identity Map (z.B. für Batch Processing)
$this->entityManager->clear();
// Identity Map Statistics
$stats = $this->entityManager->getIdentityMapStats();
// Returns: ['total_entities' => 150, 'entities_by_class' => [...]]
```
### Reference Loading
**getReference()** - Lade Entity-Referenz ohne Existenz-Check:
```php
// Erstellt Proxy ohne Database Query
$user = $this->entityManager->getReference(User::class, $userId);
// Query wird erst bei Zugriff ausgeführt
$name = $user->getName(); // Jetzt wird geladen
```
**Use Cases**:
- Foreign Key Relationships
- Performance-kritische Pfade
- Wenn Existenz bereits bekannt ist
### Profiling und Debugging
```php
// Profiling aktivieren
$this->entityManager->setProfilingEnabled(true);
// Operationen ausführen
$users = $this->entityManager->findAll(User::class);
// Profiling-Statistiken abrufen
$stats = $this->entityManager->getProfilingStatistics();
// Profiling-Summary
$summary = $this->entityManager->getProfilingSummary();
// Returns: ProfileSummary mit total_queries, total_time, etc.
// Profiling-Daten löschen
$this->entityManager->clearProfilingData();
```
### Criteria API (Type-Safe Queries)
**Modern**: Verwendung von Criteria statt Arrays
```php
use App\Framework\Database\Criteria\DetachedCriteria;
// Type-safe Criteria
$criteria = DetachedCriteria::forEntity(User::class)
->where('email', '=', $email->value)
->andWhere('active', '=', true)
->orderBy('created_at', 'DESC')
->limit(10);
$users = $this->entityManager->findByCriteria($criteria);
$user = $this->entityManager->findOneByCriteria($criteria);
$count = $this->entityManager->countByCriteria($criteria);
```
### QueryBuilder Integration
Für komplexe Queries:
```php
// QueryBuilder für Entity-Klasse
$queryBuilder = $this->entityManager->createQueryBuilderFor(User::class);
$users = $queryBuilder
->select('*')
->where('email LIKE ?', ['%@example.com'])
->andWhere('created_at > ?', ['2024-01-01'])
->orderBy('name', 'ASC')
->limit(50)
->fetchAll();
// QueryBuilder für Table (ohne Entity)
$qb = $this->entityManager->createQueryBuilderForTable('users');
```
### Domain Events Integration
```php
// Domain Event aufzeichnen
$this->entityManager->recordDomainEvent($user, new UserRegisteredEvent($user));
// Events für Entity dispatchen
$this->entityManager->dispatchDomainEventsForEntity($user);
// Alle Events dispatchen
$this->entityManager->dispatchAllDomainEvents();
// Event-Statistiken
$stats = $this->entityManager->getDomainEventStats();
// Returns: ['total_events' => 23, 'events_by_entity' => [...]]
```
## Repository Pattern
TODO: Document repository implementation and usage
### Overview
Das Repository Pattern abstrahiert Daten-Zugriff und bietet domain-spezifische Query-Methoden. Das Framework nutzt **Composition over Inheritance**.
### Base EntityRepository
**Framework-Service** für Common Operations:
```php
namespace App\Framework\Database\Repository;
final readonly class EntityRepository
{
public function __construct(
private EntityManager $entityManager
) {}
public function find(string $entityClass, string $id): ?object
public function findAll(string $entityClass): array
public function findBy(string $entityClass, array $criteria, ...): array
public function findOneBy(string $entityClass, array $criteria): ?object
public function save(object $entity): object
public function delete(object $entity): void
public function transaction(callable $callback): mixed
// Batch Loading (N+1 Prevention)
public function findWithRelations(...): array
// Pagination
public function findPaginated(...): PaginatedResult
// Batch Operations
public function saveBatch(array $entities, int $batchSize = 100): array
public function deleteBatch(array $entities, int $batchSize = 100): void
}
```
### Domain Repository Pattern
**Composition-basiert** statt Inheritance:
```php
namespace App\Domain\User\Repositories;
use App\Framework\Database\Repository\EntityRepository;
final readonly class UserRepository
{
public function __construct(
private EntityRepository $entityRepository
) {}
public function findById(UserId $id): ?User
{
return $this->entityRepository->find(User::class, $id->value);
}
public function findByEmail(Email $email): ?User
{
return $this->entityRepository->findOneBy(
User::class,
['email' => $email->value]
);
}
public function findActiveUsers(): array
{
return $this->entityRepository->findBy(
User::class,
criteria: ['active' => true],
orderBy: ['created_at' => 'DESC']
);
}
public function save(User $user): User
{
return $this->entityRepository->save($user);
}
public function delete(User $user): void
{
$this->entityRepository->delete($user);
}
// Domain-spezifische Methode mit Batch Loading
public function findUsersWithProfiles(array $userIds): array
{
return $this->entityRepository->findWithRelations(
User::class,
criteria: ['id' => $userIds],
relations: ['profile', 'settings']
);
}
// Pagination
public function findPaginated(int $page, int $limit = 20): PaginatedResult
{
return $this->entityRepository->findPaginated(
User::class,
page: $page,
limit: $limit,
orderBy: ['created_at' => 'DESC']
);
}
}
```
### Repository Best Practices
**✅ Composition Over Inheritance**:
```php
// ✅ Framework Pattern - Composition
final readonly class OrderRepository
{
public function __construct(
private EntityRepository $entityRepository
) {}
}
// ❌ Avoid - Inheritance
class OrderRepository extends BaseRepository
{
// Problematisch: Tight coupling
}
```
**✅ Domain-Specific Methods**:
```php
// ✅ Sprechende Domain-Methoden
public function findOverdueOrders(): array
{
return $this->entityRepository->findBy(
Order::class,
['status' => 'pending'],
orderBy: ['due_date' => 'ASC']
);
}
// ❌ Generic Methods außerhalb Domain
public function findBy(array $criteria): array
{
// Zu generisch - nutze EntityRepository direkt
}
```
**✅ N+1 Prevention**:
```php
// ✅ Batch Loading für Relations
public function findOrdersWithItems(array $orderIds): array
{
return $this->entityRepository->findWithRelations(
Order::class,
criteria: ['id' => $orderIds],
relations: ['items', 'customer'] // Lädt in Batches
);
}
```
## Unit of Work Pattern
### Overview
Der Unit of Work Pattern verwaltet Entities und deren Änderungen für transaktionale Konsistenz.
**Key Features**:
- **Change Tracking**: Automatische Änderungserkennung
- **Transactional Consistency**: Atomic Commits/Rollbacks
- **Batch Operations**: Optimierte Bulk-Operationen
- **Entity States**: NEW, MANAGED, DIRTY, DELETED, DETACHED
### Entity Lifecycle
```php
use App\Framework\Database\UnitOfWork\UnitOfWork;
final readonly class OrderService
{
public function __construct(
private EntityManager $entityManager
) {}
public function createOrder(CreateOrderCommand $command): Order
{
$unitOfWork = $this->entityManager->unitOfWork;
// 1. Create new entity (state: NEW)
$order = Order::create($command);
// 2. Persist marks entity for INSERT (state: NEW → MANAGED)
$unitOfWork->persist($order);
// 3. Flush writes to database (state: MANAGED)
$unitOfWork->flush();
// 4. Commit transaction
$unitOfWork->commit();
return $order;
}
}
```
### Change Tracking
**Automatic Change Detection**:
```php
$unitOfWork = $this->entityManager->unitOfWork;
// Load entity (state: MANAGED)
$user = $this->entityManager->find(User::class, $userId);
// Modify entity
$user->updateProfile($newName);
// Change Tracking erkennt Änderung automatisch
$unitOfWork->flush(); // UPDATE query wird generiert
$unitOfWork->commit();
```
**Manual Change Tracking**:
```php
// Entity aus externer Quelle (state: DETACHED)
$user = unserialize($serializedUser);
// Merge in Unit of Work (state: DETACHED → MANAGED)
$managedUser = $unitOfWork->merge($user);
// Änderungen werden getrackt
$unitOfWork->flush();
```
### Transaction Management
**Explicit Transactions**:
```php
public function processOrder(Order $order): void
{
$unitOfWork = $this->entityManager->unitOfWork;
try {
$unitOfWork->beginTransaction();
// Multiple operations
$order->confirm();
$unitOfWork->persist($order);
$this->inventoryService->reserve($order->items);
$this->paymentService->charge($order->total);
// Write changes to database
$unitOfWork->flush();
// Commit transaction
$unitOfWork->commit();
} catch (\Exception $e) {
$unitOfWork->rollback();
throw $e;
}
}
```
**Implicit Transactions (Auto-Commit)**:
```php
// Auto-commit ist standardmäßig aktiviert
$unitOfWork->persist($entity); // Automatisch committed
```
**Disable Auto-Commit** für Batch Operations:
```php
$unitOfWork->setAutoCommit(false);
try {
$unitOfWork->beginTransaction();
foreach ($orders as $order) {
$unitOfWork->persist($order); // Kein Auto-Commit
}
$unitOfWork->flush(); // Batch-Write
$unitOfWork->commit(); // Atomic Commit
} catch (\Exception $e) {
$unitOfWork->rollback();
throw $e;
} finally {
$unitOfWork->setAutoCommit(true);
}
```
### Entity States
```php
use App\Framework\Database\UnitOfWork\EntityState;
// Check Entity State
$state = $unitOfWork->getEntityState($entity);
match ($state) {
EntityState::NEW => 'Entity marked for INSERT',
EntityState::MANAGED => 'Entity being tracked, no changes',
EntityState::DIRTY => 'Entity modified, pending UPDATE',
EntityState::DELETED => 'Entity marked for DELETE',
EntityState::DETACHED => 'Entity not managed by UnitOfWork',
};
// Check if entity is managed
if ($unitOfWork->contains($entity)) {
// Entity is managed (NEW, MANAGED, DIRTY, or DELETED)
}
```
### Detaching Entities
```php
// Detach entity from Unit of Work
$unitOfWork->detach($user);
// Entity state: DETACHED
// Changes to entity are no longer tracked
```
**Use Cases**:
- Long-running processes
- Serialization/Caching
- Read-only operations
- Memory management in batch processing
### Bulk Operations
```php
// Bulk Insert
$unitOfWork->bulkInsert($entities); // Optimized batch INSERT
// Bulk Update
$unitOfWork->bulkUpdate($entities); // Optimized batch UPDATE
// Bulk Delete
$unitOfWork->bulkDelete($entities); // Optimized batch DELETE
```
## Query Optimization
TODO: Document N+1 prevention and batch loading
### N+1 Query Problem
**Erkennnung**: Jede Iteration führt zusätzliche Query aus
```php
// ❌ N+1 Problem
$orders = $this->entityManager->findAll(Order::class); // 1 Query
foreach ($orders as $order) {
echo $order->getCustomer()->name; // N Queries (1 pro Order)
}
// Total: 1 + N Queries
```
### Solution: Batch Loading
```php
// ✅ Batch Loading
$orders = $this->entityManager->findWithRelations(
Order::class,
criteria: [],
relations: ['customer', 'items'] // Preload relations
);
foreach ($orders as $order) {
echo $order->getCustomer()->name; // Kein Query - bereits geladen
}
// Total: 2-3 Queries (1 für Orders, 1-2 für Relations)
```
### Eager vs Lazy Loading
**Strategic Loading**:
```php
// Heavy Operation - Eager Loading
public function getOrderDetails(OrderId $id): OrderDetails
{
$order = $this->entityManager->findEager(Order::class, $id->value);
// Alle Relations sofort geladen
return OrderDetails::fromOrder($order);
}
// Light Operation - Lazy Loading
public function listOrders(): array
{
// Relations werden nur bei Bedarf geladen
return $this->entityManager->findAll(Order::class);
}
```
### Pagination
```php
use App\Framework\Database\Repository\EntityRepository;
public function getUsers(int $page): PaginatedResult
{
return $this->entityRepository->findPaginated(
User::class,
page: $page,
limit: 20,
orderBy: ['created_at' => 'DESC']
);
}
// PaginatedResult enthält:
// - items: array (Entities)
// - total: int (Total count)
// - page: int (Current page)
// - limit: int (Items per page)
// - totalPages: int (Calculated)
```
### Query Caching
```php
// EntityManager mit Cache
$entityManager = new EntityManager(
// ...
cacheManager: $cacheManager
);
// Find verwendet Cache automatisch
$user = $entityManager->find(User::class, $id);
// Beim zweiten Aufruf: Cache Hit
```
## Connection Pooling
TODO: Document connection pool configuration
### Overview
Connection Pooling verbessert Performance durch Wiederverwendung von Datenbankverbindungen.
**Features**:
- Min/Max Connection Limits
- Connection Health Monitoring
- Automatic Retry mit Exponential Backoff
- Idle Connection Cleanup
- Connection Warmup
### Configuration
```php
use App\Framework\Database\Config\PoolConfig;
$poolConfig = new PoolConfig(
minConnections: 2, // Minimum pool size
maxConnections: 10, // Maximum pool size
maxIdleTime: 300, // Idle timeout (seconds)
healthCheckInterval: 60, // Health check frequency
enableWarmup: true // Pre-create min connections
);
```
### Usage
```php
use App\Framework\Database\ConnectionPool;
$pool = new ConnectionPool($driverConfig, $poolConfig, $timer);
// Get connection from pool
$connection = $pool->getConnection();
try {
// Use connection
$result = $connection->query('SELECT * FROM users');
} finally {
// Return connection to pool (automatic with PooledConnection)
$connection->release();
}
```
### Health Monitoring
```php
// Pool automatically monitors connection health
$pool->performHealthCheck();
// Get pool statistics
$stats = $pool->getPoolStatistics();
// Returns:
[
'current_connections' => 5,
'max_connections' => 10,
'connections_in_use' => 3,
'healthy_connections' => 5,
'total_created' => 12,
'total_destroyed' => 7
]
```
### Retry Logic
```php
// Automatic retry with exponential backoff
$retryStrategy = ExponentialBackoffStrategy::create()
->withMaxAttempts(3)
->withBaseDelay(Duration::fromMilliseconds(100));
// Connection pool uses retry automatically
$connection = $pool->getConnectionWithRetry($retryStrategy);
```
### Production Configuration
```php
// Production Pool Config
$productionPoolConfig = new PoolConfig(
minConnections: 5, // Keep 5 connections ready
maxConnections: 50, // Scale up to 50 under load
maxIdleTime: 600, // 10 minutes idle timeout
healthCheckInterval: 30, // Check every 30 seconds
enableWarmup: true, // Pre-warm connections
connectionTimeout: 5000 // 5 second timeout
);
```
## Transaction Management
TODO: Document transaction patterns and best practices
### Basic Transactions
```php
// EntityManager Transaction Helper
$this->entityManager->transaction(function (EntityManager $em) {
$order = Order::create($command);
$em->save($order);
$this->inventoryService->reserve($order->items);
$this->paymentService->charge($order->total);
// Automatic commit on success, rollback on exception
});
```
### Manual Transaction Control
```php
$unitOfWork = $this->entityManager->unitOfWork;
$unitOfWork->beginTransaction();
try {
// Operations
$unitOfWork->persist($entity1);
$unitOfWork->persist($entity2);
$unitOfWork->flush();
$unitOfWork->commit();
} catch (\Exception $e) {
$unitOfWork->rollback();
throw $e;
}
```
### Nested Transactions (Savepoints)
```php
$unitOfWork->beginTransaction();
try {
$order = Order::create($command);
$unitOfWork->persist($order);
// Nested transaction (savepoint)
$unitOfWork->beginTransaction();
try {
$this->inventoryService->reserve($order->items);
$unitOfWork->commit(); // Commit savepoint
} catch (\Exception $e) {
$unitOfWork->rollback(); // Rollback to savepoint
throw $e;
}
$unitOfWork->commit(); // Commit main transaction
} catch (\Exception $e) {
$unitOfWork->rollback(); // Rollback main transaction
throw $e;
}
```
### Transaction Isolation Levels
```php
use App\Framework\Database\TransactionIsolation;
$unitOfWork->beginTransaction(
isolation: TransactionIsolation::READ_COMMITTED
);
// Available isolation levels:
// - READ_UNCOMMITTED (lowest isolation)
// - READ_COMMITTED (default)
// - REPEATABLE_READ
// - SERIALIZABLE (highest isolation)
```
### Transaction Best Practices
**✅ Short Transactions**:
```php
// ✅ Keep transactions short
$this->entityManager->transaction(fn(EntityManager $em) =>
$em->save($entity)
);
```
**❌ Long Transactions**:
```php
// ❌ Avoid long-running transactions
$this->entityManager->transaction(function (EntityManager $em) {
$this->sendEmail($user); // External I/O - BAD
$this->processHeavyComputation(); // CPU-intensive - BAD
$em->save($entity);
});
```
**✅ Batch with Periodic Commits**:
```php
// ✅ Batch processing with periodic commits
$unitOfWork->setAutoCommit(false);
$batchSize = 100;
for ($i = 0; $i < count($items); $i++) {
$unitOfWork->persist($items[$i]);
if (($i + 1) % $batchSize === 0) {
$unitOfWork->flush();
$unitOfWork->commit();
$unitOfWork->beginTransaction();
}
}
$unitOfWork->flush();
$unitOfWork->commit();
$unitOfWork->setAutoCommit(true);
```
## Database Testing