© saicle/Shutterstock.com
Wartbares Design dank CQRS

Trennungsfreuden


Getter lesen, Setter schreiben, das weiß doch jedes Kind. Aber was passiert eigentlich, wenn man diese Idee auf eine ganze Anwendung beziehungsweise Architektur anwendet? Wer Buzzwords mag, spricht dann von CQRS. Wir lassen die Buzzwords weg und sehen uns an, was es durch eine klare Trennung von Lese- und Schreibzugriffen zu gewinnen gibt.

Mit den Daten in unseren Applikationen ist es wie mit Quellcode: Es wird häufiger gelesen als geschrieben. Während bei klassischen Enterprise-Anwendungen oft tausende Lesezugriffe auf einen Schreibzugriff kommen (denken Sie an den Kantinenplan, das Herz und die Seele vieler Corporate Intranets), liegt bei aktuellen Webanwendungen das Verhältnis von Lese- zu Schreibzugriffen typischerweise zwischen 7 zu 1 und 10 zu 1.

Der Begriff „Separation of Concerns“, also die Trennung unterschiedlicher Belange, geht auf den Informatikpionier Edsger Dijkstra zurück. Dieser schrieb 1974 in einem Paper: „But nothing is gained – on the con­trary! – by tackling these various aspects simultaneously. It is what I sometimes have called ‘the separation of conerns’, which, even if not perfectly possible, is yet the only available technique for effective ordering of one’s thoughts, that I know of.“ [1].

Über die Jahre hat sich „Separation of Concerns“ zu einer zentralen „goldenen Regel“ beim Programmieren entwickelt. Man kann viele Fehler im Entwurf oder bei der Programmierung damit erklären, dass unterschiedliche Belange nicht sauber voneinander getrennt wurden. „Single Responsibility“, von Robert C. Martin als das „S“ im Akronym SOLID bekannt gemacht, ist übrigens eine Ausprägung von „Separation of Concerns“.

Bertrand Meyer, der Vater der Programmiersprache Eiffel, schrieb in seinem 1988 erschienenen Buch „Object-oriented Software Construction“ [2] ausführlich darüber, dass Methoden entweder Auskunft über den Zustand eines Objekts geben sollen (Getter), oder den Zustand eines Objekts ändern (Setter oder auch Mutatoren genannt). Er schrieb: „Asking a question should not change the answer“ und meinte damit, dass ein Lesezugriff nicht den Zustand eines Objekts ändern darf.

Einer kann lesen, einer kann schreiben

Das mag heute wenig überraschend klingen, denn bewusst oder unbewusst halten sich die meisten Programmierer heute an diese Regel. Es gibt einige Ausnahmen wie etwa Operationen, die unteilbar sein sollen (get and increment) oder die Abfrage der letzten aufgetretenen Fehlermeldung, die typischerweise den „Fehlerspeicher“ löscht und damit den Zustand des Objekts ändert. Wenn aber die Idee der Trennung unterschiedlicher Belange, also der Trennung von Lese- und Schreibzugriffen auf der Ebene einzelner Objekte eine so gute (und zentrale) Idee ist, warum wenden wir sie dann nicht auch auf ganze Applikationen an?

Sehen wir uns einmal ein einfaches Beispiel an: das API eines einfachen Bestellservice. Einige Methoden sind im Listing 1 skizziert.

Listing 1

interface OrderService { public function findOrderById($orderId); public function findOrdersByUserId($userId); public function placeOrder($userId, array $items); public function cancelOrder($orderId); // ... } 

Diese Schnittstelle enthält sowohl lesende als auch schreibende Methoden. Folgen wir dem Prinzip „Command Query Separation“ (CQS) und trennen die lesenden und die schreibenden Methoden auf, dann erhalten wir zwei Schnittstellen (Listing 2).

Listing 2

interface OrderWriteService { public function placeOrder($userId, array $items); public function cancelOrder($orderId); // ...  } interface OrderReadService { public function findOrderById($orderId); public function findOrdersByUserId($userId); // ...  }

Wir wollen hier nicht darauf eingehen, wie man dieses API noch besser machen könnte, etwa durch die Verwendung von Wertobjekten für die einzelnen Identifikatoren. Die Methoden dienen schließlich nur als Beispiel.

Man wird sich nun fragen, was die Trennung von lesenden und schreibenden Methoden hier nun wirklich bringt. Im ersten Schritt könnte man sich schon einmal über einen Sicherheitsgewinn freuen, denn für den lesenden Zugriff braucht die Applikation nun keine Schreibrechte mehr. Das bedeutet weniger Angriffsfläche etwa für SQL Injections, über die zwar an dieser Stelle noch immer unberechtigt Daten gelesen, diese aber nicht mehr verändert werden können.

Da Sicherheit aber (leider) nur selten ein Verkaufsargument ist, blicken wir noch ein wenig weiter hinter die Kulissen unserer fiktiven Applikation. Wir stellen uns vor, dass die Bestellung durch ein Domänenobjekt repräsentiert wird (Listing 3).

Listing 3

class Order { // ... public function __construct($userId) { // ...  } public function addItem(OrderItem $orderItem) { // ...  } public function getTotal() { // ...  } public function finalize() { // ...  } public function cancel() { // ...  } // ... } 

Die Persistenz für eine Bestellung verbirgt sich hinter einer Repository-Fassade (Listing 4).

Listing 4

class OrderRepository { // ... public function addOrder(Order $order) { // ... prepare to persist this order ...  } public function findOrderById($orderId) { $order = new Order($orderId); // ... retrieve data and hydrate order ...  return $order; } public function findOrdersByUserId($userId) { $collection = new OrderCollection(); // ... retrieve orders and add to // collection ...  return $collection; } // ...  }

Wir können also aus dem Repository eine einzelne Bestellung oder eine Sammlung (Collection) von Bestellungen laden. Diese Collection ist, wenn man so will, ein „aufgebohrtes“ Array von Order-Objekten....

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang