Skip to content

Appiyon Architectural Laws ​

Version: 3.0 Last Updated: 2025-10-24 Framework: Symfony 7.3 + Doctrine ORM Purpose: Die fundamentalen architektonischen Gesetze von Appiyon fΓΌr KI-Agenten und Entwickler


πŸ—οΈ Die 6 Layer ​

Infrastructure  β†’ Admin-System (separater Guard, KEINE Principals)
    ↓
Shared          β†’ Enums, Traits, Identity (teams, principals, users, CRM)
    ↓
Foundation      β†’ Basis-Bausteine (Tenant, User, Media, Communication)
    ↓
Core            β†’ Primitive Data Models (Country, Language, Category)
    ↓
Domain          β†’ Business Logic (App, Developer, Review)
    ↓
Dev             β†’ Framework-Integration (Controllers, Commands, EventSubscribers)

πŸ”΄ Critical Laws (NIEMALS brechen) ​

Law 1: Layer Dependencies ​

Layers dΓΌrfen nur tiefere Layers importieren

βœ… Domain β†’ Core β†’ Foundation β†’ Shared β†’ Infrastructure
❌ Core β†’ Domain
❌ Foundation β†’ Core
❌ Shared β†’ Foundation

Beispiele:

php
// βœ… RICHTIG - Domain importiert Core
namespace App\Appi\Domain\App\UseCase;
use App\Appi\Core\Category\Entity\Category;

// ❌ FALSCH - Core importiert Domain
namespace App\Appi\Core\Category\UseCase;
use App\Appi\Domain\App\Entity\App; // VERBOTEN!

Grund: Verhindert zirkulΓ€re AbhΓ€ngigkeiten und hΓ€lt die Architektur sauber.


Law 2: Event-Only Cross-Domain Communication ​

Domains kommunizieren NUR via Events

php
// ❌ FALSCH - Direkter Domain-zu-Domain Call
namespace App\Appi\Domain\Finance;
use App\Appi\Domain\Automotive\Service\RepairService;

class InvoiceService {
    public function __construct(
        private RepairService $repairService // VERBOTEN!
    ) {}
}

// βœ… RICHTIG - Via Events
namespace App\Appi\Domain\Finance;

class InvoiceListener {
    #[AsEventListener]
    public function onRepairCompleted(RepairCompletedEvent $event): void {
        // Erstelle Rechnung basierend auf Event
    }
}

Innerhalb EINES Domain-Moduls: Synchrone Calls erlaubt

Grund: Domains bleiben entkoppelt und kΓΆnnen unabhΓ€ngig entwickelt werden.


Law 3: Infrastructure Layer Isolation ​

Infrastructure = Separates Admin-System

php
// βœ… RICHTIG - Infrastructure mit eigener admins-Tabelle
namespace App\Appi\Infrastructure\Admin\Entity;

#[ORM\Entity]
#[ORM\Table(name: 'admins')]
class Admin {
    // Eigene Tabelle, KEIN Principal
}

// ❌ FALSCH - Admin als Principal
class Admin {
    #[ORM\OneToOne(targetEntity: Principal::class)]
    private Principal $principal; // NEIN!
}

Infrastructure-Layer:

  • βœ… Hat eigene admins Tabelle
  • βœ… Hat eigenen Authentication Guard
  • βœ… Zugriff auf GLOBALE Infrastruktur
  • ❌ NIEMALS Zugriff auf Mandanten-Daten
  • ❌ KEINE Verwendung von Principal-System

Grund: Admins verwalten Infrastruktur, nicht Mandanten-Daten.


Law 4: Shared Layer = Identity + Utilities ​

Shared enthΓ€lt universelle Komponenten

Shared Layer enthΓ€lt:

  • βœ… Identity-System: teams, principals, users, crm_entries
  • βœ… Enums: PrincipalType, EntityStatus, LocationType, CommunicationType
  • βœ… Traits: AuditableTrait, SoftDeleteTrait
  • βœ… Contracts: Interfaces fΓΌr alle Layer
  • βœ… Exceptions: DomainException, ApplicationException
  • βœ… Events: BaseDomainEvent, AuditableInterface
  • βœ… Audit-System: AuditLog Entity

Grund: Shared stellt fundamentale Bausteine fΓΌr alle Layer bereit.


Law 5: Principal-Based Identity ​

Principal = Universelle IdentitΓ€t (in Shared Layer)

php
#[ORM\Entity]
#[ORM\Table(name: 'principals')]
class Principal {
    #[ORM\Column(type: 'string')]
    private string $principalType; // 'person' or 'organization'
}

#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User {
    #[ORM\Id]
    private int $principalId; // Primary Key = Principal ID
}

#[ORM\Entity]
#[ORM\Table(name: 'crm_entries')]
class CrmEntry {
    private int $principalId;
    private int $teamId;
    private int $roleTypeId; // customer, supplier, employee, developer
}

// ❌ FALSCH - Separate Tables
#[ORM\Entity]
class Customer { } // NEIN! Nutze CrmEntry

Grund: Eine Person/Organisation = Ein Principal mit vielen Rollen.


Law 6: No team_id in Foundation/Core/Domain ​

team_id existiert NUR in Dev Layer

php
// ❌ FALSCH - team_id in Core
namespace App\Appi\Core\Category\Entity;
class Category {
    private int $teamId; // VERBOTEN!
}

// βœ… RICHTIG - Core ohne team_id
namespace App\Appi\Core\Category\Entity;
class Category {
    // Universell, KEIN team_id
}

// βœ… RICHTIG - Dev mit team_id
namespace App\Appi\Dev\Catalog\Entity;
class Item {
    private int $teamId; // OK in Dev!
    private int $categoryId; // Referenz zu Core
}

Ausnahme: Shared/Identity (verwaltet Teams)

Grund: Foundation/Core/Domain sind universell. Kontext kommt in Dev.


Law 7: Authentication in Shared, Authorization in Dev ​

Auth-System ist zweigeteilt

php
// βœ… Authentication in Shared
namespace App\Appi\Shared\Identity\UseCase;
class AuthenticateUserHandler { }

// βœ… Authorization in Dev
namespace App\Appi\Dev\Http\Controller;
class ItemController {
    public function create() {
        $this->denyAccessUnlessGranted('ITEM_CREATE');
    }
}

// ❌ FALSCH - auth() in Core
namespace App\Appi\Core\Category\UseCase;
class CreateCategoryHandler {
    public function handle() {
        $user = $this->security->getUser(); // VERBOTEN!
    }
}

Grund: Business Logic bleibt testbar und kontext-frei.


Law 8: Enums statt Lookup-Tables ​

Enums in Shared, keine ID-basierten Lookups

php
// βœ… RICHTIG - Enum nutzen
use App\Appi\Shared\Enums\PrincipalType;

#[ORM\Entity]
class Principal {
    #[ORM\Column(type: 'string', enumType: PrincipalType::class)]
    private PrincipalType $principalType;
}

// Migration mit CHECK Constraint
CREATE TABLE principals (
    principal_type VARCHAR(50),
    CONSTRAINT chk_type CHECK (principal_type IN ('person', 'organization'))
);

// ❌ FALSCH - Lookup-Table
CREATE TABLE principal_types (id SERIAL, name VARCHAR);

VerfΓΌgbare Enums: PrincipalType, EntityStatus, LocationType, OperationalStatus, CommunicationType

Grund: Enums sind typsicher, kein JOIN-Overhead, klare Validation.


Law 9: CQRS Pattern with Commands & Handlers ​

Use Cases folgen Command/Handler Pattern

php
// βœ… Command (DTO)
final readonly class CreateAdminCommand {
    public function __construct(
        public string $name,
        public string $email,
        public string $password
    ) {}
}

// βœ… Handler
final readonly class CreateAdminHandler {
    public function handle(CreateAdminCommand $command): Admin {
        // 1. Validation
        // 2. Business Rules
        // 3. Create Entity
        // 4. Persist
        // 5. Dispatch Event
    }
}

// ❌ FALSCH - Business Logic im Controller

Grund: Klare Trennung von Input, Business Logic, Output.


Law 10: Doctrine statt Active Record ​

Entities sind reine Datenstrukturen

php
// βœ… RICHTIG - Doctrine Entity
#[ORM\Entity]
class Admin {
    private ?int $id = null;
    private string $name;

    public function getName(): string { return $this->name; }
}

// βœ… Business Logic in Handlers
class CreateAdminHandler {
    public function handle() {
        $admin = new Admin();
        $this->repository->save($admin);
    }
}

// ❌ FALSCH - Active Record (Laravel)
class Admin extends Model {
    public static function create() { } // NO!
    public function save() { } // NO!
}

Grund: Doctrine Pattern. Entities = Daten, Repositories = Persistence.


Law 11: Repository Interfaces + Doctrine Implementation ​

Repositories immer als Interface definieren

php
// βœ… Contract
interface AdminRepositoryInterface {
    public function save(Admin $admin): void;
    public function findById(int $id): ?Admin;
}

// βœ… Implementation
class AdminRepository implements AdminRepositoryInterface {
    // Doctrine-spezifische Implementation
}

// βœ… Services nutzen Interface
class CreateAdminHandler {
    public function __construct(
        private AdminRepositoryInterface $repository
    ) {}
}

Grund: Testbarkeit, Austauschbarkeit, klare Contracts.


Law 12: Events mit AuditableInterface ​

Wichtige Domain Events implementieren AuditableInterface

php
final class AdminCreatedEvent implements AuditableInterface {
    public function getEventName(): string {
        return 'admin.created';
    }

    public function getAuditableType(): string {
        return Admin::class;
    }

    public function getAuditData(): array {
        return ['name' => $this->admin->getName()];
    }
}

Naming: {entity}.{action} (admin.created, user.authenticated)

Grund: Automatisches Audit-Logging fΓΌr alle wichtigen Events.


πŸ“‹ Quick Reference ​

FeatureInfrastructureSharedFoundationCoreDomainDev
Imports-InfraSharedFoundCoreDomain
team_id❌CrmEntryβŒβŒβŒβœ…
EnumsβŒβœ…usesusesusesuses
AuthOwn Guardβœ…βŒβŒβŒβœ… (authz)
APIsβŒβŒβŒβŒβŒβœ…
ControllersβŒβŒβŒβŒβŒβœ…

Diese Laws sind die Grundlage fΓΌr Code-Generierung. Bei Unsicherheit: Frage nach!

Built with VitePress