Follow my new blog

Donnerstag, 24. September 2009

Modelle für Geschäftsanwendungen mit Domain-Driven Design

Software soll man zuerst modellieren, heißt es. Nicht gleich loscodieren, sondern erst irgendwie planen. Dieses Planen bezeichnet man oft als Modellieren. Aber was bedeutet “Modellieren”? Was ist ein Modell für eine Software?

Was ist ein Modell?

Wikipedia sagt zum Begriff “Modell”:

“Von einem Modell spricht man oftmals als Gegenstand wissenschaftlicher Methodik und meint damit, dass eine zu untersuchende Realität durch bestimmte Erklärungsgrößen im Rahmen einer wissenschaftlich handhabbaren Theorie abgebildet wird. Da im Allgemeinen nicht alle Aspekte der untersuchten Realität in Modellen abbildbar sind, wird Modellbildung oftmals als Reduktion, Konstruktion oder Abstraktion bezeichnet.”

Achso, alles klar, oder? ;-) Nein, ich glaube, das verdient noch etwas Übersetzungsmühe. Die wichtigen Begriffe sind aus meiner Sicht:

  • Realität: Die Problemdomäne mit ihren Anforderungen
  • Theorie: Das Modell als eine mit Unsicherheiten/Unvollständigkeiten behaftete Beschreibung der Realität
  • Reduktion: Das Modell beschreibt nur einen Ausschnitt der Realität
  • Abstraktion: Das Modell beschreibt die Realität in allgemeinen Begriffen

Ein Modell beschreibt also einen Ausschnitt der Realität in allgemeinerer Form und unter bewusster Auslassung mancher Details. Modelle konzentrieren sich also auf das Wesentliche aus einem bestimmten Blickwinkel.

image Beispiel: Für die Problemdomäne “Schuss aus einer Kanone” ist die Formel y=x^2 ein Modell, das den Flug der Kanonenkugel beschreibt. Das Modell reduziert die Problemdomäne, weil in ihm z.B. Flugbahneinflüsse durch Wind nicht berücksichtigt werden; und es abstrahiert von den Konkreta Kanone, Pulver, Kugel usw., indem es die Flugbahn mit rein mathematischen Mitteln beschreibt. In der Formel gibt es keine Kanone und keine Kugel und kein Ziel mehr, sondern nur noch Punkte in einem Koordinatensystem.

Der Zweck solchen Modells ist es, eine Domäne in der Realität besser zu verstehen, um dann Antworten zu liefern. Ein Modell hilft also Fragen zu klären, die sich im Detailreichtum der Realität nicht so einfach beantworten lassen, z.B. die, wie eine Kanone ausgerichtet werden muss, um ein Ziel zu treffen.

image Modelle sind damit wie Landkarten. Sie schaffen Überblick. Mit ihnen wird die Planung einer Lösung handhabbar, ohne dass man sie sogleich ausführen muss.

Sie können sich von A nach B einen Weg durch ein Terrain bahnen. Das ist dann schon eine konkrete Lösung. Oder Sie können auf einer Landkarte mit dem Finger wandern. Da gewinnen Sie einen Überblick, erkennen Hindernisse, können Alternativen diskutieren. Im Gelände “implementieren” Sie dann Ihre Weg-Planung. Das geht zwar nicht glatt, aber die Wahrscheinlichkeit großer Überraschungen ist viel geringer als ohne Terrain-Modell. Die Landkarte hilft also bei Beantwortungen von Fragen an ein Terrain.

Gleiches gilt für das Modell eines Hauses. Früher war ein solches Modell materiell. Es sollte z.B. dem Auftraggeber einen Überblick über das spätere Erscheinungsbild geben. Der konnte sich das anhand von Blaupausen schlecht vorstellen. Also baute ein Modellbauer ein Modell zum “Sehen und Anfassen”. Vor der Ausführung konnten anhand des Modells dann Fragen beantwortet werden wie z.B. “Ist die Anordnung der Räume so praktikabel, wie man es sich gedacht hat?” oder “Pass sich das Haus gut in die Umgebung ein?”

Details der Realität wie Baustoffe oder Stabilität blendet so ein Modell aus, es reduziert die Realität. Und die Abstraktion besteht in der Handhabbarmachung. Das Modell ist kleiner als das spätere reale Haus. Es konzentriert sich auch auf das für die Fragestellungen Wesentliche, z.B. Größenverhältnisse oder Stil. Damit dient es einem einfacheren Erkenntnisgewinn als er möglich wäre, würde das Haus erst komplett ausgeführt.

Eine Kulisse ist deshalb kein Modell. Erstens dient sie ohnehin nicht dem Erkenntnisgewinn, zweitens macht sie die Realität nicht wirklich handhabbarer, weil sie Originalgröße hat. Da nützt auch die Reduktion nichts, die eine Kulisse betreibt. Sie besteht z.B. nicht aus dem Baustoff realer Häuser.

Dass dann heute Hausmodell meist virtuell sind, tut ihrem Modellcharakter keinen Abbruch. Im Gegenteil! Die Virtualität betont ihn. Sie reduzieren quasi maximal und können je nach Fragestellungen ganz unterschiedlich abstrahieren, z.B. indem sie sich auf das Aussehen oder die Wärmeleitung oder einen anderen Aspekt konzentrieren.

Modelle in der Softwareentwicklung

Modelle in der Softwareentwicklung müssen dieselben Kriterien wie Modelle in anderen Bereichen erfüllen, um wirklich Modelle zu sein. Sie müssen also reduzieren und abstrahieren und es damit einfacher machen, Fragen zu beantworten bzw. einen Überblick zu gewinnen.

Diese Modelle können sich auf die Problemdomäne beziehen und z.B. einfach nur die Realität des Kunden beschreiben. Dann dienen sie der Analyse. Oder sie können sich auf die Lösung beziehen, d.h. die Software, welche Anforderungen in der Problemdomäne des Kunden erfüllen soll. Dann dienen sie dem Entwurf. Wenn von Softwaremodellierung die Rede ist, sind Entwurfsmodelle gemeint. Darauf konzentriere ich mich denn auch im Folgenden.

Wie könnte nun ein Softwaremodell aussehen? Im Grunde ist alles recht, was reduziert und abstrahiert – und zwar von der Implementation! Ha!

Das müssen wir uns auf der Zunge zergehen lassen: Ein Entwurfsmodell abstrahiert von der Implementation. (Die Reduktion lasse ich mal unter den Tisch fallen, um die Argumentation einfacher zu halten.)

Klar, abstrahieren muss ein Modell, sonst bietet es keinen Vorteil gegenüber der Implementation. Oder anders: Modellierung findet nur dort statt, wo abstrahiert wird. Was nicht abstrahiert, das ist kein Modell.

Klassendiagramme modellieren nicht

Das lässt eigentlich nur eine sehr ernüchternde Erkenntnis zu: Die üblichen Klassendiagramme sind keine Modelle. Sie abstrahieren einfach nicht. Klassendiagramme sind nur eine andere Notation für das, was auch in Quellcode ausgedrückt werden kann – und zwar 1:1. Sie machen das zwar visuell, aber deshalb reduzieren sie allerhöchstens wenig (weil sie Methodenimplementationen auslassen). Vor allem abstrahieren sie dadurch jedoch nicht. Eine Klasse als Rechteck liegt auf keinem höheren Abstraktionsniveau als eine textuell beschriebene Klasse.

Wer direkt mit Klassen seine Software entwirft, der modelliert sie also nicht, sondern legt gleich sozusagen “Stein auf Stein”. Da macht es auch keinen Unterschied, wenn diese Klassen mit CRC Cards im Rollenspiel gefunden werden. Solange Klassen gesucht werden, findet keine Modellierung statt.

Das ist natürlich nicht per se falsch. Aber man sollte sich bewusst sein, dass man eben im Terrain wandert und nicht auf eine Karte blickt. Visualität macht noch kein Modell. Modell ist, was abstrahiert. Egal wie.

Etablierte Modelle der Softwareentwicklung

Nachdem ich nun die für manche wohl heilige Kuh “Klassendiagramm” geschlachtet habe ;-), stellt sich ganz berechtigt die Frage, was denn dann Modelle sein sollen.

imageEin Blick in die Informatikliteratur erhöht die Modellkenntnis, könnte man in Anlehnung an ein juristisches Sprichwort sagen. Da findet sich nämlich z.B. ein Titel wie dieser: “Modellierung – Grundlagen und formale Methoden”.

Darin – soviel sollte inzwischen gewiss sein – findet sich kein einziges Klassendiagramm. Modellierung hat einfach nichts mit einem Programmierparadigma wie der Objektorientierung zu tun. Ja, ich möchte fast sagen, sofern in einer Darstellung Spuren einer Plattform oder eines Implementationsparadigmas zu finden sind, kann es kein Modell sein. Das trifft allemal auf Klassen und ihre Verwandten die Module zu. Klingt rigoros und ist hier bewusst so gemeint. Sie werden sehen, worauf ich abziele.

Wenn kein Klassendiagramm, was aber dann als Modell? Unter anderem bietet das empfehlenswerte Büchlein diese Modellarten:

  • Graphen
  • Endliche Automaten
  • Grammatiken

Wenn Sie einen online Shop programmieren sollen, dann könnten Sie den z.B. als endlichen Automaten modellieren.

image

Das ist eine echte Abstraktion der angestrebten Lösung! So bekommt man Übersicht, so lassen sich Fragen leicht stellen. Das versteht womöglich sogar der Kunde, für den Sie den Shop entwickeln.

Die Visualität der Darstellung macht die Modellhaftigkeit allerdings nicht aus. Der Automat wäre immer noch ein Modell, würden Sie ihn als Tabelle beschreiben:

  Suchen In Warenkorb Zur Kasse Menge ändern Bezahlen
Z1 Z1 Z2      
Z2 Z2   Z3    
Z3       Z3 Z1


Warum nun aber ein Modell für den Shop und nicht einfach nur ein hübsches Klassendiagramm? Der Sprung wäre zu weit von der Problemdomäne direkt in die Lösungsdomäne. Sie würden quasi mühevoll einen Berg durchtunneln. Den Berg, der das Tal des Problems vom Tal der Lösung trennt.

Mit einem Modell dazwischen machen Sie eine Wanderung vom einen ins andere Tal. Sie arbeiten sich einen Abstraktionsberg hinauf, der Ihnen auf dem Gipfel eine schöne Aussicht auf beide Täler bietet. Zumindest beim Abstieg ins Lösungstal können Sie daher Ihren Weg planen, weil Sie vom hohen Abstraktionsgipfel auf die Niederungen des Lösungsterrains blicken. Außerdem übersehen Sie aber auch noch das Terrain Ihres Aufstiegs. Sie bekommen also auch einen besseren Überblick über das Problem.

In Bezug auf das Shop-Beispiel bedeutet das: Die herausgearbeiteten Ereignisse und Zustände machen dem Kunden klar, wie Sie seine Problemdomäne verstanden haben. Ihm geht jetzt womöglich auf, dass man nach dem Gang zur Kasse keinen weiteren Artikel mehr in den Warenkorb legen kann.

Und Sie haben eine sehr abstrakte Beschreibung Ihrer Lösung, die Sie einfach in Code umsetzen können. Für Zustandsautomaten gibt es etablierte Implementationsstrategien. Die können Sie natürlich als Klassendiagramme skizzieren. Die Ebene des Modells verlassen Sie jedoch damit.

Gleiches gilt, wenn Sie z.B. die Kommunikation zwischen Client und Server als Konversation in einer Sprache modellieren, die Sie mit einer Grammatik beschreiben.

Und es gilt auch, wenn Sie Ihren Ansatz zur Lösung der KataPotter als Graph modellieren.

Spüren Sie doch einmal in sich hinein: Fühlen Sie beim Umgang mit einem echten Modell nicht auch eine gewisse Leichtigkeit? Ist es nicht entspannend, sich nicht mit Klassen herumschlagen zu müssen? Oder wenn, dann sollten die Klassen sich quasi von allein ergeben aus dem Modell – als Implementierungsdetails nach einer Übersetzungsvorschrift. Das kann doch nur der Korrektheit Ihrer Lösung zuträglich sein.

Modelle für Geschäftsanwendungen

Jetzt zur Masterfrage: Wie sollen denn Modelle für Geschäftsanwendungen aussehen? Was, wenn Graphen, Automaten, Grammatiken nicht passen und auch keine griffige Formel Problem bzw. Lösung hübsch abstrakt beschreiben?

Nun, nicht triviale Geschäftsanwendungen bieten sicherlich Platz für mehrere Modelle. Die Benutzerinteraktion mag eben doch passend mit einem endlichen Automaten modelliert werden. Und für die Planung von Ressourcen bietet sich vielleicht ein mathematisches Modell an.

Doch die formalen Modelle aus Informatik und Mathematik decken sicherlich nicht die ganze Anwendung ab. Was also tun mit den Lücken? Oder was tun, um die verschiedenen kleineren Modelle unter einem Dach zusammenzufassen?

imageIch glaube, genau dafür macht ”Domain-Driven Design” (DDD) von Eric Evans einen Vorschlag. Der lautet: Modelliere (Geschäfts)Anwendungen mit Services, Entitäten bzw. Aggregaten und Value Objects sowie den “Hilfsdiensten” Applikationsschicht und Repository.

Dass es sich bei Services usw. um Elemente eines Modells handelt, können sie an der Abwesenheit von Implementationsdetails erkennen. Diese Konzepte haben nichts direkt zu tun mit Klassen. Sie können Service, Entität oder Repository auch mit C implementieren. Mit C# mag es leichter fallen, doch Objektorientierung ist nicht zwingend nötig.

Die dynamischen Aspekte eines online Shops mögen Sie nun also mit einem endlichen Automaten modellieren. Was ist aber mit den statischen Aspekten? Was fließt durch den Automaten? Wer sind die Beteiligten an einem Bestellvorgang?

Hier kommt DDD ins Spiel. Denken Sie nicht sofort in Klassen, sondern in DDD Begriffen. Für die gibt es später Übersetzungen in Klassen. Aber zunächst wollen Sie nichts von diesen Details wissen. Der online Shop lässt sich leichter auf einem höheren Abstraktionsniveau entwerfen.

Fragen Sie also: Was sind die Entitäten? Was sind die Value Objects? Was sind die Services?

Ohne hier näher auf DDD eingehen zu wollen, liegen folgende Elemente eines online-Shop-Modells nahe:

  • Warenkorb-Entität
  • Warenkorbposition-Value-Object
  • Produkt-Entität
  • Kunde-Entität
  • Warenkorb-Aggregat
  • Warenkorb-Repository
  • Produkt-Repository
  • Kunde-Repository
  • Bestellung-Service

Ähnliches hätten Sie auch gleich mit einem normalen Klassendiagramm entwerfen können? Irgendwie natürlich schon. Aber Sie hätten Ihr Denken damit nicht beschränkt. Sie hätten sich schnell in Details verloren. Zum Beispiel frisst “die Persistenzfrage” oft viele mentale Ressourcen.

Mit DDD gibt es “die Persistenzfrage” jedoch im Grunde nicht. DDD sagt: Wo Entitäten sind, da sind Repositories mit ganz einfacher Schnittstelle für die Persistenz zuständig. DDD abstrahiert also von den vielen möglichen Wegen, Daten zu persistieren.

Der Wert eines DDD-Modells liegt also in den Entscheidungen, die es schon für Sie getroffen hat. Wie werden Daten überhaupt zusammengefasst? Wie greift man auf Daten zu? Wie modifiziert man Date? Wie werden Daten persistiert? Darauf hat DDD einfache Antworten. Wer DDD betreibt, tut es dann halt so – und bekommt durch diese Beschränkung – oder besser: durch diese Fokussierung – Freiraum, sich um das Wesentliche der Problemlösung zu kümmern.

Wie endliche Automaten und Grammatiken und mathematische Formeln ist DDD natürlich keine “one size fits all” Modellart. Ob sich ein Game oder eine Maschinensteuerung damit geeignet modellieren lässt, mag bezweifelt werden. Oder vielleicht auch nicht. Denn die Modellelemente von DDD sind so allgemein, dass sie sich womöglich in allen nicht trivialen Anwendungen finden lassen.

Aber einerlei: Auch DDD hat selbstverständlich seine Grenzen.

Dennoch glaube ich, dass es sich lohnt, beim Entwurf von Software Ausschau zu halten nach Anwendungsmöglichkeiten für DDD. DDD bietet ein unaufdringliches Metamodell mit einfach zu verstehenden Elementen. So hilft DDD, Software echt zu modellieren, d.h. sich nicht in Implementationsdetails zu verlieren.

Montag, 21. September 2009

Motivationshappen

Da habe ich angekündigt, dass Teilnehmer von Entwicklerkonferenzen der nächsten Zeit in ihren Taschen Clean Code Developer Mausmatten finden würden – aber ich hab mir keine Gedanken gemacht, wie diese Taschen überhaupt an Teilnehmer kommen. Tststs, wie nachlässig von mir. Das darf ich doch nicht einer unsicheren Chefentscheidung überlassen ;-)

Deshalb hier für die bisher noch Unentschiedenen oder Chefabhängiggen zwei Motivationshappen:

image Wer zur ADC 2009 nach Bonn kommen und auch noch meinen Workshop zum Thema Softwarearchitektur besuchen möchte, der kann sich bei mir per Email melden und bekommt einen Promocode, der ihm oder ihr 200 EUR Rabatt gewährt. Die ADC bietet ein breites Spektrum an Vorträgen zu vielen Themen der .NET Softwareentwicklung. Dieser Happen kann bis zum 25.09.2009 geschnappt werden.

image Wer hingegen oder ebenfalls nach München zur prio 2009 kommen möchte, um sich speziell rund um das Thema “User Interfaces für .NET Software” auf den aktuellen Stand zu bringen, der kann sich ebenfalls bei mir per Email melden und erhält einen Promocode für 400 EUR Rabatt auf den regulären Veranstaltungspreis. Von diesem Happen sind noch 10 auf dem Teller.

Der Rechtsweg ist für beide Angebote natürlich ausgeschlossen.

Also: Ran an die Email! Ich hoffe, wir sehen uns auf zumindest einer der beiden Konferenzen. Die Mauspads wollen zu euch ;-)

Sonntag, 20. September 2009

Clean Code Developer Bausteine immer zur Hand [endlich-clean.net]

Endlich ist es soweit: Clean Code Developer (CCD) bringt seine Bausteine auf den Desktop. Wir haben keine Mühen und Kosten gescheut, um einen Weg zu finden, die Inhalte der CCD-Grade gleichermaßen übersichtlich wie praktisch und auch noch – so hoffen wir – angenehm fürs Auge zu visualisieren. Die tollen Initiativen der CCD-Communities mit Tetraedern, Pyramiden und Mindmaps haben Stefan und mich angeregt und angespornt. Mit dem Professional Developer College haben wir dann einen Sponsor gefunden, der die Entwurfs- und Herstellungskosten trägt.

Das Ergebnis sind zunächst eine Mausmatte:

image

und ein Bildschirmhintergrund:

image

Die Mausmatte werden wir auf den Entwicklerkonferenzen des Herbstes verteilen. Beim .NET Open Space imagehaben wir ein Kontingent dabei, das wir kostenlos an Interessierte  verteilen. Und bei der ADC 2009 imagesowie der prio 2009 imagefinden alle Teilnehmer eine Mausmatte in ihrer Veranstaltungstasche.  Wer bisher noch keinen Grund gesehen hatte, mindestens eine dieser Veranstaltungen zu besuchen, der sollte also jetzt nochmal darüber nachdenken ;-)

Für die Daheimbleibenden mag allerdings der Bildschirmhintergrund ein kleiner Trost sein. Mit dem hat man zwar die CCD-Bausteine nicht im wahrsten Sinne des Wortes “zur Hand”, aber immerhin vor Augen. Hier der die Bitmap in der Auflösung 1200x800 zum Download.

Viel Freude und Erfolg mit diesen CCD-Gedächtnisstützen!

Donnerstag, 17. September 2009

Root Cause Analysis einer Code Kata [endlich-clean.net]

Stefan Lieser tut es nun auch: Er hält seine Codierfinger mit Code Katas geschmeidig. Gerade hat er beschrieben, wie es ihm da bei der KataPotter ergangen ist. Zunächst ist ihm die Lösung leicht gefallen:

“Da ich es gewohnt bin, testgetrieben zu arbeiten, hatte ich mit den ersten Schritten keine Probleme. Die ersten Tests waren schnell erstellt und haben die Implementierung schnell in die richtige Richtung getrieben.”

Später war es nicht mehr so einfach:

“Dann kamen jedoch die komplizierteren Beispiele an die Reihe und ich habe länger mit der Implementierung gekämpft.”

Seine Schlussfolgerung zur Ursache mit den Lösungsschwierigkeiten:

“Ich habe erkannt, dass ich mit dem Refaktorisieren tendenziell zu früh beginne.”

Hört sich gut an, oder? So soll es sein: Erkenntnisgewinn durch Code Kata.

Da ich diese Kata neulich auch gemacht habe und auch Schwierigkeiten hatte, erlaube ich mir jedoch, hinter den Erkenntnisgewinn zu schauen. Ich bezweifle nicht, dass es einer ist und wir alle daraus etwas lernen können: TDD ist kein Selbstzweck; auch mit TDD müssen wir schauen, dass wir Nutzen produzieren, bevor wir innere Code Qualität herstellen.

Aber ich frage mich, ob Stefan damit sein Ursprungsproblem aufgedeckt hat. Hat er hier erfolgreich eine Root Cause Analysis betrieben?

Ich vermute, seine Schwierigkeit ist ein Folgeproblem eines Symptoms, das durch TDD quasi provoziert wird. Das nenne ich jetzt mal das No Design Up Front (NDUF) Symptom.

Aus meiner Sicht ist passiert, was heufig passiert und scheinbar durch TDD auch noch gutgeheißen wird: Stefan hat ein Problem gelesen, kurz darüber nachgedacht, ob er es versteht, und dann mit dem Codieren begonnen in dem Glauben, dass sich schon ein angemessenes Design bei der Codierung ergeben wird. TDD = Test Driven Design.

Der Gedanke ist sicher nicht falsch. Die Frage ist nur, wofür sich ein angemessenes Design durch die kleinen TDD-Schritte ergibt?

Meine Antwort ist: TDD führt zu einem angemessenen Design für das Modell, das man sich von einer Lösung gemacht hat. Nicht mehr, nicht weniger.

TDD ist also ein Werkzeug, das mich eine Zielvorstellung konkretisieren lässt. Mit TDD kann ich ein evolvierbares Design manifestieren. Ich schlage es mit TDD-Schritten sozusagen als Skulptur aus einem Marmorblock heraus.

Tja… was aber, wenn ich einen falschen Marmorblock gewählt habe? Wenn du zu klein ist, dann komme ich nicht zu der Skulptur, die ich gern hätte.

Verräterisch an Stefans Aussage ist, dass die  Tests “die Implementierung schnell in die richtige Richtung getrieben” haben – und er trotzdem am Ende mit den komplizierten Beispielen zu kämpfen hatte. Ich behaupte mal, Stefans anfängliche Tests bzw. die Implementierung haben eben nicht (!) in die “richtige Richtung” gezielt. Nur weil Tests grün waren, heißt das eben nicht, dass irgendwie die Gesamtlösung näher gerückt ist. Denn die Gesamtlösung enthält eben auch und gerade die komplizierten Fälle.

Mein Verdacht ist eher – und den erlaube ich mir, weil ich selbst in diese Falle getappt bin –, dass Stefan keinen Leitstern hatte und damit keine Richtung. Er hatte kein Modell der Lösung, auf das hin er Code geschrieben hat. Er hat nur unmittelbar vor seine Füße geschaut. Dort lag dann immer nur ein nächster Testfall, den er in ad hoc Manier gelöst hat.

Tut man das, dann läuft man bei der KataPotter aber unweigerlich gegen eine Mauer. Ab einem gewissen Punkt muss man mit seiner Lösungsstrategie umschwenken. Da geht es nicht mehr mit “brute force”. Das ist der Fall, wenn der beste Preis für einen Warenkorb nicht der naheliegende ist. Die Kata-Beschreibung nennt diesen Fall explizit.

Und da setzt meine Kritik an: Wider besseren Wissens ist Stefan mit Babysteps losgelaufen und hat einfach Test auf Test gehäuft. Das Problem ist dann am Ende nicht gewesen, dass er zu früh refaktorisiert hat, sondern dass er nicht vor dem ersten Schritt überlegt hat, wie die Lösung im Modell aussehen soll. Der komplizierte Warenkorb hat ihn überrascht wie den geschäftigen Familienvater alle Jahre wieder das Weihnachtsfest.

Dabei glaube ich, dass Stefan schon vor dem ersten Test ein Modell im Kopf hatte. Wenn er die Lösung für den komplizierten Warenkorb selbst gefunden hat, dann hat er auch eine implementierbare Strategie gekannt. Ich sag mal als Stichwort “Baum” ;-)

Warum hat er dann diese Strategie nicht auf einem Blatt aufgezeichnet und überlegt, welche Algorithmen und Datenstrukturen dafür nützlich wären? Warum hat er dann diese Algorithmen und Datenstrukturen nicht vom ersten Test an angepeilt? Auch das hätte kleinschrittig mit TDD geschehen können.

Stattdessen hat er sich sozusagen dümmer gestellt als er war. Er hat sich ganz TDD überlassen in dem Glauben, dass sich durch TDD schon eine Problemlösung ergeben würde. Aber TDD führt nur zu Strukturen, nicht zu Algorithmen. Die Algorithmen, die grundsätzlichen Lösungsansätze, die Modelle, die entstehen im Kopf. Sie sind die Leitsterne für das Voranschreiten mit TDD.

Da liegt für mich das Wurzelproblem. Nicht nur bei Stefan. Ich bin ja selbst in diese Falle getappt. TDD bietet sich so vollmundig als Design-Werkzeug an, dass wir (und sicher auch andere) ihm auf den Leim gehen und allzuleicht glauben, dass wir uns weitere Gedanken ersparen können. Mit TDD gleich loslegen können: das ist so verlockend.

Aber TDD ist immer nur so gut wie das Modell, das ich für eine Lösung habe. TDD ist ein Werkzeug, das mir den Weg zu wartbarem Code für ein gegebenes Modell zeigt. TDD ersetzt die Modellierung aber nicht. Und die lohnt sich eben – wie hier zu sehen ist – auch für ein so kleines Problem wie die KataPotter. Mich lehren meine eigenen und nun auch Stefans Schwierigkeiten deshalb, dass sich ein paar Gedanken zur Lösung immer lohnen. Die Lösung sollte ich im Kopf haben – aber nicht ihre Struktur. Zu der führt mich TDD.

Freitag, 11. September 2009

Code Kata statt Thai Chi vor dem Frühstück [endlich-clean.net]

Statt einer Zeitung lese ich am Morgen vor dem Frühstück gern meine RSS-Feeds. Heute fand ich darin einen Bericht vom ersten Coding Dojo in München. Der hat mich motiviert, die FizzBuzz Kata auch gleich mal zu machen.

Welch erweckende Tätigkeit am Morgen! Statt sich beim Thai Chi im Park die Füße im Gras nass zu machen, lieber im Bett die geistigen Energien in Fluss bringen :-) Eine kleine Kata vor dem Frühstück macht frisch und kregel. FizzBuzz ist nicht schwierig vom Problem her, bietet dem “Testmuskel” aber genügend Widerstand, um einen Übungseffekt zu erzielen.

Mir ist heute dabei z.B. diese kleine Erkenntnis gekommen: Was mache ich eigentlich, ich weiß, das mein Code für Einzelfälle korrekt ist, aber noch prüfen möchte, ob er auch im allgemeinen Fall oder größeren Umfang läuft? Wie sieht sozusagen mein “Induktionsbeweis” aus?

Beispiel FizzBuzz Kata: Ich hatte geprüft, dass meine Klasse FizzBuzzGenerator für 1, 2, 3, 5, 15 den erwarteten Output liefert: “1”, “2”, “Fizz”, “Buzz”, “FizzBuzz”. Der API ist ganz einfach:

var fbg = new FizzBuzzGenerator();

Assert.AreEqual("1", fbg.Next());

Zum Abschluss wollte ich dann noch einen Test mit einer längeren Folge von Zahlen durchlaufen lassen. Die bisherigen Tests hatten eher separat die Generierung von Zahlen und die Prüfung auf Fizz usw. geprüft. Die längere Zahlenfolge war für mich eine Art Integrationstest. Hier der Code:

[Test]

public void Integrationstest()

{

    var sut = new FizzBuzzGenerator();

    var folge = new List<string>() { "1", "2", };

 

    while(folge.Count > 0)

    {

        Assert.AreEqual(folge[0], sut.Next());

        folge.RemoveAt(0);

    }

}

Dabei bin ich über zwei Fragen gestolpert: Wie kann ich bei so einem Test dem Muster red-green-refactor folgen, wenn ich doch schon die Erwartung habe, dass mein Code korrekt ist? Und: Wie kann auch sicher sein, dass mein Testcode korrekt ist?

Zunächst habe ich diese Fragen beiseite geschoben und mir einfach gesagt, dass das hier ein triviales Beispiel sei und es schon einfach laufen würde.

Doch dann haben sich die Fragen von selbst beantwortet.

Die Antworten kamen aus der Folge des erwarteten Output, die zunächst so aussah: “1”, “2”, “Fizz”, “4”, “Buzz”, “6”, “7”, “8”, “Fizz”, “10”, “11”, “12”, “13”, “14”, “FizzBuzz”, …

Wer sieht, worin die Antwort besteht?

Die Antwort lautet: Wenn du eigentlich sicher bist, dass die zu testende Funktionalität korrekt ist, dann baue in die Erwartungen des Tests Fehler ein, um ihn im ersten Anlauf rot zu machen.

Das hatte ich unwillentlich mit meiner Output-Folge getan, weil ich 6,  10 und 12 nicht als “Buzz” bzw. “Fizz” eingetragen hatte. Das doppelt positive Ergebnis des ersten Testlaufs war daher:

  1. Der Test schlug fehl, weil er auf eine inkorrekte Erwartung gelaufen war. Das bedeutete im Umkehrschluss, dass der Testalgorithmus korrekt war, denn sonst wäre er gar nicht erst zu dieser Erwartung gekommen.
  2. Die inkorrekte Erwartung wurde erkannt, so dass auch das System Under Test für diesen bisher nicht getesteten Fall korrekt war.

Nach Korrektur der fehlerhaften Erwartungen lief dann alles glatt und ich war zuversichtlich, dass nun das integrierte Ganze wirklich fehlerfrei war.

So hat eine kleine Code Kata meinen Testmuskel noch vor dem Frühstück sehr angenehm gestärkt und für den Tag fit gemacht. Eine empfehlenswerte Praktik für alle, die für den Frühsport lieber im Haus bleiben wollen ;-)

Samstag, 29. August 2009

Entspannte Persistenz – The Lounge Repository

Relationale Datenbanken sind nicht die ganze Wahrheit für die Datenspeicherung. Das merken immer mehr Entwickler. Die Zahl der “alternativen Datenbanken” nimmt zu und die RDBMS-Frustrierten formieren sich schon: NOSQL ist der Schlachtruf.

image Einige Prominenz hat bei den “alternativen Datenbanken” nun CouchDB erlangt: eine schemalose Datenbank zur Speicherung von Dokumenten. Der API ist denkbar einfach, Queries sind natürlich auch möglich – aber für viele Anwendungen sind Dokumente eine unpassende Abstraktion ihrer Daten. Ein Kunde mit seinen Adressen mag noch als Dokument durchgehen. Doch wie ist es mit einem Kunden und seinen Rechnungen oder umgekehrt einer Rechnung mit ihrem Kunden? Kunden und Rechnungen als separate Dokumente anzusehen, funktioniert, doch sie müssen ja in Beziehung gesetzt werden. Beziehungen zwischen Dokumenten sind jedoch nicht natürlich. Das macht ja gerade die “Dokumentenhaftigkeit” aus, dass in einem Dokument Daten zusammengefasst sind, die eng zueinander gehören. Ein Dokument ist etwas Abgeschlossenes, es ist self-contained.

Dennoch übt die Einfachheit der Persistenz mit CouchDB Faszination aus. StupidDB versucht z.B. das Persistenz-Paradigma von CouchDB in die .NET-Welt zu bringen und noch “einen oben drauf zu setzen”: StupidDB ist bewusst serverlos, denn “[d]urch Replikation des Filesystems z.B. per Windows-DFS ist […] eine einfache Hochverfügbarkeit und Skalierbarkeit der Datenbasis” herstellbar.

So schön einfach die Persistenz mit StupidDB jedoch auch ist, sie leidet unter demselben Problem wie CouchDB. StupidDB verwaltet Dokumente, die nur als Ganzes gespeichert werden. Objektgraphen werden en bloc in eine Datei serialisiert.

Schemalosigkeit für Geschäftsanwendungen: The Lounge Repository

Motiviert durch diese Ansätze habe ich nun versucht, die Vorteile der Schemalosigkeit auch für Geschäftsanwendungen zu erschließen. Statt stupide auf dem Sofa abzuhängen, finde ich es jedoch zeitgemäß und auch geselliger, zu “loungen”. Deshalb habe ich meinen kleinen Open Source Persistenzframework “The Lounge Repository” genannt.

Der Name ist Programm:

  • “Lounge” soll anzeigen, dass es ein denkbar einfach und intuitiv zu benutzender Framework ist. Entspannt Objektgraphen persistieren: das soll The Lounge Repository möglich machen. Sie müssen das Persistenzmedium (hier: das Dateisystem) nicht mit einem Schema strukturieren, bevor Sie darin etwas speichern können.
  • “Repository” statt des verbreiteten Suffixes “DB” soll einen Hinweis auf die Art der Daten geben, die mit dem Framework verwaltet werden.  Repository ist ein Begriff aus dem Domain Driven Design (DDD) und bezeichnet eine Dienstleistung zur Speicherung von Entities: “Definition: A Repository is a mechanism for encapsulating storage, retrieval, and search behavior which emulates a collection of objects.”

Entspannt Entities persistieren, darum geht es bei The Lounge Repository. Es ist als generisches Repository für alle möglichen Arten von Entities gedacht.

Was ist eine Entity? Ein zustandsbehaftetes Objekt mit einer eigenen, speicherübergreifenden Identität. Sie identifiziert es im Hauptspeicher wie im Persistenzmedium.

Kunde und Rechnung sind naheliegende Entitäten für eine Faktura-Anwendung. Eine Rechnungsposition jedoch nicht, da sie nicht unabhängig von einer Rechnung existiert. Auf Kunden wie Rechnungen möchte man sicher direkt zugreifen, sie müssen unabhängig von einander adressierbar sein; auf Rechnungspositionen kommt man hingegen nur über ihre Rechnung. Rechnungspositionen sind im DDD-Jargon sog. Value Objects.

Wenn Sie Ihre Anwendung auf der Basis von DDD modellieren, dann kommen am Ende sicherlich auch Entities und Value Objects heraus, die Sie persistieren wollen. Entities sollen einzeln geladen und gespeichert werden oder in größeren Zusammenhängen, z.B. eine Rechnung zusammen mit ihrem Kunden. Solche Zusammenhänge (cluster) nennt DDD Aggregate.

image

Wie nun die Persistenz von Éntities bewerkstelligen? Sicherlich können Sie dafür ein RDBMS Ihrer Wahl heranziehen. ADO.NET macht es dann möglich. Oder Sie machen es sich schon etwas einfacher und benutzen einen O/R-Mapper Ihres Geschmacks – von EntityFramework über Open Access und LLBLGENPRO bis zu NHibernate. Aber ich versichere Ihnen, wenn Sie nicht sattelfest mit einem dieser Persistenzframeworks sind, dann werden Sie eine rechte Mühe haben, Ihre Entities zu persistieren. Persistor.Net verspricht zwar, Ihnen einige dieser O/R-Mapping-Mühen abzunehmen, aber auch er setzt auf ein RDBMS, das es dann zu verwalten gilt.

Entity Graphen im Dateisystem speichern

CouchDB & Co. haben es deshalb auch leicht gehabt, bei der Usability zu punkten. Ihre APIs sind allemal “für die schnellere Persistenz zwischendurch” sehr schön einfach. Nur leider passen sie, wie schon erwähnt, nicht so gut zum Datenmodell er üblichen Geschäftsanwendungen. Objektgraphen mit vielen Entities (Aggregate) lassen sich nicht wirklich als Dokument beschreiben. Denn wenn dieselbe Entity in mehreren solchen Aggregaten vorkommt, wird sie in mehreren Dokumenten persistiert. Sie verliert damit ihre zweckstiftende Eigenschaft, ihre Identität, ihre Eindeutigkeit.

image

So sieht Ihr schöner Entity Graph aus, wenn CouchDB oder StupidDB ihn gespeichert haben. Alles ist zu einem Dokumentenganzen zusammengefasst.

The Lounge Repository macht es hingegen anders! Jede Entity wird hier separat in einer Datei gespeichert, egal wie tief eingeschachtelt sie in einem Entity Graphen ist. Das funktioniert natürlich auch mit zyklischen Referenzen:

image

The Lounge Repository erhält die logische Identität durch physische Separierung. Das unterscheidet es wesentlich von StupidDB.

Der Lounge Repository API

The Lounge Repository ist zunächst eine Fingerübung (oder eine umfangreichere Code Kata, wenn Sie so wollen). Mit dem Lounge Repository will ich also zwei Fliegen mit einer Klappe schlagen: Ich möchte ein Werkzeug haben, mit dem ich meine Gedanken zum Thema Schemalosigkeit praktisch ausprobieren kann. Und ich möchte in kontrollierter Umgebung meinen Programmiersüchten nachgehen können ;-) Denn wer würde leugnen wollen, dass die Programmierung von Infrastruktur nicht süchtig macht? Im kleinen Freizeitrahmen ist das aber genauso wenig schlimm, wie ein gelegentliches Bierchen am Abend oder eine Zigarette alle Jahre wieder auf der Wies´n. In den endlich-clean.net Entzug muss ich deshalb jedenfalls noch nicht. Allemal, weil ich mich bemüht habe, den Lounge Repository Code clean zu halten. Doch Vorsicht vor Infrastrukturprogrammierung in Ihren Projekten!

Doch jetzt weiter zu Konkretem, zum Code. Wie sieht der API des Lounge Repository aus? Ein Hello-World-Beispiel zeigt das Grundsätzliche in wenigen Zeilen. Laden Sie den Quellcode von CodePlex herunter und machen Sie mit. Das geht mit einem SVN-Client wie Tortoise ganz schnell. Die Quellen enthalten auch eine Projektmappe mit kleinen Beispielen.

Hier nun ein Beispielprojekt, wie Sie es aufsetzen können, wenn Sie die Lounge Repository Quellen mittels deren Projektmappe einmal übersetzt haben. Es stehen dann im globalen bin-Verzeichnis des Quellbaumes die Assemblies des Frameworks bereit:

image

Davon binden Sie zunächst aber nur zwei ein: LoungeRepo.Core und LoungeRepo.Contracts:

image

Mehr ist als Vorbereitung nicht nötig. The Lounge Repository kennt keine Datenbankdateien und hat (noch) keinen Serverprozess. Es ist eine experimentelle “embedded database”.

Und jetzt der Hello-World-Code:

    1 using System;

    2 using LoungeRepo.Contracts.Core;

    3 using LoungeRepo.Core;

    4 

    5 namespace BlogSample

    6 {

    7     class Program

    8     {

    9         static void Main()

   10         {

   11             using(ILoungeRepository repo = new LoungeRepository())

   12             {

   13                 repo.Store("hello, world!", "1");

   14 

   15                 string greeting = repo.Load<string>("1");

   16                 Console.WriteLine(greeting);

   17             }

   18         }

   19     }

   20 }

Mit Store() speichern Sie Entities, mit Load() laden Sie sie wieder. So einfach ist das mit der Persistenz.

Eine Entity ist jedes Objekt, dem Sie dieses Privileg zugestehen möchten. Sie müssen die Klassen zu persistierender Objekte nicht mit einem bestimmten Attribut kennzeichnen oder von bestimmten Klassen ableiten. Eine Zeichenkette kann genauso gut wie ein Kunde-Objekt eine Entity sein:

    5 namespace BlogSample

    6 {

    7     class Kunde

    8     {

    9         public string Name { get; set; }   

   10     }

   11 

   12     class Program

   13     {

   14         static void Main()

   15         {

   16             using(ILoungeRepository repo = new LoungeRepository())

   17             {

   18                 Kunde k = new Kunde {Name="Peter"};

   19                 repo.Store(k, "2");

   20 

   21                 k = repo.Load<Kunde>("2");

   22                 Console.WriteLine(k.Name);

   23             }

   24         }

   25     }

   26 }

Wichtig ist, dass jede Entity auch wirklich eine Identität hat. Die ist beim Speichern und Laden wichtig. Denn aus ihr “berechnet” das Repository den Namen der Datei, in der es die Entität speichert bzw. aus der es sie lädt.

Ein string oder das obige Kundenobjekt haben keine von ihrer Hauptspeicheradresse unabhängige Identität. Also muss der Code explizit eine beim Speichern angeben.

Die Identität einer Entity besteht aus zwei Teilen: einer Id (eine Zeichenkette Ihrer Wahl) und einer optionalen Partition (ebenfalls eine Zeichenkette Ihrer Wahl).

Partitionen unterteilen den “Persistenzraum” (hier: das Dateisystem) und ermöglichen auf lange Sicht eine Lastverteilung. Entities einer bestimmten Partition könnten von dedizierten Servern verwaltet werden. Auch wenn das Zukunftsmusik ist, habe ich mir gedacht, das grundlegende Konzept der Partitionierung schon jetzt mit in die Funktionalität aufzunehmen. Das mag ein wenig YAGNI sein… aber was soll´s? ;-)

Innerhalb einer Parition muss dann die Id eindeutig sein. Zusammen ergeben sie die Identität, die im “Persistenzraum” eindeutig ist. Ist keine Partition definiert, nimmt das Lounge Repository eine default Partition an.

Wenn Sie nicht wissen, was Sie als Partition angeben sollen, dann lassen Sie sie aus – oder wählen Sie z.B. den Klassennamen einer Entity als Partitionsnamen:

   12 class Rechnung

   13 {

   14     public string Rechnungsnummer;

   15     public Kunde Empfänger;

   16 }

   17 

   18 class Program

   19 {

   20     static void Main()

   21     {

   22         using(ILoungeRepository repo = new LoungeRepository())

   23         {

   24             Kunde k = new Kunde {Name="Maria"};

   25             Rechnung r = new Rechnung

   26                             {

   27                                 Rechnungsnummer = "090829-1",

   28                                 Empfänger = k

   29                             };

   30             repo.Store(r, "3", "Rechnung");

   31 

   32             r = repo.Load<Rechnung>("3", "Rechnung");

   33             Console.WriteLine("#{0} für {1}",

   34                         r.Rechnungsnummer,

   35                         r.Empfänger.Name);

   36         }

   37     }

   38 }

Aber nicht nur die Partition “Rechnung” für die Rechnung-Entity ist bemerkenswert an diesem Stück Code. Bitte beachten Sie auch folgendes:

  • Die Kundin Maria wurde natürlich zusammen mit der Rechnung persistiert.
  • Beim Laden der Rechnung wurde die Empfängerin natürlich auch wieder mit geladen.
  • In diesem Beispiel gibt es nur eine Entity: die Rechnung. Das Kunde-Objekt hat von sich aus keine Identität und wurde nicht ausdrücklich als Entity gespeichert.

Das Repository sieht jetzt so aus:

image 

Die Entities aus den vorangehenden Beispielen hatten keine eigene Partition und wurden daher in der default Partition gespeichert. Entity “3”, die Rechnung, steht in der explizit angegebenen Partition “Rechnung”.

Und wo ist die Kundin Maria? Sie steckt in der Rechnung-Entity mit Namen 3.entity, weil ihr Kunde-Objekt ja nicht als Entity gespeichert wurde. Das ist verständlich, oder? Schön ist es aber nicht. Sie wollen ja nicht alle Entity-Objekte explizit speichern müssen. Außerdem würde das nichts nützen, denn selbst wenn das Objekt zu Kundin Maria als Entity gespeichert worden wäre, würde das Repository das nicht merken während der Speicherung der Rechnung. Die “Entitätshaftigkeit” ist einem Kunde-Objekt ja nicht anzusehen. Bisher.

Um Entities nicht mit expliziter Identitätsangabe speichern zu müssen und auch in Entity Graphen erkennbar zu machen, können Sie sie das Interface ILoungeRepoEntityIdentity implementieren lassen. Das definiert nur zwei Properties: Id und Partition. Es trägt also nicht dick auf Ihre Domänenobjekte auf. Damit wird dann das kleine Szenario wirklich intuitiv:

    7 class Kunde : ILoungeRepoEntityIdentity

    8 {

    9     #region Implementation of ILoungeRepoEntityIdentity

   10     public string Id { get; set; }

   11     public string Partition { get { return "Kunden"; } }

   12     #endregion

   13 

   14     public string Name { get; set; }

   15 }

   16 

   17 

   18 class Rechnung : ILoungeRepoEntityIdentity

   19 {

   20     #region Implementation of ILoungeRepoEntityIdentity

   21     public string Id { get { return this.Rechnungsnummer; } }

   22     public string Partition { get { return "Rechnungen"; } }

   23     #endregion

   24 

   25     public string Rechnungsnummer;

   26     public Kunde Empfänger;

   27 }

   28 

   29 

   30 class Program

   31 {

   32     static void Main()

   33     {

   34         using(ILoungeRepository repo = new LoungeRepository())

   35         {

   36             Kunde k = new Kunde {Id="4", Name="Maria"};

   37             Rechnung r = new Rechnung

   38                             {

   39                                 Rechnungsnummer = "090829-1",

   40                                 Empfänger = k

   41                             };

   42             repo.Store(r);

   43 

   44             r = repo.Load<Rechnung>("090829-1", "Rechnungen");

   45             Console.WriteLine("#{0} für {1}",

   46                         r.Rechnungsnummer,

   47                         r.Empfänger.Name);

   48 

   49             k = repo.Load<Kunde>("4", "Kunden");

   50             Console.WriteLine(k.Name);

   51         }

   52     }

   53 }

Kunde und Rechnung implementieren nun das ILoungeRepoEntityIdentity Interface und liefern dem Repository darüber ihre Identitäten. Den Kunden habe ich zur Demonstration so ausgelegt, dass ihm die Id bei Erzeugung zugewiesen werden muss, die Rechnung entnimmt sie ihrer Rechnungsnummer. Beide enthalten jedoch eine fest verdrahtete Partition.

Die Rechnung wird wie erwartet auch jetzt wieder mit ihrem Kunden geladen, darüber hinaus kann der Code jedoch auf den Kunden auch direkt zugreifen, wie Zeile 49 zeigt.

Das ist im Grunde alles, was es zum Laden und Speichern zu sagen gibt. Sie müssen keine Vorbereitungen treffen, aber Entities sollten gekennzeichnet sein. Alle nicht gekennzeichneten Objekte sind für das Repository Value Objects.

Objektgraphen, d.h. Objekthierarchien und –netzwerke – auch solche mit Zyklen – werden korrekt gespeichert/geladen, d.h. Entitäten wandern in je eigene Dateien. Wie Sie Objektverweise aufbauen, ist Ihnen überlassen. Sie können einzelne Referenzen halten wie die Rechnung auf ihren Empfänger. Oder Sie benutzen Arrays oder Collections, um mehrere Referenzen zu verwalten.

The Lounge Repository persistiert alle Felder der Objekte, die ihm zur Speicherung übergeben werden. Immer. Es findet (derzeit) kein change tracking statt. Wollen Sie ein Feld ausschließen, dann setzen Sie darüber das [NonSerialized] Attribut, das Sie von der .NET-Serialisierung kennen.

Dass Sie Entitäten auch löschen können, ist selbstverständlich. Rufen Sie Delete() auf dem LoungeRepository unter Angabe der Identität auf.

Zum Schluss bleibt nur noch eine Frage: Kann man eigentlich auch Entitäten durch Queries ermitteln? Ja, man kann. Das Lounge Repository sammelt alle Entitäten eines Typs in einem sog. Extent. Das ist nichts weiter als eine lange Liste von Objekten, die das Repository als IEnumerable<T> anbietet. Deshalb können Sie darauf mit Linq in gewohnter Weise zugreifen:

   33 static void Main()

   34 {

   35     using(ILoungeRepository repo = new LoungeRepository())

   36     {

   37         Kunde k = new Kunde {Id="4", Name="Maria"};

   38         Rechnung r = new Rechnung

   39                             {

   40                                 Rechnungsnummer = "090829-1",

   41                                 Empfänger = k

   42                             };

   43         repo.Store(r);

   44 

   45         r = new Rechnung

   46                     {

   47                         Rechnungsnummer = "090715-2",

   48                         Empfänger = k

   49                     };

   50         repo.Store(r);

   51 

   52         k = new Kunde { Id = "5", Name = "Dennis" };

   53         r = new Rechnung

   54                     {

   55                         Rechnungsnummer = "090803-3",

   56                         Empfänger = k

   57                     };

   58         repo.Store(r);

   59 

   60 

   61         var mariasRechnungen =

   62             from rg in repo.GetExtent<Rechnung>()

   63                 where rg.Empfänger.Name == "Maria"

   64                 select rg;

   65 

   66 

   67         foreach (Rechnung mariasRg in mariasRechnungen)

   68             Console.WriteLine("#{0} für {1}",

   69                 mariasRg.Rechnungsnummer,

   70                 mariasRg.Empfänger.Name);

   71     }

   72 }

Die Zeilen 37 bis 58 bauen eine kleine Datenbasis an persistenten Entities auf. Und die Zeilen 61 bis 64 fragen sie mit einer Linq-Query ab. repo.GetExtent<T>() liefert dafür die Grundlage in Form einer Liste aller Rechnung-Entities, die geladen oder gespeichert wurden.

Hier liegt z.Z. noch eine Begrenzung des Lounge Repository: Extents enthalten zunächst nur Objekte, die das Repository “gesehen” hat. Objekte, die auf der Platte im Repository liegen, aber vom Repository weder direkt oder indirekt geladen wurden, sind (noch) nicht in dessen Cache enthalten und tauchen daher nicht im Extent auf.

Das können Sie jedoch ausbügeln, indem Sie zu Beginn einer Sitzung den internen Cache mit allen persistenten Entities populieren. Ja, so laden Sie zwar die ganze Datenbank in den Hauptspeicher, aber das macht nichts. The Lounge Repository ist (zunächst) genau für solche Szenarien gedacht, in denen Sie eben nicht Gigabytes an Daten verwalten. Selbst einige Hundert Megabytes in den Hauptspeicher zu laden auf einem 4 GB Laptop sollte allerdings den Kohl nicht fett machen.

Für einen solchen sog. “prefetch” binden Sie einfach die Assembly LoungeRepo.Core.Extensions ein und importieren Sie den gleichnamigen Namensraum. Dann sind alle Entities über ihre Extents zu erreichen:

    5 using LoungeRepo.Core.Extensions;

    6 

    7 namespace BlogSample

    8 {

    9     …

   32     class Program

   33     {

   34         static void Main()

   35         {

   36             using(ILoungeRepository repo = new LoungeRepository())

   37             {

   38                 repo.PrefetchAllEntities();

   39                 …

   63                 var mariasRechnungen =

   64                     from rg in repo.GetExtent<Rechnung>()

   65                         where rg.Empfänger.Name == "Maria"

   66                         select rg;

Ausblick

The Lounge Repository ist “a work in progress”. Ich habe mir damit eine Spielwiese angelegt, auf der ich Ideen zum schemalosen Umgang mit Daten und anderem ausprobieren kann. Das Projekt ist Open Source und Sie finden es bei CodePlex in seiner vollen “clean beauty”: http://loungerepo.codeplex.com/

Laden Sie den Quellcode runter und spielen Sie damit. Wenn Sie Fragen oder Einfälle haben, lassen Sie uns bei CodePlex darüber diskutieren. In der Projektmappe finden Sie auch eine kleine Aufgabenliste, die ich führe. Da sehen Sie, dass noch einiges zu tun ist am Lounge Repository. Und auch darüber hinaus habe ich schon Ideen, z.B. wie ein solche Repository verteilt und asynchron betrieben werden kann.

Einstweilen mag The Lounge Repository Datenbanken wie SQL Server oder selbst CouchDB nicht ersetzen. Aber ich würde mich freuen, wenn in ihm ein Keim läge, der es in einigen Szenarien zu einer Alternative zum default RDBMS machte. Prototypen, kleine Anwendungen… dort, wo Sie Entitäten identifizieren und “schnell mal persistieren wollen” ohne Schemaaltlasten, hat The Lounge Repository (bzw. eine der anderen “alternativen Datenbanken”) es sicher verdient, berücksichtigt zu werden.

Ich bin gespannt auf Ihr Feedback.

Sonntag, 23. August 2009

Was ist mit Bob? – Verwirrung anlässlich einer Code Kata

image Wollte mich heute mal zur Entspannung mit einer Code Kata beschäftigen. Meine Wahl fiel auf die "Bowling Game Kata" von unser aller Onkel Bob. Das PPT dazu sieht hübsch strukturiert aus:

  • Am Anfang werden die Regeln für die Punktezählung bei einem Bowling Game erklärt. Da besteht ein Spiel z.B. aus 10 Sätzen (Frame), innerhalb derer mehrere Würfe (Roll) gemacht werden dürfen, um zu einem Punktestand (Score) für den Satz zu kommen. Die Gesamtpunktezahl für ein Spiel ist dann die Summe der Punkte der Sätze.
  • Dann wird eine Sollklasse als Anforderung formuliert. Die ist in der Kata per TDD zu implementieren. Ich formuliere sie mal als Interface für C#:

image interface IGame
{
    void Roll(int pins);
    int Score { get; }
}

 

Mit den Regeln und der formalen Anforderung in der Tasche kann es dann losgehen.

Oder man linst mal auf die weiteren Folien der Katabeschreibung - immerhin sind es mehr als 50. Da erklärt Meister Bob nämlich sein Vorgehen.  Aber, wer hätte das gedacht: Statt mit TDD loszulegen, macht Onkel Bob eine Design Session!

image

imageWas ist mit Bob? Da gibt es nicht nur eine Klasse Game, sondern auch eine Klasse Frame und Roll. Hört sich ja auch plausibel an, wenn man die Regeln liest. Darin tauchen diese Begriffe als Substantive auf. Aber warum müssen die denn in ein Design einfließen? Ist es ausgemacht, dass man sie für die Implementation wirklich braucht?

Oder besser: Ist es aus den Anforderungen ablesbar, dass diese Klassen gebraucht werden? Ich glaube, das kann man nicht. Denn ich habe meine Implementation der Anforderung “Implementiere eine Klasse Game wie folgt…” strickt mit TDD begonnen. Für so ein kleines Beispiel habe ich schlicht keine ausdrückliche Design Session für nötig gehalten. Und ich habe auch gedacht, genau das sei eben der Sinn solcher kleinen Katas: dass man eben vor allem den TDD-Prozess einübt mit seiner Schrittfolge red-green-refactor. Das Design soll iterativ und absolut bedarfsgetrieben evolvieren. Und dann sowas von Bob?!

Bei meinem TDD-Vorgehen konnte ich keine Notwendigkeit erkennen, Klassen jenseits von Game zu implementieren. Den Grund halte ich für ganz naheliegend: Die Anforderung (!) enthält aber auch gar keinen Bezug zu Sätzen und Würfen.

Die Regeln sagen zwar, dass man pro Satz nur soundsoviele Würfe machen darf und bestimmte Wurferfolge Zusatzwürfe (Bonus) gestatten. Doch die Punktezählung ist am Ende nur eine schlichte Addition aller Wurferfolge (Pins).

Darüber hinaus abstrahiert die Klasse Game von all diesen Details, indem auf ihr einfach immer nur wieder Roll() aufgerufen werden soll:

game.Roll(5);
game.Roll(3);
game.Roll(7);
game.Roll(9);

Console.WriteLine(game.Score);

image Was, bitte, hat das noch mit Sätzen zu tun? Ob ein Wurf ein Bonuswurf ist, entscheidet nicht die Klasse Game. Das steht jedenfalls nicht in den Anforderungen. Auch ist nicht erwähnt, ob irgendwie die Einhaltung der Regeln geprüft werden soll. Oder warum die Einschränkung, Score nur am Ende aufzurufen? Es macht keinen Unterschied, wann man Score befragt, da die Gesamtpunktezahl immer nur eine Addition aller per Roll() gemeldeten Wurfergebnisse ist.

Was ist also mit Bob? Wie kommt es, dass er eine so merkwürdige Aufgabe stellt und sich letztlich nicht an die eigenen Prinzipien hält? Explizites Design statt TDD – was hat ihn bei der Aufgabengröße denn da geritten?

Oder habe ich da etwas übersehen, falsch verstanden? Ist mir in der Spieldefinition etwas entgangen? Ist mir der tiefere Sinn der Anforderung an die Klasse Game entgangen? Ich bitte um Aufklärung.

Empfehlung für Katas

Egal, ob ich etwas nicht richtig verstanden habe oder in der Aufgabenstellung der Wurm ist, ich denke, eine Lehre lässt sich in jedem Fall ziehen: keine Code Kata ohne Akzeptanztests!

Alles wäre leichter, wenn Bob der Kata eine Datei in einem einfachen Format beigegeben hätte, die Sollergebnisse enthält. Schon folgendes hätte gereicht:

7,2,5,4,…,103
3,4,10,7,…134

Wobei jede Zahl einen Wurf repräsentiert und die letzte den Score.

Wer also Code Katas sucht, der sollte darauf achten, dass ihr “Abnahmetests” beiliegen. Und wer Code Katas beschreiben will, dem sei empfohlen, ein bisschen Mühe auf die Definition von “Abnahmetests” zu verwenden. Sie steigern den Wert der Code Kata – oder machen sie erst überhaupt durchführbar.