Ordnerstruktur für DDD in Symfony
Eine klare und effektive Organisation deiner Symfony-Anwendung nach DDD-Prinzipien.
Einleitung
Im Domain-Driven Design werden Software-Projekte in sogenannte Schichten unterteilt. Jede Schicht hat dabei klar definierte Aufgaben.
Überblick über die verschiedenen DDD-Schichten
Eine DDD-orientierte Symfony-Anwendung sollte in vier Hauptschichten unterteilt werden:
- Domain: Enthält die Kerngeschäftslogik, unabhängig von Framework und Infrastruktur
- Application: Verbindet die Domäne mit der äußeren Welt, enthält Anwendungsfälle und Orchestrierung
- UI: Die Eingangsschicht kümmert sich um User-Eingaben, egal ob über HTTP, CLI, Forms, Views, etc.
- Infrastructure: Enthält technische Details, Framework-spezifischen Code und externe Integrationen
Diese Schichtenarchitektur folgt dem Prinzip der Abhängigkeitsregel: Abhängigkeiten dürfen nur nach innen zeigen, niemals nach außen. Die Domain-Schicht ist die innerste Schicht und hat keine Abhängigkeiten zu anderen Schichten. Die Application-Schicht hängt von der Domain-Schicht ab, und die UI- und Infrastructure-Schicht hängen von beiden ab.
Diese Architektur sorgt für Übersichtlichkeit sowie bessere Wart- und Erweiterbarkeit – durch eine sinnvolle Trennung von Verantwortlichkeiten, also durch Kapselung. Die Domänenlogik kann unabhängig von der Infrastruktur getestet werden.
Wir verwenden hier im Folgenden für die Benamung der Schichten die eingedeutschten Ausdrücke für Application (Anwendung) und Infrastructure (Infrastruktur).
Grundlegende Ordnerstruktur
Hier siehst du meine Empfehlung für eine grundlegende DDD-orientierte Ordnerstruktur.
src/
├── Domain/ # Domänenschicht - Geschäftslogik
│ ├── Model/ # Domänenmodelle und Entitäten
│ ├── Service/ # Domänenservices
│ ├── Repository/ # Repository-Interfaces
│ ├── Event/ # Domänen-Events
│ ├── Exception/ # Domänenspezifische Exceptions
│ └── ValueObject/ # Wertobjekte
│
├── Application/ # Anwendungsschicht - Use Cases
│ ├── Command/ # Kommandos (Schreiboperationen)
│ ├── Query/ # Abfragen (Leseoperationen)
│ ├── DTO/ # Data Transfer Objects
│ ├── EventHandler/ # Event-Handler
│ └── Service/ # Anwendungsservices
│
├── UI/ # Schicht für alle Entry Points
│ ├── Http/
│ │ ├── Controller/ # Symfony-Controller
│ │ └── Request/ # Custom Request-Klassen (DTOs)
│ ├── Console/ # Symfony-Commands
│ ├── Web/ # Templates, z. B. Twig
│ └── Form/ # Symfony-Formulare
│
└── Infrastructure/ # Infrastrukturschicht - technische Details
├── Repository/ # Implementierungen der Interfaces
├── Persistence/ # Doctrine-Mappings, DB-Config
├── Security/ # Custom Voter, Authenticator etc.
├── Api/ # externe API-Integrationen
└── Messaging/ # Queue, EventBus, etc.
Domain-Schicht
Die Domain-Schicht enthält die Kerngeschäftslogik und sollte unabhängig von Framework und Infrastruktur sein. Hier werden die Geschäftsregeln, Entitäten und Wertobjekte definiert, die das Kerngeschäft repräsentieren. Mehr..
Anwendungs-Schicht
Die Anwendungs-Schicht dient als Brücke zwischen der Domain-Schicht und der äußeren Welt. Sie orchestriert den Fluss von Daten und Befehlen zwischen den Benutzern oder externen Systemen und der Domäne. Mehr..
UI-Schicht
Die UI-Schicht ist die Eingangsschicht der Anwendung. Sie kümmert sich um alle Arten von Benutzereingaben sowie die Darstellung, z. B. über HTTP (Controller), Console, Formulare oder Views. Mehr..
Infrastruktur-Schicht
Die Infrastruktur-Schicht ist die äußerste Schicht einer DDD-Anwendung und enthält alle technischen Details, Framework-spezifischen Implementierungen und Integrationen mit externen Systemen. Mehr..
Ordner-Struktur mit Bounded Contexts
Für größere Anwendungen empfiehlt sich die Organisation nach Bounded Contexts. Ein Bounded Context definiert die Grenzen eines bestimmten Domänenmodells und stellt sicher, dass Begriffe innerhalb dieses Kontexts eine klare und konsistente Bedeutung haben.
src/
├── Beteiligungsverfahren/
│ ├── Domain/
│ ├── Application/
│ ├── UI/
│ └── Infrastructure/
│
├── Zugriffsverwaltung/
│ ├── Domain/
│ ├── Application/
│ ├── UI/
│ └── Infrastructure/
│
├── PlattformAdministration/
│ ├── Domain/
│ ├── Application/
│ ├── UI/
│ └── Infrastructure/
│
├── Behoerdenverwaltung/
│ ├── Domain/
│ ├── Application/
│ ├── UI/
│ └── Infrastructure/
│
└── Shared/ # zwischen den Bounded Contexts geteilte Klassen
├── Identity/ # z. B. UserId, UserGroupId, etc.
├── Event/ # geteilte Events
├── Exception/ # universelle Ausnahmen
└── Utils/ # evtl. TimeProvider, Slugger etc.
Jeder Bounded Context hat seine eigene Ubiquitous Language und sein eigenes Domänenmodell. Die Kommunikation zwischen Bounded Contexts erfolgt über definierte Schnittstellen, wie z. B. Events oder Services.
Beginne mit einer einfachen Struktur und refaktoriere zu Bounded Contexts, wenn deine Anwendung wächst und die Domäne komplexer wird.
Jeder Bounded Context sollte natürlich seinen eigenen sprechenden Namen haben!
Integration in Symfony
Wenn du bereits PSR-4-Autoloading und Symfony 6+ verwendest, musst du für diese Ordnerstruktur keine besonderen Vorkehrungen oder Config-Anpassungen vornehmen. Dank Autowiring, Namespaces und PHP-Attributen wie #[Route]
funktioniert diese Ordneraufteilung quasi out of the box.