Facebook
Twitter
Google+
Kommentare
18

Teile und Herrsche! – CQRS, Teil 1

Letzte Woche habe ich eine E-Mail von Nino bekommen, ob ich Interesse hätte an einem fortgeschrittenen Artikel und da wäre ich ja doof, wenn ich „Nein“ sagen würde. Nino hatte auch gleich etwas parat zum Thema „Command Query Responsibility Segregation“. Da ich davon noch nie was gehört hatte und er bereits etwas darüber geschrieben hat, haben wir uns schnell geeinigt, dass wir diesen Artikel doch auch gerne bei uns hätten. Hier ist das Resultat und der zweite Artikel zum Thema wird sicherlich auch nicht lange auf sich warten lassen.

Im Leben eines Entwicklers gibt es immer wieder „Aha“- oder sog. „Breakthrough“-Momente, in denen man entweder etwas entdeckt, dass die eigenen Fähigkeiten beträchtlich erweitert oder lange bestehende Probleme löst.

Hier geht es um ein solches, (noch) kaum bekanntes und bisher selten angewendetes Prinzip für das Design von objektorientierter Software.  Meines Erachtens ist es sehr einfach  zu verstehen und auch sehr einfach anwendbar. Aber noch größer sind die Vorteile die es bietet.

Aber erst mal die Auflösung des Akronyms:

CQS = Command Query Separation  (nach Bertrand Meyer):

“Separate command methods that change state from query methods that read state. But not both.”

Das heißt, die Trennung von Methoden, die entweder Zustände ändern (Befehle verarbeiten) oder den Zustand eines Objekts lesen bzw. darüber berichten.

CQRS = Command Query Responsibility Segregation

Auf Deutsch: Auftrennung der Verantwortlichkeit nach Befehl und Abfrage.

Ist doch das gleiche, oder?

Nicht ganz, bei CQRS ist nicht die Rede von Methoden…

SOLIDe Klassen und Methoden

Bevor ich in die Details gehe, möchte ich darauf hinweisen, dass dem Leser die SOLID Prinzipien [1] und auch Clean Code [2] ein Begriff sein sollten. Zusammengefasst geht es um elementare Prinzipien, mit Hilfe derer man qualitativ hochwertige, d.h. vor allem änderbare, wartbare und testbare Software schreibt.

Schauen wir uns eine oft in dieser oder ähnlicher Form auftretende, „traditionelle“ Klasse eines Models an:

class Customer
{
    private $name;
    private $zipcode;
    private $street;
    private $city;
   
    public function doThis() {
        // …some sophisticated domain logic
        return $yuk;
    }
   
    public function doThat() {
        // …some other sophisticated domain logic
        return $yak;
    }
   
    public function setCity($aCity) {
        $this->city = $aCity;
    }
   
    public function getCity() {

        return $this->city;

    }
   
    // … a bunch of other setters and getters
}

Selbst in diesem rudimentären Beispiel erkennt man beim genaueren Hinschauen mehrere Probleme und Verstöße gegen SOLID:

  1. SRP: was ist die Verantwortlichkeit dieser Klasse?Sie tut mehrere Dinge (doThis, doThat),
    man kann einen ihrer Zustände direkt setzen (setBar) und sie berichtet
    direkt über einen ihrer Zustände (getCity)
  2. Information Hiding – sie exponiert ihren Zustand direkt.
  3. Die Befehlsmethoden doThis und doThat führen zwar eine Anweisung aus, geben aber zugleich auch etwas zurück.
  4. setCity ist fast schon kriminell, denn
    letztendlich soll es eine Änderung (eines Teils) einer Adresse sein, es
    werden aber einzelne Attribute des Konzepts Adresse gesetzt.
    (Aber das ist Thema des kommenden Posts „Das ultimative Ende aller Getter und Setter“…) 

Sowohl SOLID als auch Clean Code propagieren das „Tell, don’t ask!“-Prinzip [3]:

Sag einem Objekt was es tun soll und frage nicht erst, ob es das tun kann (wie elementar wichtig diese Regel ist, werde ich hoffentlich mal getrennt abhandeln).

Und nach CQS sollte eine Methode, die einen Befehl darstellt, keine Informationen zurück liefern bzw. gar keine Rückgabe haben.

Aber das grundlegendste Problem dieser ( und der meisten mir bekannten Klassen oder Modelle) ist die Verletzung von SRP.

Denn die Verantwortlichkeit der obigen Klasse kann man minimal so formulieren als „Die Klasse Foo macht doThis und doThat, und man kann mit ihr bar setzen und lesen“.

Eine einzige Verantwortlichkeit ist das nicht. Überprüft bitte eure bestehenden Modell-Klassen eines aktuellen Projektes. Wie viele davon machen zu viel und verstoßen gegen SRP?

Ein weiteres Beispiel für einen möglichen Service:

class BlogService
{
    public function setPost($post);
    public function deletePost($postId);
    public function getPost($postId);
    public function getAllPosts();
}

Auch hier sehen wir keine klare Verantwortlichkeit. Vielmehr ein Sammelsurium aus mehreren Anweisungen und vieler Abfragen.

Jetzt kommt CQRS ins Spiel.

Im Prinzip geht es darum, die obigen Prinzipien und CQS auf einer höheren Ebene anzuwenden:

„Trenne zwei Endpunkte (oder Objekte) und gib dem einen die Aufgabe, Befehle zu verarbeiten und einem anderen, Abfragen durchzuführen.“


Das ist die Essenz  von CQRS.

CQRS ist eine m.E. logische Weiterentwicklung von CQS, SOLID, Clean Code und Domain Modeling.

Wenn obige Methodiken und Prinzipien eine Trennung von Anweisungen (commands) und Abfragen (queries) auf Methodenebene propagieren, macht CQRS dies auf Klassen- bzw. sogar Architekturebene.

Verdopple deine Klassen

Was bedeutet die Anwendung von CQRS in der Praxis?

Zu aller erst mal nur, dass man seine Klassen (Service- als auch Domain-Klassen) klar nach der jeweiligen Verantwortlichkeit trenne sollte. Also nichts anderes, als das wir aus jeder Klasse zwei machen, so dass sie entweder nur Befehle verarbeiten (also Zustände ändern)  oder Abfragen durchführen.

Aus dem einen Service BlogService wird dann:

class BlogService
{
    public function setPost($post);
    public function deletePost($postId);
}
class BlogReadService
{
    public function getPost($postId);
    public function getAllPosts();
}

Jetzt hat jede von beiden zumindest eine fokussierte  Aufgabe: die eine führt alle möglichen Befehle im Zusammenhang mit Posts durch und die andere berichtet uns darüber.

„Aber dann habe ich ja doppelt so viele Klassen wie vorher!“


Ist das relevant?

Entweder halten wir grundlegende, inzwischen allgemein akzeptierte, Prinzipien guter OOP ein oder behaupten es nur und mauscheln uns weiter durch. Wenn die Anzahl der Klassen das größte Problem ist, dann ist es wohl eher ein Luxusproblem.

Und dabei werden nicht nur die bereits oben erwähnten Prinzipien befolgt, sondern auch noch viele Weitere, wie OCP oder ISP.

[Ha, nun habe ich doch wieder Getter benutzt. Hier sind sie aber harmlos, da sie nur Zustände reporten, nicht aber Vermutungen anstellen, um darauf basierend etwas mit den abgefragten Zuständen zu tun]

Was bringt das alles?

Selbst diese simpelste Form von CQRS bringt enorme Vorteile und beseitigt viele Nachteile einer “traditionellen” (meist dreischichtigen) Architektur.

Simples CQRS ähnelt übrigens sehr dem MVC Pattern:

Auch dabei wird eine Trennung von Befehlen (controller) zu Abfragen bzw. Ansichten der Daten (view) bereitgestellt.

CQRS geht noch einen Schritt weiter und führt eine zusätzliche vertikale Trennung ein:

Daneben gibt es noch etliche weitere Vorteile:

  • Transaktionen können wesentlich spezieller und isolierter ausgeführt werden
  • Module, Klassen und Methoden sind wesentlich expliziter, da sie nur auf ganz spezielle Aufgaben fokussiert sind
  • Die eigentliche Businesslogik wird nicht durch unnötige Abfragen aufgebläht
  • Man kann sich mehr auf Konsistenz statt auf Skalierung konzentrieren

Oder noch spezieller

  • Lesevorgänge erfordern kein kompliziertes und langsames Umwandeln komplexer Objektgraphen zu DTOs
  • Es können hoch performante Abfragen erzeugt und eigesetzt werden
  • Skalierung kann bei Abfragen praktisch unbegrenzt erfolgen
  • Es können spezialisierte Persistenzmethoden für die jeweilige Aufgabe eingesetzt werden (DB, Key-Value-Store, DocDB, 1NF, 3NF)

Fazit und Vorschau

Natürlich hat CQRS, wie alles, seine Grenzen, Ausnahmen und auch seinen Preis.

Da es aber ein sehr rudimentäres, klares und auf bekannten Prinzipien  basierendes Konzept ist, sind diese fast zu vernachlässigen.

Die Vorteile sind fast unglaublich.

Ich setze es nun bereits länger ein und bis auf die nötige Umstellung auf eine andere Denkweise bei der Entwicklung von Softwareanwendungen, gab es kaum praktische Probleme. Es war auch völlig egal, ob man das Prinzip in kleinen oder sehr komplexen Projekten einsetzt. Bei steigender Komplexität sind die Vorteile auf Dauer noch evidenter.

Ein weiteres Thema, das oft als „Konsequenz“ oder Erweiterung zu CQRS angesehen wird, ist Domain Eventing mit Commands und Events. Oder Event Sourcing, ein Ansatz zur ausschließlichen Speicherung von Zustandsänderungen von Objekten.

Falls genügend Interesse zu diesen weiterführenden Themen oder zu CQRS vorliegt, kann ich gerne die entsprechenden Artikel veröffentlichen.

[1] http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29

[2] http://www.amazon.de/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882

[3] http://pragprog.com/articles/tell-dont-ask

Über den Autor

Nino Martincevic

Kommentare

18 Comments

  1. Sollte die Klasse „BlogService“ in deinem Beispiel später nicht besser „BlogWriteService“ heißen? Wäre doch noch einen titsch konsequenter, einmal BlogReader, einmal BlogWriter, dann wäre auch Platz für einen BlogIrgendwas, der was-weiß-ich-für-aufgaben übernehmen kann.

    Ich muss zugeben, mein erster Gedanke war „ui, _noch_ mehr Klassen, die immer weniger machen“, aber der Gedanke ist beim zwei-dreimaligen Brainstorming nicht mehr ganz so fremd.

    Danke für den Artikel, den nächsten lese ich ganz bestimmt auch noch. Beim nächsten Projekt versuche ich es mal umzusetzen.

    Reply
  2. >> Lesevorgänge erfordern kein kompliziertes und langsames Umwandeln komplexer Objektgraphen zu DTOs

    Widerspricht dieser Satz nicht der Grafik(image004.jpg). Auf der rechten Seite der Grafik ist doch ganz gut zu sehen das hier DTOs zurück gegeben werden. Sollte es denn nicht heißen „Schreibvorgänge erfordern kein kompliziertes und langsames Umwandeln komplexer Objektgraphen zu DTOs“. Denn wenn ich die Grafik richtig interpretiere, dann werden auf der linken Seite Daten gesendet und auf der rechten Seite Daten gelesen.

    >> Es können hoch performante Abfragen erzeugt und eingesetzt werden
    Das verstehe ich nicht. Was macht hier Abfragen performanter, gegenüber anderen Architekturen/Prinzipien?

    Reply
  3. @Christian

    Nein, das widerspricht sich nicht.
    Für beide Fragen ist die Antwort: wir haben (mindestens) zwei Arten der Datenhaltung, eine für die eigentlichen Zustandsänderungen und eine für das reine Reporting.
    Mache ich, wie herkömmlich, meine Lesevorgänge auf nur einer Datenbasis, muss ich mit Joins etc. arbeiten, was bei Einsatz eines ORMs zu komplexen Graphen führen kann.

    Habe ich aber für die Lesevorgänge eine getrennte Schicht, wie in
    „Es können spezialisierte Persistenzmethoden für die jeweilige Aufgabe eingesetzt werden (DB, Key-Value-Store, DocDB, 1NF, 3NF)“ beschrieben, kann ich genau passende Abfragen und damit auch einfachere DTOs bauen, die sehr schnell genau das liefern, was angesehen werden soll (Stichwort: Persistent View).

    Hope that helps.

    Reply
  4. @Sascha

    Kann man so machen, klar.
    Hängt von den persönlichen und/oder vorgegebenen Business-Präferenzen und Namenskonventionen ab. Und davon, welche Bedeutung das Domain Model hat.

    Reply
  5. Für Lese- und Schreibvorgänge sollten also zwei verschiedene Klassen benutzt werden? Mehr als eine optische Trennung von Grundaufgaben ist das für mich nicht. In den meisten Fällen wäre dies sicherlich ein Overkill. Wir versuchen, Dinge immer weiter zu abstrahieren, bis irgenwann jedes Objekt nur noch ein, zwei Methoden hat, die allenfalls bei DI zu Trage kommen und die Wartung erschweren. Jede weitere Klasse erhöht außerdem die Komplexität.

    Wie geht man vor, wenn die Klasse, die für das Löschen von Posts (deletePost()) zuständig ist, wiederum einen Wert zurückliefern soll (etwa die Rückgabe der affected rows). Muss BlogService dann wiederum eine Instanz von BlogReadService aktivieren?

    PS: Warum ist ::setCity „kriminell“? Ich habe die Begründung nicht verstanden.

    Reply
  6. @Daniel

    Die Antwort darauf sind die SOLID-Prinzipien. Wenn man die konsequent befolgt, kann es eigentlich nur so sein.
    Wie sollte man sonst SRP oder OCP einhalten?

    Die Wartung wird nicht erschwert, sondern erleichtert. Je kleiner und spezieller einzelne Klassen und Methoden und Klassen, desto test- und wartbarer, das ist ja nichts neues.
    Und die Klassen werden nicht nur eine Methode haben, sondern bilden das gesamte Verhalten des Objekts ab, können also viele Methoden besitzen.
    Sie haben aber keine „Getter“.

    Tatsächlich erhöhen nicht viele Klassen die Komplexität sondern wenige zu große. Wir alle kennen jede Menge „Gott“-Klassen mit ellenlangen Methoden und tausenden von Zeilen. Abgesehen von der nicht vorhandenen Lesbarkeit sind sie der Antichrist der OOP. Fixe einen Fehler, ändere mal ein Detail und such dich tot und hoffe, dass du nichts anderes kaputt machst.
    Das ist das eigentliche Armageddon der Komplexität von allwissenden und allesmachenden Klassen.

    Es gibt wahrscheinlich keine Klasse, die nur für das Löschen zuständig ist, sondern eine für das eigentliche Verhalten und andere für beliebige Reportings (Lesevorgänge).
    Warum sollte die Methode deletePost() etwas zurückliefern sollen? Sie soll was tun, das reicht doch.

    Und CQS (und neben vielen anderen auch Clean Code) sagt aus, dass eine Methode eben etwas tut oder etwas berichtet. Würde sie beides tun, hätte ich ebenfalls wieder zwei Gründe, die evtl. geändert werden müssten.
    Und man verhindert damit auch die allgegenwärtigen Verstöße gegen das „Tell, don’t ask“-Prinzip.

    Wie im Artikel gesagt, ist MVC ja auch nichts anderes. Nur meistens noch zu grob gehandhabt.

    Reply
  7. SetCity ist ein (häufiges) Beispiel für rein datenzentrierte Programmierung, die aber einem elementaren Irrtum aufsitzt.

    Das Problem ist: Objekte, die mehrere Attribute besitzen, die konzeptionell nur in der Gesamtheit einen Sinn machen, sollten auch nur zusammen geändert werden, nicht in Einzelteilen.

    Gegeben sei eine Adresse bestehend aus PLZ, Ort, Land und Straße/Hausnummer. Kann man nur die PLZ alleine ändern?
    Macht diese ohne die Angabe des Landes einen Sinn? Oder Straße für sich alleine?
    Nein. Nur die Summe aller Attribute formen das Ganze.

    Ergo kann es eine Methode setAddress($address) geben aber nicht einen Setter für jedes einzelne Attribut.

    Vergesse ich nämlich eines davon oder macht eines in der Gesamtheit keinen Sinn, erhalte ich de facto ein ungültiges Adressobjekt.
    Behandle konzeptionell Zusammenhängendes auch immer zusammen oder garnicht, sonst kommst du in die Hölle der Inkonsistenz.

    Reply
  8. >> Gegeben sei eine Adresse bestehend aus PLZ, Ort, Land und Straße/Hausnummer. Kann man nur die PLZ alleine ändern?
    Macht diese ohne die Angabe des Landes einen Sinn? Oder Straße für sich alleine?
    Nein. Nur die Summe aller Attribute formen das Ganze.

    Das klingt logisch, aber nur im Objektkontext. Wenn die Daten aus einem Formular stammen, in dem der Ort ein Extra-Feld besitzt, so würde diese Information doch von Beginn an separat vorhanden sein? Würde es zu einer Bündelung aus dem Formular kommen, wäre die Reihenfolge doch:

    (Formular) Getrennt -> (Verarbeitung und Model) Gebündelt -> (Zuweisung zum Property und Persistenz) Getrennt.

    Bei einer Abfrage aus einer Datenbank, um ein Formularfeld vorab-zu-befüllen, wäre die Reihenfolge
    (Datenbank) Getrennt -> (Datenholen aus dem Model) Gebündelt -> (Verarbeitung für Formularfeldbefüllung) -> Getrennt

    Das ist doch ein einziges hin-und-her ohne erkennbaren Mehrwert. Ich spendiere meinem Model immer einen Getter für die gewollte, zusammenhängende Ausgabe einer Adresse, etwa
    public function getAddress() {
    return $this->street . „\n“ . $this->zipcode . ‚ ‚ . $this->city;
    }

    Reply
  9. $address = new Address($country, $zip, $city, $street);
    (oder über eine Factory)

    Es werden alle Attribute zusammen überprüft. Ist alles valide und konsistent so bekommst du ein Adressobjekt, sonst nicht.

    Dein getAddress liefert keine Adresse, sondern einen String.
    Wenn in deiner Applikation Adresse nur ein String ist, dann wäre es ok.
    Andernfalls ist es nicht wirklich eine Adresse.

    Ich kann dir nur empfehlen, dir die SOLID-Prinzipien anzuschauen:
    http://de.wikipedia.org/wiki/Prinzipien_Objektorientierten_Designs

    Und auch Clean Code und Refactoring.
    Man muss an grundlegenden Prinzipien eine Übereinstimmung finden, sonst wird man nicht über darauf aufbauende Themen kommunizieren können.

    Reply
  10. Ich kann deine Begründung zum Verstoß gegen SRP nicht nachvollziehen.
    Wer behauptet, dass die Klasse „Customer“ keine _einzelne_ Verantwortlichkeit besitzt? Die Abstraktion, die der Klasse zugrundeliegt, ist für die Eigenschaft, eine einzelne Verantwortung zu haben, bestimmend. Dass du keine „gute“ Abstraktion, welche der dargestellten Klasse zugrundeliegen könnte, genannt hast, heißt nicht, dass es keine solche gibt.

    Deine erste Klasse „BlogService“ könnte die Verantwortlichkeit, einen Blog zu verwalten, haben. Wohingegen deine zweite Variante diese Verantwortlichkeit in zwei kleinere Verantwortlichkeiten zerlegt. Diese Verantwortlichkeiten können aber weiter zerlegt werden… Die Frage ist: Wo hört man auf?

    Programmierung besteht, zumindest aus meiner Sicht, hauptsächlich darin, geeignete Abstraktionen zu finden, die helfen, das Problem (und deren Lösung!) adäquat zu beschreiben und den zugehörigen Code zielgerichtet zu strukturieren.
    Einfache Abstraktionen helfen mir, mich auf schwierigere Probleme konzentrieren zu können. Mächtige Abstraktionen hingegen, obwohl sie weniger verständlich sind, können größere Problemklassen lösen.

    Ich behaupte, dass erfolgreiches Programmieren darin besteht, einen guten Kompromiss zwischen softwaretechnischen Prinzipien und domänenspezfischen Abstraktionen zu erzielen. Weder das dogmatische Einhalten noch das Ignorieren der softwaretechnischen Prinzipien wird zum Erfolg führen.

    Reply
  11. @Andre

    Es ist relativ einfach:

    „könnte die Verantwortlichkeit, einen Blog zu verwalten“

    „verwalten“ ist das Problem, damit definiert man fast immer Klassen, die zu viel machen.
    Für „verwalten“ könnte man auch „managen“, „steuern“, etc. nehmen.
    Das sind Begriffe, bei denen stets eine Warnlampe aufgehen sollte.
    Denn da fängt die Verletzung von SRP (und generell OOP) an.

    Und die Grenze ist auch relativ klar:
    Auf der eine Seite geht es um Anweisungen, die meine Software verarbeiten soll. Auf der anderen lese ich die Ergebnisse oder Zustände wieder.
    Das sind immer völlig verschiedene Welten. Das fängt bei Schreiben z.B. mit Transaktionssicherheit, Parallelverarbeitung etc. an und auf der anderen Seite (Lesen) mit Skalierbarkeit und Performance.

    Wo die Grenzen all dessen liegen, hängt von der Anwendung und den Anforderungen ab. Für einen Blog mag das zu viel zu sein (obwohl es nicht weh tut) aber für eine komplexe Businessanwendung kann es immer noch nicht genug sein.

    Denn das massive Problem, das meist entsteht, wenn man dies nicht trennt kennt jeder von uns:
    Mit getXXX lesen wir einen Zustand und treffen daauf eine Entescheidung, um weitere Aktionen auszuführen.
    Etwas lesen bedeutet aber immer, dass es unmittelbar danach bereits veraltet und falsch sein könnte!
    Deshalb führen Commands Aktionen aus ohne vorher Vermutungen anstellen zu müssen oder auf Daten zu arbeiten, die bereits veraltet sind.
    Da könnte man jetzt noch eine Menge guter Gründe aufführen, kommt aber im zweiten Teil.

    Programmieren besteht aus meiner Sicht zu aller erst aus nachdenken.
    Und nicht aus Grundsatzdiskussionen. Die SOLID-Prinzipiene sind allesamt aus jahrelanger praktischer Erfahrung von OOP-Experten enstanden und nicht weil es einem Dogma genügt. Aus meiner Sicht sind sie simpel zu verstehen und simpel anzuwenden.

    Der Schlüssel dazu ist übrigens: denke nicht in Daten.

    Reply
  12. Danke für die Antwort. Mir war deine Annahme, dass es sich um nebenläufige oder datenbankgetriebene Anwendungen handelt, nicht klar. Denn in diesem Fall wäre die von mir genannte Abstraktion natürlich „leaky“. In anderen Fällen jedoch nicht.

    „Wo die Grenzen all dessen liegen, hängt von der Anwendung und den Anforderungen ab.“: Das wollte ich mit dem Kompromiss andeuten.

    „Die SOLID-Prinzipiene sind allesamt aus jahrelanger praktischer Erfahrung von OOP-Experten enstanden und nicht weil es einem Dogma genügt.“: Ich bestreite nicht, dass die Erfahrungen von Experten als Heuristik, gute Lösungen zu finden, verwendet werden können. Sie sollten es sogar!
    Allerdings hast du die Kausalität in meiner Aussage umgedreht. Es ging mir darum, SOLID-Prinzipien nicht zu einem Dogma werden zu lassen. Warum? Code, der den SOLID-Prinzipien 100%ig folgt, ist nicht zwangsläufig guter Code. Aber umgekehrt befolgt guter Code die SOLID-Prinzipien in hohem Maße. Ob Code gut oder schlecht ist, ist im wesentlichen kontextabhängig: Kann er das gestellte Problem lösen?

    Reply
  13. Prinzipiell ist die Art der Anwendung nicht maßgeblich.
    Wichtiger ist es zu verstehen, dass die zwei völlig verschiedenen Belange der Commands und Querys eine Trennung bedingen.
    Commands sind das, was den eigentlichen Wert der Software ausmacht.
    Sie müssen erfolgreich sein, hier sind bspw. Transaktionssicherheit und Konsistenz die entscheidenden Kriterien.

    Querys dagegen müssen nicht aktuell oder konsistent sein, sie können jederzeit ausgeführt und neu aufgebaut werden oder auch zeitversetzt sein.

    Umgekehrt werden Querys i.d.R. wesentlich häufiger durchgeführt und haben deshalb oft ganz andere Anforderungen an die Performance und Skalierbarkeit.

    Die letzte Frage ist die Kernfrage von XP oder Scrum: nur der „Geschäftswert“, also die Erfüllung gestellter Aufgaben, macht eine Software gut.

    Allerdings sehe ich SOLID schon als sehr praxistaugliche Prinzipien.
    Denn sie führen zu versteh-, wartungs- und testbarer Software.

    Und es ist wie mit vielen Prinzipien, sie beschreiben oft das Ideal.
    Der Weg dahin ist oft steinig oder verlangt nach Alternativen.

    Reply
  14. Ich fürchte, Deine Beispiele waren didaktisch nicht optimal gewählt: in Deinem Customer-Beispiel werden die Daten in private members vorgehalten, Dein BlogService/BlogReadService-Beispiel dagegen scheint aber die Annahme externer Datenhaltung (Datenbank oder wasausimmer) zu treffen.

    Ich kann mir nicht wirklich vorstellen, wie man das Customer-Beispiel CQRSifien könnte, da PHP keine Friend-Beziehungen zwischen Klassen kennt, welche Klasse soll also die Daten vorhalten? public members zu verwenden kann ja nicht die Lösung sein. Und eine dahintergeschaltete dritte Klasse zur Datenhaltung auch nicht, denn dann bräuchte die ja auch wieder Setter’n’Getter und würde damit dem Grundziel der ganzen Übung widersprechen …

    Reply
  15. @Sven:

    Ich habe gar keine Daten angegeben. Nicht, dass es sie nicht gäbe aber sie spielen für die Erklärung keine Rolle.

    Es geht hier um Verhalten von Objekten, nicht um ihre interne Struktur.
    Das sollte es übrigens immer (mindestens in komplexeren Modellen), ist ja schließlich das Prinzip der Kapselung und des Information Hidings.

    Reply
  16. Erst einmal danke für Deinen Artikel.
    Ich finde es sind sehr interessante Gedanken, die mich inspirieren und auch zum Nachdenken bingen.
    Das SOLID Prinzip kannte ich dabei noch nicht.

    Ich sehe in der Trennung auch die von Dir genannten Vorteile.
    Mich interessiert konkret die weitere Implementierung.
    Der Grund:
    Aus meiner Erfahrung stehe ich bei Abfragen immer vor der Überlegung,
    wann gebe ich nur ein Array von Daten zurück und wann ein oder mehrere Objekte.
    Komplexer wird das, wenn die Daten nicht aus einer Datentabelle stammen sondern aus diversen.
    Ein Teil in mir möchte eine schnelle und performante Abfrage die einfach die Ergebnisse zurück gibt, ohne immer den Overhead der Objekte und ggf. einzelnen SQL Abfragen durchzuführen.
    Ich kenne nicht den Namen des Prinzips aber wenn ich in der Reader Klasse mal Objekte zurück gebe, mal die reinen Daten als Arrays dann ist das auf alle Fälle keine konsistente Schnittstelle.
    Der andere Teil in mir möchte die Daten als Objekte haben.
    Denn ich habe ja in diesen Daten auch Logik.
    Beispiel: getUsername() der die Logik kapselt „if ($this->username !== “) return $this->username; else return $this->email“.
    Diese Logik müsse ich ja mit dem Array immer wieder berücksichtigen….
    Und Code soll ja bekanntlich nur einmal vorkommen. Müsste in Clean Code stehen. 🙂

    Die nächste Frage stellt sich mir, wenn ich mir das Thema mit den Getter und Setter so durchlese.
    Das Objekt das ich aus der Abfrage zurück gebe, das hat ja die Getter und Setter.
    Aber soweit ich das verstehe ist grundsätzlich daran nichts falsch, Du möchtest nur darauf hinweisen, dass gewisse Werte einfach zusammen gehören und nicht einzeln verändert werden sollten.
    Richtig?

    Dein Beispiel ist die Adresse.
    Verstehe ich das richtig, das Du das zwar logisch so in den Models unterteilen würdest (Stichword Klasse Adress) aber das hat nichts mit der Datenspeicherung zu tun.
    Sonst würde ich ja bei einer relationalen Datenbank in die 4te Normalform wechseln, was meist ein unnötiger Aufwand darstellt.
    Auch richtigt?

    Dazu noch ne ganz praktische Frage: was ist wenn ich z.B. Doctrine2 verwende.
    Da repräsentiert ein Model eine Klasse (oder auch anders herum). Ich bin mir jetzt nicht so sicher aber ich glaube ich kann da nicht einfach daraus zwei Klassen machen ohne diese auch in der Datenbank in zwei unterschiedliche Tabellen zu stecken.
    Sollte man dann davon absehen bzw. gibt es dafür eine Lösung?

    Ok, es ist spät… 😉 Ich freue mich auf die Antwort und auch auf den nächsten Artikel.
    Und wie gesagt, eine konkretere Implementierung fände ich hilfreich.

    Reply
  17. @Francois

    Das sind nicht nur Gedanken, sondern ich setze CQRS bereits seit langem bei vielen Projekten ein, derzeit u.a. bei einem sehr komplexen und großen
    Brownfield-Projekt. Da tritt übrigens noch ein anderer positiver Effekt ein:
    es kann schrittweise auch über bestehende oder auch nicht testbare Anwendungen „gebaut“ werden, und damit die Anwendung nach und nach verbessern.

    Bevor ich deine einzelnen Fragen beantworte, solltest Du generell folgende
    Regel beachten, wenn du über Models nachdenkst:
    Es gibt keine Datenbank!

    1) Array oder Objekte als Rückgabe von Abfragen:
    Hängt davon ab, wer der Konsument dieser Abfragen ist.
    Auf der sicheren Seite ist man aber fast immer, wenn man nur mit simplen DTO’s arbeitet.
    In PHP ganz einfach über ArrayObjects implementierbar.
    Ein DTO hat Getter für jedes einzelne Attribut und auch Setter – diese aber nur in dem Kontext, in dem das DTO benutzt wird.
    Soll heißen, der Client erhält ein DTO als Ergebnis einer Abfrage und kann damit machen, was er will. Das DTO kann auch für Transaktionen wieder zurückgegeben werden aber das Domain Model setzt es in eigene Objekte der Domain um.
    Natürlich solltest du dich entscheiden und nur eine Art von Response zurückgeben.

    Aus Abfragen gelieferte DTOs haben niemals Logik, zumindest keine Geschäftslogik.

    Mit dem Absatz Model/Datenspeicherung hast du recht: ein richiges Model weiß nichts davon, welches ORM du benutzt, in welche Tabellen es persistiert wird oder was eine Normalform ist.
    Es kann ja auch in eine Objektdatenbank oder einen Key Value Store gespeichert werden.
    Es spielt auch keine Rolle.
    Und es kann aus einer oder aus Millionen Klassen bestehen.

    Du kannst zwei völlig getrennte Models haben, das ist sogar praktisch die Konsequenz.
    Eines für die Transaktionen (also dem eigentlichen Sinn und Wert der Anwendung) und eines für Abfragen.
    Beide können auf demselben Datenquellen arbeiten aber auch auf völlig verschiedenen und beliebig vielen, die jeweils für den speziellen Zweck optimiert sind.
    Davon sollte das eigentliche Model garnichts wissen.

    In Doctrine kannst du tausende Klassen auf einer Tabelle oder beliebig komplexen Relationen aufbauen. Dazu brauchst du noch nicht mal existierende Tabellen. Im Prinzip geht es ja nur um die Zusammenführung der eigentlich inkompatiblen relationalen und objektorientierten Welten.

    Hope that helps.

    Reply

Leave a Comment.

Link erfolgreich vorgeschlagen.

Vielen Dank, dass du einen Link vorgeschlagen hast. Wir werden ihn sobald wie möglich prüfen. Schließen