Dies ist eine alte Version des Dokuments!
Vorschlag: Umstrukturierung der Käufererstellung nach 3-Schichten-Architektur Analyse der aktuellen Implementierung Aktuelle Struktur (Buyer::Create()) Die aktuelle Buyer::Create() Funktion enthält alle Schichten vermischt:
Validierung (ValidateData, ValidateVaucher, CheckDuplicate, CheckLager, CheckCountry) Geschäftslogik (setAgentByEmail, Generierung von Passwort/Kundennummer/Username, Bestimmung von frominternet) Datenbankzugriffe (InsertBuyer, SaveAdresses, SaveBank, direkte SQL-Queries) Cache-Operationen (CacheBuyers, CacheAddress) Externe Integrationen (E-Mail-Versand, CMP-Export, Letter Package) Orchestrierung (implizit durch die Reihenfolge der Aufrufe) Vorschlag: Neue Struktur Übersicht der Schichten API (CustomerController)
↓
Orchestrierung (BuyerCreationOrchestration)
↓ ├─→ Business-Funktionen (Fachfunktionen) │ ├─→ ValidateBuyerDataFunction │ ├─→ DetermineSponsorFunction │ ├─→ GenerateBuyerIdentifiersFunction │ ├─→ ValidateAddressesFunction │ ├─→ DetermineRegistrationSourceFunction │ └─→ ValidateVoucherFunction ↓
Zwischenschicht (Repositories)
├─→ BuyerRepository ├─→ AddressRepository ├─→ BankRepository ├─→ UserRepository ├─→ SponsorRepository ├─→ VoucherRepository └─→ InterestingRepository ↓
Framework (Yii2) Detaillierte Aufteilung 1. API Layer Datei: backend/modules/api/controllers/CustomerController.php
Verantwortlichkeiten:
Empfang von HTTP-Requests Validierung des Request-Formats Aufruf der Orchestrierung Formatierung der Response Fehlerbehandlung auf API-Ebene Code-Struktur:
class CustomerController extends Controller {
public function actionCreate($request)
{
try {
// 1. Request-Validierung (Format, nicht Inhalt!)
$this->validateRequestFormat($request);
// 2. Aufruf Orchestrierung
$orchestration = Yii::$container->get(BuyerCreationOrchestration::class);
$result = $orchestration->createBuyer($request);
// 3. Response formatieren
return $this->formatResponse($result);
} catch (ValidationException $e) {
return $this->formatErrorResponse($e);
} catch (\Exception $e) {
return $this->formatErrorResponse($e);
}
}
} Was NICHT hier:
❌ Geschäftslogik ❌ Validierung von Geschäftsdaten ❌ Entscheidungen über Prozessablauf 2. Orchestrierung (BuyerCreationOrchestration) Datei: backend/modules/core/orchestration/BuyerCreationOrchestration.php
Verantwortlichkeiten:
Koordination des gesamten Erstellungsprozesses Aufruf der Business-Funktionen in richtiger Reihenfolge Datenbeschaffung über Zwischenschicht Zusammenführung der Ergebnisse KEINE Geschäftsentscheidungen Code-Struktur:
class BuyerCreationOrchestration {
private $buyerRepository;
private $validateBuyerDataFunction;
private $determineSponsorFunction;
private $generateIdentifiersFunction;
private $validateAddressesFunction;
private $determineRegistrationSourceFunction;
private $validateVoucherFunction;
private $saveBuyerFunction;
private $saveAddressesFunction;
private $saveBankFunction;
private $sendEmailFunction;
private $cacheService;
private $cmpService;
public function createBuyer($request)
{
// 1. Daten aus Request extrahieren
$buyerData = $this->extractBuyerData($request);
// 2. Validierung der Eingabedaten
$validationResult = $this->validateBuyerDataFunction->validate($buyerData);
if (!$validationResult->isValid()) {
return $this->createErrorResponse($validationResult);
}
// 3. Bestimmung des Sponsors (agentid)
$sponsorData = $this->determineSponsorFunction->determine($buyerData);
$buyerData->merge($sponsorData);
// 4. Validierung Gutschein (wenn aktiviert)
if ($this->isVoucherValidationEnabled()) {
$voucherResult = $this->validateVoucherFunction->validate($buyerData);
$buyerData->merge($voucherResult);
}
// 5. Prüfung auf Duplikate (wenn nicht Guest)
if (!$buyerData->isGuest() && !$buyerData->skipDuplicateCheck()) {
$duplicateCheck = $this->checkDuplicates($buyerData);
if ($duplicateCheck->hasDuplicates()) {
return $this->createDuplicateResponse($duplicateCheck);
}
}
// 6. Automatische Erstellung aus Interessent (wenn aktiviert)
if ($this->isAutoCreateFromInterestingEnabled() && $buyerData->hasEmail()) {
$interestingData = $this->buyerRepository->findInterestingByEmail($buyerData->getEmail());
if ($interestingData) {
return $this->createFromInteresting($interestingData, $buyerData);
}
}
// 7. Bestimmung der Registrierungsquelle (frominternet)
$registrationSource = $this->determineRegistrationSourceFunction->determine($buyerData);
$buyerData->setRegistrationSource($registrationSource);
// 8. Generierung von Identifikatoren
$identifiers = $this->generateIdentifiersFunction->generate($buyerData);
$buyerData->merge($identifiers);
// 9. Validierung von Adressen
if ($buyerData->hasAddresses()) {
$addressValidation = $this->validateAddressesFunction->validate($buyerData->getAddresses());
if (!$addressValidation->isValid()) {
return $this->createErrorResponse($addressValidation);
}
}
// 10. Validierung Lager (wenn angegeben)
if ($buyerData->hasWarehouse()) {
$warehouseValidation = $this->validateWarehouse($buyerData);
if (!$warehouseValidation->isValid()) {
return $this->createErrorResponse($warehouseValidation);
}
}
// 11. Speicherung des Käufers
$savedBuyer = $this->saveBuyerFunction->save($buyerData);
// 12. Speicherung der Adressen
if ($buyerData->hasAddresses()) {
$this->saveAddressesFunction->save($savedBuyer->getId(), $buyerData->getAddresses());
}
// 13. Speicherung der Bankdaten
if ($buyerData->hasBankData()) {
$this->saveBankFunction->save($savedBuyer->getId(), $buyerData->getBankData());
}
// 14. Erstellung des Benutzers (WboUser)
$user = $this->createUser($savedBuyer, $buyerData);
// 15. Zusätzliche Aktionen
$this->performPostCreationActions($savedBuyer, $buyerData);
// 16. Cache aktualisieren
$this->cacheService->cacheBuyer($savedBuyer->getId());
$this->cacheService->cacheAddresses($savedBuyer->getId());
// 17. Response zusammenstellen
return $this->createSuccessResponse($savedBuyer, $user);
}
private function checkDuplicates($buyerData)
{
// Aufruf über Repository
return $this->buyerRepository->checkDuplicates($buyerData);
}
private function validateWarehouse($buyerData)
{
// Business-Funktion für Lager-Validierung
return $this->validateWarehouseFunction->validate($buyerData);
}
private function createUser($buyer, $buyerData)
{
// Orchestrierung für User-Erstellung (kann auch eigene Orchestrierung haben)
$userOrchestration = Yii::$container->get(UserCreationOrchestration::class);
return $userOrchestration->createUser($buyer, $buyerData);
}
private function performPostCreationActions($buyer, $buyerData)
{
// E-Mail-Versand (wenn nicht Guest)
if (!$buyerData->isGuest()) {
$this->sendEmailFunction->sendBuyerRegistrationEmail($buyer);
}
// Letter Package
$this->letterPackageService->checkAndAdd($buyer, $buyerData);
// Starter zu VP konvertieren (wenn starterid vorhanden)
if ($buyerData->hasStarterId()) {
$this->convertStarterToVP($buyerData->getStarterId());
}
// CMP-Export (wenn aktiviert)
if ($this->isCmpExportEnabled()) {
$this->cmpService->addToQueue($buyer->getId(), 1);
}
}
} Was NICHT hier:
❌ Geschäftslogik (z.B. „wenn agentid fehlt, dann…“) ❌ Direkte Datenbankzugriffe ❌ SQL-Queries ❌ Validierungsregeln 3. Business-Funktionen (Fachfunktionen) 3.1. ValidateBuyerDataFunction Datei: backend/modules/core/functions/ValidateBuyerDataFunction.php
Verantwortlichkeiten:
Validierung aller Käuferdaten nach Geschäftsregeln Prüfung von Pflichtfeldern Format-Validierung (E-Mail, IBAN, Steuernummer, etc.) Rückgabe von Validierungsergebnissen Code-Struktur:
class ValidateBuyerDataFunction {
private $emailValidator;
private $ibanValidator;
private $taxIdValidator;
private $usernameValidator;
public function validate(BuyerData $buyerData): ValidationResult
{
$result = new ValidationResult();
// 1. E-Mail-Validierung
if ($buyerData->hasEmail()) {
if (!$this->emailValidator->isValid($buyerData->getEmail())) {
$result->addError('email', 'Invalid email format');
}
} else {
$result->addError('email', 'Email is required');
}
// 2. Username-Validierung (wenn angegeben)
if ($buyerData->hasUsername()) {
if (!$this->usernameValidator->isUnique($buyerData->getUsername())) {
$result->addError('username', 'Username already exists');
}
}
// 3. Steuernummer-Validierung (wenn angegeben)
if ($buyerData->hasTaxId()) {
if (!$this->taxIdValidator->isValid($buyerData->getTaxId())) {
$result->addError('taxId', 'Invalid tax ID');
}
}
// 4. IBAN-Validierung (wenn Bankdaten vorhanden)
if ($buyerData->hasBankData()) {
$ibanValidation = $this->ibanValidator->validate($buyerData->getBankData());
if (!$ibanValidation->isValid()) {
$result->mergeErrors($ibanValidation);
}
}
// 5. PLZ-Validierung (wenn Adressen vorhanden)
if ($buyerData->hasAddresses()) {
$plzValidation = $this->validatePostalCodes($buyerData->getAddresses());
if (!$plzValidation->isValid()) {
$result->mergeErrors($plzValidation);
}
}
return $result;
}
private function validatePostalCodes(array $addresses): ValidationResult
{
$result = new ValidationResult();
foreach ($addresses as $address) {
if (!$this->postalCodeValidator->isValid($address->getPostCode(), $address->getCountry())) {
$result->addError('postCode', 'Invalid postal code for country');
}
}
return $result;
}
} Was NICHT hier:
❌ Datenbankzugriffe (nur über Interfaces/Repositories) ❌ SQL-Queries ❌ Framework-spezifischer Code 3.2. DetermineSponsorFunction Datei: backend/modules/core/functions/DetermineSponsorFunction.php
Verantwortlichkeiten:
Bestimmung des Sponsors (agentid) nach Geschäftsregeln Automatische Bestimmung über E-Mail (wenn aktiviert) Rückgabe des Sponsor-Ergebnisses Code-Struktur:
class DetermineSponsorFunction {
private $sponsorRepository;
private $buyerRepository;
private $config;
public function determine(BuyerData $buyerData): SponsorResult
{
$result = new SponsorResult();
// 1. Wenn agentid bereits vorhanden → validieren
if ($buyerData->hasAgentId()) {
$sponsor = $this->sponsorRepository->findByAgentId($buyerData->getAgentId());
if (!$sponsor || !$sponsor->isActive()) {
throw new InvalidSponsorException('Sponsor not found or inactive');
}
$result->setAgentId($sponsor->getAgentId());
$result->setAgentInternalId($sponsor->getInternalId());
return $result;
}
// 2. Automatische Bestimmung über E-Mail (wenn aktiviert)
if ($this->config->isAgentByEmailEnabled() && $buyerData->hasEmail()) {
$sponsor = $this->determineByEmail($buyerData->getEmail());
if ($sponsor) {
$result->setAgentId($sponsor->getAgentId());
$result->setAgentInternalId($sponsor->getInternalId());
return $result;
}
}
// 3. Fallback auf Master-VP (Geschäftsregel)
$masterVP = $this->sponsorRepository->findMasterVP();
$result->setAgentId($masterVP->getAgentId());
$result->setAgentInternalId($masterVP->getInternalId());
return $result;
}
private function determineByEmail(string $email): ?Sponsor
{
// Geschäftsregel: Suche nach Käufer mit dieser E-Mail
$buyer = $this->buyerRepository->findByEmail($email);
if (!$buyer) {
return null;
}
// Geschäftsregel: Bestimme Sponsor des gefundenen Käufers
$sponsor = $this->sponsorRepository->findByBuyerId($buyer->getId());
// Geschäftsregel: Sortierung (neueste/älteste)
if ($this->config->getAgentByEmailMode() === 'newest') {
// Neueste zuerst
return $sponsor;
} else {
// Älteste zuerst
return $sponsor;
}
}
} 3.3. GenerateBuyerIdentifiersFunction Datei: backend/modules/core/functions/GenerateBuyerIdentifiersFunction.php
Verantwortlichkeiten:
Generierung von Kundennummer (kundennr) Generierung von Username Generierung von Passwort (wenn nicht vorhanden) Bestimmung von accounting-Nummer Rückgabe aller generierten Identifikatoren Code-Struktur:
class GenerateBuyerIdentifiersFunction {
private $buyerRepository;
private $passwordGenerator;
private $usernameGenerator;
private $customerNumberGenerator;
public function generate(BuyerData $buyerData): IdentifierResult
{
$result = new IdentifierResult();
// 1. Kundennummer generieren
$customerNumber = $this->generateCustomerNumber($buyerData);
$result->setCustomerNumber($customerNumber);
// 2. Username generieren
$username = $this->generateUsername($buyerData, $customerNumber);
$result->setUsername($username);
// 3. Passwort generieren (wenn nicht vorhanden)
if (!$buyerData->hasPassword()) {
$password = $this->passwordGenerator->generate();
$result->setPassword($password);
} else {
$result->setPassword($buyerData->getPassword());
}
// 4. Accounting-Nummer bestimmen
$accounting = $this->determineAccountingNumber($buyerData, $customerNumber);
$result->setAccounting($accounting);
return $result;
}
private function generateCustomerNumber(BuyerData $buyerData): string
{
// Geschäftsregel 1: Wenn number angegeben → verwenden (nach Prüfung)
if ($buyerData->hasNumber() && !$buyerData->hasImportNumber()) {
// Prüfung auf Eindeutigkeit über Repository
if ($this->buyerRepository->isCustomerNumberAvailable($buyerData->getNumber())) {
return $buyerData->getNumber();
}
throw new CustomerNumberAlreadyExistsException();
}
// Geschäftsregel 2: Wenn mitarbeiternr vorhanden → verwenden
if ($buyerData->hasMitarbeiternr()) {
return $buyerData->getMitarbeiternr();
}
// Geschäftsregel 3: Automatische Generierung
return $this->customerNumberGenerator->generate();
}
private function generateUsername(BuyerData $buyerData, string $customerNumber): string
{
// Geschäftsregel: Wenn username vorhanden → verwenden (bereits validiert)
if ($buyerData->hasUsername()) {
return $buyerData->getUsername();
}
// Geschäftsregel: Generierung basierend auf Kundennummer
return $this->usernameGenerator->generateFromCustomerNumber($customerNumber);
}
private function determineAccountingNumber(BuyerData $buyerData, string $customerNumber): string
{
// Geschäftsregel 1: Wenn emplaccounting vorhanden → verwenden
if ($buyerData->hasEmplAccounting()) {
return $buyerData->getEmplAccounting();
}
// Geschäftsregel 2: Wenn accounting vorhanden → verwenden
if ($buyerData->hasAccounting()) {
return $buyerData->getAccounting();
}
// Geschäftsregel 3: Fallback auf Kundennummer
return $customerNumber;
}
} 3.4. ValidateAddressesFunction Datei: backend/modules/core/functions/ValidateAddressesFunction.php
Verantwortlichkeiten:
Validierung aller Adressen Prüfung auf Pflichtfelder Validierung von Ländern Prüfung auf Konsistenz Code-Struktur:
class ValidateAddressesFunction {
private $countryRepository;
private $postalCodeValidator;
public function validate(array $addresses): ValidationResult
{
$result = new ValidationResult();
foreach ($addresses as $index => $address) {
// 1. Land-Validierung
if (!$address->hasCountry()) {
$result->addError("addresses[$index].country", 'Country is required');
continue;
}
$country = $this->countryRepository->findByCode($address->getCountry());
if (!$country) {
$result->addError("addresses[$index].country", 'Invalid country code');
continue;
}
// 2. PLZ-Validierung
if ($address->hasPostCode()) {
if (!$this->postalCodeValidator->isValid($address->getPostCode(), $country)) {
$result->addError("addresses[$index].postCode", 'Invalid postal code');
}
}
// 3. Weitere Validierungen...
}
return $result;
}
} 3.5. DetermineRegistrationSourceFunction Datei: backend/modules/core/functions/DetermineRegistrationSourceFunction.php
Verantwortlichkeiten:
Bestimmung von frominternet basierend auf autonumbertype Rückgabe der Registrierungsquelle Code-Struktur:
class DetermineRegistrationSourceFunction {
public function determine(BuyerData $buyerData): RegistrationSource
{
// Geschäftsregel: Mapping von autonumbertype zu frominternet
if ($buyerData->hasAutonumbertype()) {
$type = $buyerData->getAutonumbertype();
if ($type === 3) {
return new RegistrationSource(107, 'Internet registration');
}
if ($type === 5) {
return new RegistrationSource(111, 'Special registration');
}
// Standard für andere autonumbertype
return new RegistrationSource(103, 'Internet registration with auto number');
}
// Standard-Registrierungsquelle
return new RegistrationSource(102, 'Standard registration');
}
} 3.6. ValidateVoucherFunction Datei: backend/modules/core/functions/ValidateVoucherFunction.php
Verantwortlichkeiten:
Validierung von Gutschein-Code Bestimmung des zugehörigen Sponsors Rückgabe des Sponsor-Ergebnisses Code-Struktur:
class ValidateVoucherFunction {
private $voucherRepository;
private $sponsorRepository;
public function validate(BuyerData $buyerData): VoucherResult
{
$result = new VoucherResult();
// Wenn kein agentid vorhanden → kein Gutschein-Check
if (!$buyerData->hasAgentId()) {
return $result;
}
// Prüfung ob agentid ein Gutschein-Code ist
$voucher = $this->voucherRepository->findByCode($buyerData->getAgentId());
if (!$voucher) {
// Kein Gutschein → agentid bleibt unverändert
return $result;
}
// Geschäftsregel: Sponsor aus Gutschein bestimmen
$sponsor = $this->sponsorRepository->findByInternalId($voucher->getForAgent());
if (!$sponsor) {
throw new InvalidVoucherException('Sponsor for voucher not found');
}
// Ergebnis: agentid ersetzen, Gutschein-Code speichern
$result->setAgentId($sponsor->getAgentId());
$result->setAgentInternalId($sponsor->getInternalId());
$result->setVoucherCode($buyerData->getAgentId());
return $result;
}
} 4. Zwischenschicht (Repositories) 4.1. BuyerRepository Datei: backend/modules/core/repositories/BuyerRepository.php
Verantwortlichkeiten:
Alle SQL-Queries für Käufer CRUD-Operationen Suche nach Käufern Duplikat-Prüfung Code-Struktur:
class BuyerRepository implements BuyerRepositoryInterface {
private $db;
public function create(BuyerEntity $buyer): BuyerEntity
{
// SQL-Query hier
$sql = "INSERT INTO tb_kundendaten (...) VALUES (...)";
$this->db->createCommand($sql, [...])->execute();
// Stored Procedure aufrufen
$this->db->createCommand("CALL kd_adresse(?)", [$buyer->getInternekdnr()])->execute();
return $buyer;
}
public function findById(int $id): ?BuyerEntity
{
$sql = "SELECT * FROM tb_kundendaten WHERE internekdnr = ?";
$row = $this->db->createCommand($sql, [$id])->queryOne();
if (!$row) {
return null;
}
return $this->mapToEntity($row);
}
public function findByEmail(string $email): ?BuyerEntity
{
$sql = "SELECT * FROM tb_kundendaten WHERE kdemail = ?";
$row = $this->db->createCommand($sql, [$email])->queryOne();
if (!$row) {
return null;
}
return $this->mapToEntity($row);
}
public function isCustomerNumberAvailable(string $customerNumber): bool
{
$sql = "SELECT COUNT(*) FROM tb_kundendaten WHERE kundennr = ?";
$count = $this->db->createCommand($sql, [$customerNumber])->queryScalar();
return $count === 0;
}
public function checkDuplicates(BuyerData $buyerData): DuplicateCheckResult
{
// Komplexe SQL-Query für Duplikat-Prüfung
$sql = "..."; // Aus ValidateDuplicate
$rows = $this->db->createCommand($sql, [...])->queryAll();
return $this->mapToDuplicateResult($rows);
}
public function generateNewCustomerNumber(): string
{
$sql = "SELECT getNewBuyerOutID() as newkdnr";
return $this->db->createCommand($sql)->queryScalar();
}
private function mapToEntity(array $row): BuyerEntity
{
// Mapping von DB-Row zu Entity
return BuyerEntity::fromArray($row);
}
} 4.2. AddressRepository Datei: backend/modules/core/repositories/AddressRepository.php
Code-Struktur:
class AddressRepository implements AddressRepositoryInterface {
private $db;
public function create(int $buyerId, AddressEntity $address): AddressEntity
{
$sql = "INSERT INTO tb_kundendaten_zusatz (...) VALUES (...)";
$this->db->createCommand($sql, [...])->execute();
// Stored Procedure
$this->db->createCommand("CALL kd_adresse_z(?)", [$address->getId()])->execute();
return $address;
}
public function update(int $addressId, AddressEntity $address): void
{
$sql = "UPDATE tb_kundendaten_zusatz SET ... WHERE id = ?";
$this->db->createCommand($sql, [...])->execute();
$this->db->createCommand("CALL kd_adresse_z(?)", [$addressId])->execute();
}
public function findMainAddress(int $buyerId): ?AddressEntity
{
$sql = "SELECT * FROM tb_kundendaten_zusatz WHERE internekdnr = ? AND maindata = 1";
$row = $this->db->createCommand($sql, [$buyerId])->queryOne();
return $row ? $this->mapToEntity($row) : null;
}
public function findByReference(int $buyerId, string $reference): ?AddressEntity
{
$sql = "SELECT * FROM tb_kundendaten_zusatz WHERE internekdnr = ? AND reference = ?";
$row = $this->db->createCommand($sql, [$buyerId, $reference])->queryOne();
return $row ? $this->mapToEntity($row) : null;
}
public function generateNewAddressId(): int
{
// Generierung neuer ID
$sql = "SELECT ...";
return $this->db->createCommand($sql)->queryScalar();
}
} 4.3. Weitere Repositories BankRepository - für Bankdaten UserRepository - für mlm_users und mlm_wbo_users SponsorRepository - für Partner/Sponsor-Daten VoucherRepository - für Gutscheine InterestingRepository - für Interessenten CountryRepository - für Länder WarehouseRepository - für Lager 5. Framework Layer Bleibt unverändert:
Yii2 Framework Datenbankverbindungen Sicherheit Routing Dependency Injection Container Mapping: Alte → Neue Struktur Aktuelle Funktion Neue Struktur Buyer::Create() BuyerCreationOrchestration::createBuyer() Buyer::ValidateData() ValidateBuyerDataFunction::validate() Buyer::ValidateVaucher() ValidateVoucherFunction::validate() Buyer::setAgentByEmail() DetermineSponsorFunction::determineByEmail() Buyer::CheckDuplicate() BuyerRepository::checkDuplicates() Buyer::getNewBuyerOutID() BuyerRepository::generateNewCustomerNumber() Buyer::CreateUserName() GenerateBuyerIdentifiersFunction::generateUsername() Buyer::InsertBuyer() BuyerRepository::create() Buyer::SaveAdresses() AddressRepository::create() / AddressRepository::update() Buyer::SaveBank() BankRepository::create() Buyer::SendBuyerMail() SendEmailFunction::sendBuyerRegistrationEmail() Cache::CacheBuyers() CacheService::cacheBuyer() Direkte SQL-Queries Alle in Repositories Vorteile der neuen Struktur Trennung der Verantwortlichkeiten:
Geschäftslogik ist klar getrennt von Datenzugriff Jede Funktion hat eine einzige Verantwortlichkeit Testbarkeit:
Business-Funktionen können ohne Datenbank getestet werden Repositories können isoliert getestet werden Orchestrierung kann mit Mocks getestet werden Wartbarkeit:
Änderungen an Geschäftsregeln → nur Business-Funktionen Änderungen an Datenbankstruktur → nur Repositories Framework-Wechsel → nur Repositories und API-Layer Erweiterbarkeit:
Neue Geschäftsregeln → neue Business-Funktionen Neue Datenquellen → neue Repository-Implementierungen Framework-Unabhängigkeit:
Core (Business-Funktionen, Orchestrierung) kann 1:1 auf Yii3 portiert werden Nur Repositories und API-Layer müssen angepasst werden Migrationsstrategie Phase 1: Vorbereitung Interfaces für alle Repositories definieren Entity-Klassen erstellen DTOs (Data Transfer Objects) für Request/Response Phase 2: Repositories extrahieren SQL-Queries aus Buyer::Create() in Repositories verschieben Buyer::Create() verwendet Repositories (noch alte Struktur) Phase 3: Business-Funktionen extrahieren Geschäftslogik in separate Funktionen auslagern Buyer::Create() wird zur Orchestrierung Phase 4: API-Layer anpassen Controller ruft Orchestrierung auf Alte Buyer::Create() wird deprecated Phase 5: Cleanup Alte Implementierung entfernen Tests schreiben Dokumentation aktualisieren Beispiel: Vollständiger Ablauf 1. API Request
POST /api/v1/customers
{
"email": "test@example.com",
"agentid": "10038",
"adresses": [...]
}
2. CustomerController::actionCreate()
- Request formatieren
- BuyerCreationOrchestration aufrufen
3. BuyerCreationOrchestration::createBuyer()
- ValidateBuyerDataFunction aufrufen
- DetermineSponsorFunction aufrufen
- GenerateBuyerIdentifiersFunction aufrufen
- BuyerRepository::create() aufrufen
- AddressRepository::create() aufrufen
- UserCreationOrchestration aufrufen
- CacheService aufrufen
- Response zusammenstellen
4. Business-Funktionen
- Führen Geschäftsregeln aus
- Rufen Repositories auf (nur lesend)
- Keine direkten DB-Zugriffe
5. Repositories
- Führen SQL-Queries aus
- Mappen Daten zu Entities
- Keine Geschäftslogik
6. Response
{
"status": "success",
"data": {
"internekdnr": 12345,
"kundennr": "10038",
"username": "user10038"
}
}
Zusammenfassung Die neue Struktur trennt klar:
API: Request/Response-Handling Orchestrierung: Koordination des Prozesses Business-Funktionen: Geschäftsregeln Repositories: Datenzugriff Framework: Technische Infrastruktur Alle aktuellen Funktionen bleiben erhalten, sind aber klar strukturiert und testbar.
