3.5 KiB
Backend-Architektur – Veranstaltungs-Tool (Konzept)
Ziel
Mandantenfähiges Backend mit 3 Schichten pro fachlicher Domäne und mehreren Clients/Entry-Points.
Struktur
api/
├── public/index.php ← Entrypoint
└── src/
├── UI/{ClientName}/{FeatureName}/ ← pro Client + Use-Case (PascalCase, deutsch)
│ ├── Controller/ REST-Endpunkte (/api/*), Commands etc.
│ ├── Request/ Request-DTOs
│ └── Response/ Response-DTOs
├── Logic/{FeatureName}/ ← Business Rules pro Use-Case
│ ├── Manager/ Cache, Workflow, Aggregate
│ ├── Calculator/ Berechnungen (Statistiken)
│ ├── Model/ Feature-Modelle
│ ├── Provider/*ProviderInterface Interfaces für Data-Layer
│ ├── Processor/*ProcessorInterface Interface für Verarbeitungslogik
│ └── Shared/ Feature-spezifisches Shared (wenn notwendig)
├── Data/{FeatureName}/ ← Domain-Objects, DB-Zugriff pro Use-Case
│ ├── Entity/ Doctrine Entities
│ └── Repository/ Repo-Interfaces + Implementierungen
└── Shared/ ← Nur wirklich entitätsübergreifende Basics
├── BaseEntity.php
└── UuidGenerator.php
Client-Namenskonvention
API→ REST-Schnittstelle (UI/API/*)Console→ Symfony Commands (UI/Console/*)
Feature-Namenskonvention
Use-Case-basiert, PascalCode auf Deutsch. ZB:
Personen(nicht Personenliste / PersonController)Veranstaltungen(nicht VeranstaltungenIndex)Anwesenheit(nicht AnwesenheitsstatusErfassung)
Beispiel-Pfad: UI/API/Personen/Controller/PersonenController.php
Model/ pro Feature – Ordner mit ggf. mehreren Models definiert die Feature-Grenze.
Entry-Points
Mehrere Clients via Router-basierte Routing:
/api/*→ REST-Controller (UI/API/{Feature}/Controller/)- Console → Symfony Commands (
UI/Console/{Feature}/Command/) - Messages → Messenger Handler
Entscheidungen
Provider und Processoren
Provider implementieren die *ProviderInterface. Sie rufen Daten ab – per Doctrine Repositories/Entities oder über API-Aufrufe. Das Mapping zwischen Entities und Model-Objekten erfolgt in separaten Mapper-Klassen, die in Data/ liegen. Provider binden so die Data-Schicht an die Logic-Schicht.
Processoren funktionieren analog für schreibende Prozesse (*ProcessorInterface).
Shared-Klassen
Echt entitätsbergreifende Basis-Klassen (BaseEntity, UuidGenerator) leben in src/Shared/. Feature-spezifisches Shared liegt innerhalb des Features, zB:
src/Logic/Veranstaltungsmanagement/Shared/JsonSerializer.php
Multi-Tenant Scoping
Alle Repository-Queries werden per Doctrine SQL Builder Trait mit mandant_id gefiltert. Der Scope wird automatisch angehängt – sowohl lesend als auch schreibend. Tenantübergreifende Operationen sind technisch nicht möglich, da alle Repos implicit scopen. Das schützt vor unbeabsichtigten Mandanten-Überschneidungen. Bei Bedarf kann eine explizite WithoutScope-Schicht eingeführt werden.
Dependency Injection
Nur explizit über services.yaml, wenn notwendig. Standardmäßig wird Auto-Wiring mit Konventionen verwendet (*ProviderInterface, *ProcessorInterface).