Domain-Driven Design in C++: Architektur für komplexe Projekte

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Warum DDD nicht über langweilige Diagramme, sondern über lebendige Architektur ist

\n

Wenn ein C++-Projekt die Größe einer „Spielzeug“-Anwendung übersteigt, stoßen Entwickler oft auf Chaos: Die Geschäftslogik ist über Controller verstreut, die Datenbank diktiert die Klassenstruktur und Neueinsteiger haben Angst, Legacy-Code anzufassen. Domain-Driven Design (DDD) ist keine Sammlung abstrakter Regeln, sondern ein praktisches Werkzeug, das hilft, Komplexität zu bändigen. In diesem Artikel schlüsseln wir auf, wie man DDD in C++ mit konkreten Codebeispielen implementiert und warum dies Ihr Projekt vor dem „großen Schmutzkloß“ rettet.

\n\n

Was ist DDD und warum braucht ein C++-Entwickler es?

\n

Domain-Driven Design (domänengesteuertes Design) ist ein Ansatz, bei dem das Hauptaugenmerk auf dem Modell der Fachdomäne liegt, nicht auf technischen Details. In C++ ist dies besonders relevant: Die Sprache bietet leistungsstarke Werkzeuge (Templates, RAII, Smart Pointer), aber ohne die richtige Architektur wird das Projekt zu einem Monolithen, bei dem die Änderung einer Funktion alles kaputt macht.

\n

DDD bietet zwei Ebenen:

\n
    \n
  • Strategische Modellierung: Aufteilung des Systems in Kontexte (Bounded Contexts) und Konfiguration ihrer Interaktion.
  • \n
  • Taktische Modellierung: Bausteine – Entity, Value Object, Aggregate, Repository, Domain Service.
  • \n
\n

Wir konzentrieren uns auf die taktische Modellierung, da sie konkrete Muster für C++-Code liefert.

\n\n

Schichtenarchitektur: Das Fundament von DDD in C++

\n

DDD setzt eine klare Trennung in Schichten voraus. Eine typische Struktur für ein C++-Projekt sieht so aus:

\n
    \n
  • Domain Layer (Kern): Entitäten, Value Objects, Aggregate, Domänendienste. Keine Abhängigkeiten von Frameworks oder externen Bibliotheken.
  • \n
  • Application Layer: Anwendungsfälle (Use Cases), Orchestrierung. Hängt nur von Domain ab.
  • \n
  • Infrastructure Layer: Implementierung von Repositories (SQL, Redis, Dateien), Arbeit mit externen APIs. Implementiert Schnittstellen aus Domain.
  • \n
  • Presentation Layer: Benutzeroberfläche (Konsole, REST API, GUI).
  • \n
\n

Dies ist keine starre Regel, sondern eine Empfehlung. Die Hauptsache ist, dass Abhängigkeiten nach innen gerichtet sind: Äußere Schichten können von inneren abhängen, aber nicht umgekehrt.

\n\n

Beispiel: Wie man es nicht machen sollte

\n
// ❌ Schlecht: Geschäftslogik mit SQL vermischt\nclass UserController {\n    void createUser(const std::string& name) {\n        // SQL-Abfrage direkt hier\n        db.execute("INSERT INTO users (name) VALUES (?)", name);\n        // Prüflogik auch hier\n        if (name.empty()) throw std::runtime_error("Name required");\n    }\n};\n
\n\n

Beispiel: DDD-Stil

\n
// ✅ Gut: Klare Trennung\nclass User : public Entity<User, int> {\n    std::string name_;\npublic:\n    explicit User(int id, std::string name) : Entity(id), name_(std::move(name)) {\n        if (name_.empty()) throw std::invalid_argument("User name cannot be empty");\n    }\n    const std::string& name() const { return name_; }\n};\n\n// Repository-Schnittstelle (Domain)\nclass IUserRepository {\npublic:\n    virtual ~IUserRepository() = default;\n    virtual void save(const User& user) = 0;\n};\n\n// Implementierung (Infrastructure)\nclass SqlUserRepository : public IUserRepository {\n    void save(const User& user) override {\n        // Nur SQL, keine Geschäftslogik\n    }\n};\n
\n\n

Bausteine von DDD in C++

\n\n

Entity (Entität)

\n

Dies ist ein Objekt mit einer eindeutigen Kennung, die über die Zeit erhalten bleibt. In C++ ist es praktisch, eine basis-Template-Klasse zu erstellen:

\n
template<typename T, typename IdType>\nclass Entity {\n    IdType id_;\npublic:\n    explicit Entity(IdType id) : id_(std::move(id)) {}\n    IdType id() const { return id_; }\n    bool operator==(const Entity& other) const { return id_ == other.id_; }\n                

Blogs

Buchempfehlungen