Zum Hauptinhalt springen

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:

  1. Domain: Enthält die Kerngeschäftslogik, unabhängig von Framework und Infrastruktur
  2. Application: Verbindet die Domäne mit der äußeren Welt, enthält Anwendungsfälle und Orchestrierung
  3. UI: Die Eingangsschicht kümmert sich um User-Eingaben, egal ob über HTTP, CLI, Forms, Views, etc.
  4. 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.

tipp

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.

hinweis

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.

tipp

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.