Freitag, 3. Oktober 2014

Regelmäßiges Lernen - Meine Retrospektive

Ende Juni 2014 hatte ich versprochen, ich würde nun auch noch expliziter das Lernen in meine Arbeitszeit einbauen. Zwar besteht mein Job als Berater, Trainer, Autor zu einem großen Teil ohnehin aus Lernen, doch das hat eine andere Qualität als das, was ich meinen Seminarteilnehmern und Kunden nahelege. Mein Job ist Lernen, deren Job ist es nicht.1

Wenn ich Softwareentwickler empfehle, in der Arbeitszeit zu lernen, dann bürde ich ihnen scheinbar eine extra Aufgabe auf. Das war bei meinem Lernen bisher nicht der Fall gewesen. Deshalb wollte ich mein Lernen noch expliziter machen; es sollte auch für mich eine zusätzliche Aufgabe im Tagesgeschäft werden, damit ich einmal fühle, wie es meinen Kunden geht.

Mein Commitment war, dass ich wöchentlich während der Arbeitszeit 2+ Stunden mich auf diese Aufgaben konzentriere:

  • Französisch lernen
  • Bücher zu Sachthemen außerhalb normaler Lernthemen lese
  • Meditiere, d.h. "Ruhe und Fokus lernen"

Und ich hatte versprochen, über meine Erfahrung mit solchem extra Lernen nach drei Monaten zu berichten. Hier nun meine Beobachtungen:

image

Das sind zwei Auszüge aus meinem Lernprotokoll, das ich mit Lift geführt habe.

Sie sehen, lückenlos ist das nicht. Meine erste Beobachtung also: Es ist nicht leicht. Es ist nicht leicht, an jedem Arbeitstag die 15-30 Minuten aufzuwänden. Irgendetwas anderes schien oft dringender und so war das Lernen dann verschoben auf später und dann auf den nächsten Tag.2

Gelegentliche "Planübererfüllung" hat das zum Teil wett gemacht, ist langfristig aber keine erfolgversprechende Strategie, würde ich sagen. Das ist wie mit dem Refactoring: wenn man es länger und länger nicht tut, dann ist der Berg am Ende so groß, dass man es auch nicht nachholen kann.

Außerdem ist der mentale "Umschaltaufwand" gerade beim Französich Lernen für mich teilweise so hoch, dass in der kurzen Zeit die echte Lernaufmerksamkeit dann noch kürzer ist. Das fühlt sich ineffizient an.

Die Lehre, die ich daraus ziehe: Extra Lernen macht in so kleinen Tageshappen nur sehr bedingt Sinn. Besser scheint mir ein wöchentlicher Lernblock von 2+ Stunden.

Manchmal habe ich mir aber auch selbst ein Bein gestellt. Die beste Zeit für Französisch und Sachbuch war der Vormittag - ebenso jedoch auch fürs Schreiben. Welcher Tätigkeit dann den Vorzug geben? Die Entscheidung fiel mir leichter fürs Schreiben.

Die nächste Lehre daher: Der Zeitpunkt für Lernen will gut gewählt sein. Und wenn er einmal gewählt ist, sollte die Entscheidung nicht immer wieder neu getroffen werden müssen. Da geht Kraft verloren. Da zieht Lernen allzu schnell den Kürzeren. Vertagen scheint so einfach, so schmerzfrei. Doch in Wirklichkeit wird nicht vertagt, sondern gestrichen.

Aber auch von solchen Äußerlichkeiten unabhängig fiel mir das Lernen nicht immer leicht. Ich habe Wellen der Motivation gespürt. Manchmal klappte es besser, dann war ich für den nächsten Tag motivierter; manchmal klappe es nicht so gut, dann hatte ich am nächsten Tag nicht soviel Lust, mich weiterem Frust auszusetzen.

Solche ups and downs lassen sich wohl nicht vermeiden. Aber ich denke, man kann sie mildern. Das ist wie beim Fitnesstraining. Die Lust am Morgen (oder Abend) sich aufzumachen, ist unterschiedlich - da hilft es, wenn jemand auf einen wartet.

Ich habe (wieder einmal) übers Lernen gelernt: Lernen macht mehr Spaß und funktioniert verlässlicher, wenn man es nicht allein machen muss. Das Mindeste ist ein Accountability Partner, d.h. eine Person, der man direkt in die Augen blicken und gegenüber Rechenschaft ablegen muss. Eine Person, die den Lerner zieht. Die ihn an seinen Vorsatz erinnert, ihn fordert (aber auch durchaus fördert).

Damit meine ich nicht unbedingt einen Lehrer, aber der kann es natürlich auch sein.

Ein Mitlerner ist genauso gut. Oder einfach nur jemand, der eben zieht und sonst nichts.

Lernen in Teams sollte deshalb nicht Sache der Einzelnen sein, sondern der Gruppe. Gemeinsam Lernzeit einrichten, gemeinsam lernen. Das hilft ungemein.

Unterschätzt habe ich am Ende allerdings vor allem einen Aspekt: Relevanz. Nach einer initialen Phase hoher Motivation bin ich beim Französisch Lernen in den Morast gekommen. Es ging nur zäh voran. Nicht, weil es besonders schwierig gewesen wäre. Es lag vielmehr an einer fehlenden Bedeutung des Französischen für mein sonstiges Leben.

Ich mag Französisch. Ich würde es gern lesen und auch sprechen können. Doch am Ende ist das nicht jeden Tag wieder genug Antrieb gewesen, um dauerhaft dabei ins Lernen zu kommen. Anders wäre es wahrscheinlich gewesen, hätte ich schon mehr Vokabular drauf gehabt und mit dem Lesen von spannenderer Lektüre anfangen können. So aber war dieses Thema sehr abstrakt.

Das bedeutet: Lernen funktioniert umso besser, je relevanter es für das sonstige Leben (hier: Arbeitsalltag) ist. Je kleiner der Sprung vom Lernen ins Tagesgeschäft, desto besser. Das muss nicht bedeuten, dass man alles anwenden kann oder Großartiges leisten muss. Aber immer wieder sollte im Arbeitsalltag etwas ein Stückchen leichter fallen, besser werden.

Das habe ich bei der Lernaufgabe "Sachbuch lesen" positiv gemerkt. Dort habe häufiger Impulse für die Arbeit bekommen.

Diese Retrospektive zu meinem Lern-Commitment mag ernüchternd klingen. Es hat nicht so einfach geklappt, wie gedacht. Auch die Öffentlichkeit, in die ich mich mit dem Commitment begeben hatte, hat daran nicht viel geändert.

Doch ich sehe es positiv: Immerhin habe ich dabei etwas gelernt. Auf der Meta-Ebene. Für mich selbst, für meine Kunden oder für Sie braucht es neben der Erkenntnis, dass Lernen wichtig ist, und dem guten Willen einfach noch ein paar Rahmenbedindungen:

  1. Wenn es um echten Lernstoff geht, dann besser jede Woche eine längere Lernzeit einplanen (2+ Stunden).3
  2. Einen Lernpartner oder zumindest einen Accountability Partner suchen.
  3. Immer wieder probieren, den Lernstoff im Alltag anzuwenden. Sozusagen inkrementelles Lernen à la Agilität, d.h. es entsteht sofort Nutzen.

Das mögen jetzt keine weltbewegenden Einsichten sein. Irgendwie klingt das doch selbstverständlich. Und doch ist es nochmal etwas anderes, diese Einsichten durch Erfahrung gewonnen zu haben.

Angesichts der Wichtigkeit des Lernens und auch des Lernens während der Arbeitszeit, in sich immer wieder etwas anderes dazwischen drängt, ist jeder Gewinn an Klarheit jedoch ein Fortschritt, finde ich. Mit dem falschen Fuß loszugehen, sich Illusionen hinzugeben, enttäuscht nur. Besser ist es, von Anfang an ein realistisches Rahmenwerk aufzubauen.

In diesem Sinn ist auch die CCD School gedacht. Sie bietet nicht nur Input fürs Lernen, sondern auch eine Form der Begleitung, der Accountability Partnerschaft. Offline geht das im Monatsrhythmus, online auch häufiger.

Zu guter Letzt aber doch noch ein Trost: Ich habe auch festgestellt, dass sich manches verselbstständigt. Ich mag mich schließlich nicht so regelmäßig und dauerhaft zur Meditation hingesetzt haben, wie geplant - doch das, was ich damit erreichen wollte, hat sich auf anderem Wege in mein Leben eingeschlichen. Die Fokussierung und das Konzentrieren auf "Innen" finden inzwischen "einfach so" immer wieder statt.

Manchmal wird aus extra Lernen also neue Gewohnheit. Dann macht das Thema keine Last mehr, sondern ist Alltag, gar Lust.


  1. Ob das wirklich eine günstige Sichtweise ist, die Arbeit von Softwareentwickler als nicht (oder nur wenig) lernend anzusehen, lasse ich hier einmal dahingestellt.

  2. Dazu kam, dass ich nicht jeden Tag meine Arbeitszeit einteilen konnte, wie ich wollte, weil ich in Trainings- und Beratungen beim Kunden war.

  3. Anders ist es mit Gewohnheiten oder einzelnen Handlungen. Meditation lässt sich nicht auf einmal pro Woche konzentrieren. Genauso wenig eine tägliche Reflexion, wie sie der Rote Grad des Clean Code Development empfiehlt.

Donnerstag, 2. Oktober 2014

Responsibilities zählen

Das Single Responsibility Principle (SRP) ist eine Säule sauberer Softwareentwicklung für hohe Wandelbarkeit. Nicht umsonst macht es auch den Anfang bei den SOLID Prinzipien, würde ich sagen.

Aber: Wie finden Sie denn heraus, wieviele Responsibilities (Verantwortlichkeiten) eine Methode oder Klasse hat? "Naja, das sieht man halt", scheint mir ein zu schwammiges Kriterium für ein so zentrales Prinzip.

Was ist eine Verantwortlichkeit?

Bevor es mit dem Zählen losgehen kann, muss natürlich klar sein, was da überhaupt entdeckt und gezählt wird. Was ist eine Verantwortlichkeit?

Die Literatur spricht von "only one reason to change". Das finde ich leider nicht wirklich hilfreich. Denn um welche Gründe geht es, worauf beziehen die sich?

Ich versuche es daher einmal so:

Jede Methode soll nur Anweisungen enthalten, die Anforderungen eines Aspekts erfüllen.

Diese Definition ist knackiger, erfordert jedoch die Klärung zweier Begriffe:

  • Aspekt: Ein Aspekt ist eine Menge von zusammengehörigen Merkmalen, die sich unabhängig von anderen Merkmalen ändern können. Man könnte einen Aspekt auch eine Dimension nennen. Beispiele für Aspekte in der realen Welt sind z.B. Frisur, Kleidung, Bildung. Sie können die Merkmale Ihrer Frisur (Haarfarbe, Haarlänge, Schnitt) unabhängig von Merkmalen Ihrer Kleidung (Stoff, Jahreszeitlichkeit, Stil) oder Bildung (Dauer, Inhalt, Ort) ändern.
  • Anweisung: Anweisungen sind Sprachkonstrukte, die definieren, was eine Software tun soll. Sie fallen für mich in zwei Kategorien: Logik und Integration.
    • Logik: Mit Logik bezeichne ich die essenziellen Anweisung von Programmiersprachen, die der Erfüllung von funktionalen wie qualitativen Anforderungen dienen. Das sind Operatoren, die Daten transformieren, Kontrollstrukturen (z.B. if, while), die den Verarbeitungsfortgang steuern, und Hardwarezugriffe (vermittels API-Aufrufen). Logik beschreibt Algorithmen.
    • Integration: Integration bindet mehrere Methoden zu einem Datenfluss zusammen.

Ich denke, jetzt wird auch verständlicher, was "only one reason to change" bedeutet: Methoden sollen nur geändert werden müssen, wenn sich an einem Anforderungsaspekt etwas verändert hat.

Methoden sind die kleinsten Container von Programmiersprachen. Darüber liegen für mich in wachsender Größe Klassen, Bibliothekn, Komponenten und µServices. Für die muss das SRP natürlich auch gelten. Also lautet es ganz allgemein:

Jeder Container soll nur Anweisungen enthalten, die Anforderungen eines Aspekts erfüllen.

So zumindest das Ideal. In der Praxis kann und muss sogar davon temporär oder in überschaubarem Maße abgewichten werden. Ziel sollte jedoch ein einziger Aspekt pro Container sein.

Außerdem ist zu bedenken, dass Aspekte in Hierarchien existieren. Zum Aspekt der Kleidung mögen die Merkmale Stoff, Jahreszeitlichkeit und Stil gehören. Nur zum Stoff z.B. gehören dann jedoch weitere Sub-Aspekte wie Farbe, Material, Haptik. Kleidung kann aus grobem roten Leinen oder feiner grüner Seide oder rauer gelber Wolle oder feinem gelbem Leinen usw. bestehen.

Ein Container auf höherer Abstraktionsebene (z.B. Klasse) kann dann für einen Dachaspekt stehen, der Container von Sub-Aspekten (z.B. Funktionen) zusammenfasst.

Härtegrade

Für mich gibt es Aspekte in unterschiedlichen Härtegraden. Hart sind Aspekte, die man an der Form erkennt. Man muss die Anforderungen nicht verstehen, die Anweisungen erfüllen, sondern nur die Programmiersprache/Plattform.

Beispiele hierfür sind die Anweisungsaspekte Logik und Integration sowie der Aspekt Datenstruktur.

Innerhalb der Logik können dann jedoch weitere verschiedene Hardwarezugriffe unterschieden werden, z.B. Tastatureingabe, Bildschirmausgabe, Dateisystemzugriff, Datenbankzugriff. Und sogar den Zugriff auf den Heap würde ich dazurechnen, also den Umgang mit Hauptspeicher. Dazu ist zwar kein spezieller API nötig, doch Zugriffe aus globale Daten (statische Felder oder Felder von Objektinstanzen) sind klar erkennbar.

Weich hingegen sind Aspekte, bei denen man verstehen muss, worum es geht. Es geht um das Was, wohingegen harte Aspekte das Wie betreffen.

Schon die Unterscheidung zwischen lesender und schreibender Logik setzt Interpretation voraus. Es ist daher kein Wunder, dass bei weichen Aspekten schnell Diskussionen entstehen. Der eine empfindet Lesen und Schreiben als Merkmale des selben Aspekts, der andere empfindet sie als getrennt - was sich dann jeweils in unterschiedlicher Aufteilung in Container ausdrückt.

Auch hier wieder eine Hierarchie: Lesen und Schreiben sind z.B. Sub-Aspekte von Objektpersistenz (zu der auch z.B. Serialisierung gehört). Und Objektpersistenz ist ein weicher Aspekt von z.B. Personalisierung. Die wiederum ein Aspekt des Anforderungsaspektes Usability ist - welche zur Anforderungskategorie Qualität gehört.

Und was folgt aus der Unterscheidung zwischen harten und weichen Aspekten? Halten Sie sich so lange wie möglich bei der Strukturierung von Code an harte Aspekte. Darüber gibt es viel weniger Diskussion. Das macht Ihre Codierung schneller, das macht Reviews konfliktfreier.

Am Ende jedoch können Sie natürlich den weichen Aspekten nicht ausweichen. Üben Sie also immer wieder Ihre Sensibilität in der Unterscheidung und Zusammenfassung von weichen, inhaltlichen Merkmalen.

Apropos Üben...

Angewandte Aspekterkennung

Zum Abschluss ein Codebeispiel, an dem ich Ihnen die Identifikation von Aspekten praktisch demonstrieren möchte. Ich entnehme es dem Buch "Head First C#".

image

Das Szenario? Versuchen Sie es doch einmal dem Code zu entnehmen. Wie Sie feststellen werden, ist das jedoch schwierig. Weil er nicht integriert und kein Titel sichtbar ist. Die Methode Main() enthält ausschließlich Logik. Und die hat ihrer Natur nach denkbar wenig Dokumentationsqualität. Reine Logik muss immer entziffert werden. Hobbyarchäologen sind klar im Vorteil ;-)

Aber ich verrate Ihnen das Szenario: Es handelt sich um eine Anwendung zur Darstellung von Dateiinhalten in hexadezimaler Form.

Hier nun der von mir mit Aspekten kommentierte Quellcode:

image

Das Kapitel im Buch heißt "Dateien lesen und schreiben" - aber wie das geht, muss der Leser sich mühsam im ganzen Code zusammensuchen. Nicht nur wie das Problem ganz allgemein gelöst wird, wie der Prozess aussieht, der das gewünschte Verhalten herstellt, ist also unklar. Es wird auch dem technologisch Interessierten schwer gemacht, sich zu informieren.

Das ist Bullshit. Das ist dirty code par excellance. Niemandem ist mit soetwas gedient. Und das in deinem Lehrbuch...! Erschreckend.

Ich habe vier Aspekte identifiziert, die über den Code verstreut und auch noch geschachtelt sind. Das ist das Gegenteil von Entkopplung.

Die Domäne ist die Formatierung von Bytes in hexadezimale und ASCII Darstellung. Aber weder ist diese Domäne als ein für sich stehendes Stück Logik herausgearbeitet, noch die anderen Aspekte.

Aber ich will Goethes "Besser machen, nicht nur tadeln, soll den rechten Meister adeln" folgen und Ihnen nicht vorenthalten, wie ich meine, das der Code aussehen sollte.

In der reinen Logik unterscheidet sich meine Lösung nur unwesentlich von der im Buch. Doch ich habe die Logik anders (bzw. überhaupt) in Container verpackt. Zunächst nur in Funktionen:

image

Das ist ein erster Schritt. In Main() ist der Prozess nun deutlich sichtbar. Außerdem ist klar, wo welche APIs benutzt werden. Wer sich zum Thema "Dateien lesen und schreiben" informieren will, schaut einfach bei Check_if_file_exists() und Read_blocks_from_file() rein; den Rest kann man dann ignorieren.

Wem das nun jedoch zu wenig objektorientiert ist, wer gern noch eine deutlichere Zusammenfassung der Subaspekte sehen möchte, der findet hier eine Lösung mit Klassen:

image

Zum Beispiel bündelt die Klasse FileSystemProvider die Methoden, in denen sich Logik befindet, die den API System.IO benutzt.

Die Aspekttrennug ist damit deutlicher. Der Preis dafür ist etwas Rauschen: in Main() müssen nun Klassen instanziert werden und Methodenaufrufe haben Objektnamen als Präfix.

Überhaupt haben meine Lösungen doppelt oder gar mehr als doppelt so viele LOC (lines of code). Ist das gut? Sollte Code nicht immer so kurz und knapp wie möglich sein? Trägt Knappheit nicht zur Lesbarkeit bei?

Klar, wenige Zeilen Code lassen sich leichter überschauen, sozusagen physisch. Aber inhaltlich ist das nicht unbedingt der Fall, nämlich wenn es sich um reine Logik handelt. Das war ja das Problem des ursprünglichen Codes. 40-50 LOC, also rund eine Bildschirmseite, das war nicht viel - und doch war es nur schwer verständlich.

Da bezahle ich gern den Preis von etwas Rauschen und mehr LOC, wenn ich dafür Verständlichkeit bekomme. Und die ist nun vorhanden, würde ich sagen.

Der Einstieg ins Programm ist sonnenklar. Er beschreibt lesbar, wie das Verhalten "Dateiinhalt als Hex Dump anzeigen" hergestellt wird:

image

Main() hat nun eine einzige, harte Verantwortlichkeit: Integration.

Und jede Klasse hat wiederum nur eine einzige Verantwortlichkeit, z.B. FilesystemProvider:

image

Sie kapselt die Nutzung des System.IO API. Dieser Aspekt zerfällt jedoch in zwei weiche: Prüfen, ob eine Datei existiert, und blockweises Lesen der Bytes aus einer Datei.1

Fazit

Das Single Responsibility Principle ist zentral für saubere Softwareentwicklung. Um es anwenden zu können, muss man allerdings wissen, was denn eine Responsibility eigentlich ist.

Mit der Definition, die ich gegeben habe, fällt es Ihnen hoffentlich leichter, die Verteilung von Responsibilities in Ihrem Code zu überschauen - und sie dann mit Refaktorisierung zu entzerren.


  1. Ok, ich gebe zu, ein kleinwenig unsauber ist der Code noch. Denn sowohl in Check_if_file_exists() wie in Get_filename() nutze ich zwei APIs. Zum einen den eigentlichen, um den es dort geht (Dateisystem- bzw. Kommandozeilenzugriff), zum anderen jedoch auch den für die Ausgabe auf der Konsole zur Fehlermeldung. Konsequenterweise müsste ich die Fehlermeldung in eine eigene Methode verpacken und z.B. in den ConsoleProvider verschieben. Eigentlich - denn hier lasse ich das mal so stehen. Es ist eine vergleichsweise kleine Sünde. Und wer will schon Perfektion? :-) Wenigstens bin ich mir der “Schmutzrückstände” bewusst.

Sonntag, 28. September 2014

Von Startup-Krautern lernen

Crowdfunfing ist in. Ich habe auch schon mehrfach gesponsort, gefundet, gespendet. Es ist einfach schön, motivierten Menschen unkompliziert ein bisschen helfen zu können - wenn mich die Idee begeistert.

So war es auch bei den Krautreportern. Ihre Vision hat mir in Zeiten des hypebasierten Einheitsjournalismus und der undurchsichtigen Verbindungen zwischen Medienmachern und Institutionen angesprochen:

Krautreporter ist ein tägliches Magazin für die Geschichten hinter den Nachrichten. Werbefrei, gemacht für das Internet, gegründet von seinen Lesern.

Jeden Tag mit vier ausführlichen, möglichst multimedialen Beiträgen von tollen Autoren. Emotional, relevant, journalistisch. In enger Zusammenarbeit mit unseren Mitgliedern. Auf einer modernen, leicht zu bedienenden Seite.

Das war kurz vor Kampagnenschluss im Juni 2014.

Also habe ich gesponsort - und das Kampagnenziel wurde erreicht. Naja, nicht allein durch meinen Sponsorenbeitrag ;-), aber ein kleinwenig habe auch ich dazu beigetragen. Das war ein schönes Gefühl. Und ich habe mich gefreut, bald engagierten, ehrlichen Journalismus zu genießen.

Doch seitdem... nichts. Oder fast nichts. Knapp eine Woche nach Fundingschluss schreiben die Krautreporter in ihrem Blog nämlich etwas. Nur was?

image

Man kann es nicht lesen. Es ist mit einem Passwort geschützt. WTF!

Außerdem erreicht die Email mit dem Passwort nicht alle Unterstützer. WTF!

Wenn man dann jedoch irgendwann den Zugang zu diesem "Geheimpapier" hat, dann liest man da von einem Plan. Der enthält wiederum einen Plan, nämlich für eine Software. Und noch einen Plan, einen Redaktionsplan. Und man liest von Verträgen die abzuschließen sind, einer Genossenschaft, die zu gründen ist, Büros die zu suchen sind usw. Lesernutzenstifend ist das alles nicht. WTF!

Wahrlich top secret Informationen. Gut, dass man das passwortgeschützt ins Web stellt und das Passwort eher nur auf Nachfrage mitteilt.

Dann im Juli aber ein nächstes Lebenszeichen. Dieses Mal ein öffentliches. Auch schön.

image

Aha, mit Hochdruck wird gearbeitet. Weiterhin natürlich im Verborgenen. Kenne ich irgendwoher solche Aussage. Ein Muster der Softwareentwicklung abgeschaut, scheint es ;-) Dazu noch ein Versprechen: Eine exklusive Beta-Phase für Mitglieder (dazu zähle ich auch mich als Sponsor) ab September 2014.

Nun ist es jedoch schon fast Oktober. Von einer Beta-Phase habe ich bisher nichts gehört. Auch berichtet das Blog seit Juli nichts mehr über den Fortgang der Hochdruckarbeit. WTF!

So nicht!

Leute, das macht keinen Spaß. So funktioniert doch keine Gründung im Jahr 2014. Haben die Krautreporter in den letzten Jahren soviel schreiben müssen, dass sie keine Zeit zum Lesen hatten? Zum Beispiel Bücher wie Lean Startup oder Kopf schlägt Kapital oder ReWork?

Eine Idee mit Crowdfunding statt über Banken zu finanzieren, ist ein erster Schritt in die digitale Welt. Schön, dass Journalisten sich das trauen.

Aber es gehört schon etwas mehr dazu, um auch in der digitalen Welt dann anzukommen. Wer es geschafft hat, Vertrauen aufzubauen und auch noch Geld einzusammeln, der darf sich nicht zurücklehnen und meinen, damit sei dem 21. Jahrhundert Genüge getan.

Wie gewonnen, so zerronnen - das gilt ganz besonders für Vertrauen. Umso mehr, als dass die versprochene Dienstleistung selbst mit Vertrauen zu tun hat.

Mein Vertrauen jedenfalls ist im Grunde verschwunden. Wie denn auch nicht? Was habe ich bekommen für mein Geld? Nichts. Unregelmäßige Blogbeiträge ohne Relevanz, dazu nicht eingehaltene Versprechen. WTF!

Das ist das Gegenteil von dem Journalismus, den die Krautreporter versprochen haben.

Aber so!

Ich will nicht zuviel spekulieren, doch es drängt sich mir der Verdacht auf: Selbst die motiviertesten Journalisten "alter Schule" stecken so tief im traditionellen journalistischen System, dass sie es schwer haben, sich modern zu bewegen.

Ihre Sorgfalt im Journalismus, ihre Liebe zur Recherche, zum Detail... das passt womöglich nicht zu dem, was der Markt draußen eben auch erwartet: Transparenz und Geschwindigkeit.

Transparenz

Guter Journalismus enthüllt. Nur sollten die Krautreporter das auch auf sich selbst beziehen. Was soll eine geheime Blogbotschaft? Was ich als Sponsor oder auch nur Interessierter möchte, das ist Offenheit, Ehrlichkeit. Kontinuierlich. Proaktiv. Über das, was bei den Krautreportern passiert.

Dafür gibt es Blogs, dafür gibt es Twitter, Facebook, Newsletter. Insbesondere in Phasen, wenn womöglich tatsächlich noch nicht der primäre Nutzen (hier: Artikel) geliefert wird.

Sobald die Produktion begonnen hat, urteile ich anhand der Produkte. Vorher möchte ich heutzutage und insbesondere, wenn da jemand die Crowd bemüht, auf dem Laufenden darüber gehalten werden, wie sich der Fortschritt auf dem Weg zur Produktion gestaltet.

Wo ist die Enthüllung der Krautreporter? Warum gibt es keinen Hintergrundbericht über das Geschäftsmodell, das Eigentumsmodell, die Arbeitsweise? Wo ist die Home Story - oder besser: Office Story - über die Redaktion? Wann gibt es eine Reportage über die Entwicklung einer zeitgemäßen Redaktion? Natürlich in zeitmäßer Form: live! Mit Foto, Video, Tonaufnahme, Text in Kombination.

Das ist doch kein technisches Hexenwerk. Dazu reicht ein Blog. Das gibt es schon. Oder auch noch ein Newsletter gelegentlich. Den gibt es auch schon. (Sagt man, denn erhalten habe ich noch keinen.)

Aber nein. Die Journalisten geben sich intransparent und technisch unwillig bis inkompetent. WTF!

Transparenz schafft und erhält Vertrauen. Wer Geld eingesammelt hat (oder noch etwas dazuverdienen möchte), tut gut daran, den Vertrauensaufbau nicht aus den Augen zu verlieren.

Die Krautreporter sind dafür leider kein gutes Beispiel.

Ein viel besseres ist Circuit Scribe. Die Idee dort: Elektronische Schaltkreise mit einem Stift auf Papier malen.

image

So ein Produkt marktreif zu entwickeln, dauert natürlich. Es war also nicht zu erwarten, dass nach Kampagnenende bald der Stift schon im Postkasten landet.

Aber Circuit Scribe hat angesichts dessen zeitgemäß gehandelt: Rund alle 14 Tage gibt es ein Update zum Fortschritt. Bisher sind es 28 in knapp 11 Monaten. Das ist Transparenz. Das ist Kommunikation, die Vertrauen aufbaut und erhält. Da behalte ich den Spaß an meinem Sponsorenbeitrag.

Geschwindigkeit

Transparenz ist gut. Doch über alle Transparenz sollte man nicht die Lieferung vergessen. Auch und gerade, wenn man noch nicht 100% sicher sein kann, was eigentlich geliefert werden soll. Das ist bei Software der Fall, das ist aber auch bei den Krautreportern der Fall.

Klar, man startet mit einer Idee. Die hat auch genügend Sponsoren motiviert. Wunderbar. Nur ist die bisher schwammig. Es gibt keinen Prototypen. Es gibt... nichts außer Versprechen.

Weder dient das dem Vertrauenserhalt, noch ist das nützlich für den Leser, und Feedback wird so auch nicht generiert.

Wenn es eine Lehre aus der Agilitätsbewegung oder dem Lean Startup gibt, dann ist es: Liefere in Iterationen. Generiere Feedback. Lerne schnell.

Dieser zentralen Lehre für Unternehmen in Märkten, wo unklar ist, was eigentlich wirklich, wirklich gesucht, gebraucht wird, widersprechen die Krautreporter.

Nicht nur sind sie intransparent, sie haben auch noch keine Kostprobe ihres Könnens geliefert.

Das verstehe ich nicht. Es handelt sich um kein materielles Gut. Wenn Ciruit Scribe Anlaufzeit braucht, wenn Bonnaverde nicht sofort liefern kann, wenn Solar Roadways nicht im Wochenrhythmus Nützliches an jeden Sponsor versendet, dann ist das alles mehr als verständlich.

Aber Krautreporter wie Softwareentwickler können und sollten so schnell wie möglich und so häufig wie möglich liefern. Kleine, feine Inkremente, zu denen die Nutzer Feedback geben können.

Die Krautreporter wollen ja einen digitalen Dienst aufbauen. Dann sollten sie auch die Vorteile der Digitalität nutzen.

Als Sponsor interessiert mich gerade bei dieser Art Produkt nicht, ob es ein Büro gibt, ob schon die perfekte Redaktionssoftware oder der perfekte Website gebaut ist, ob man genossenschaftlich organisiert ist und dergleichen mehr.

Das ist alles nice to have - wenn das Produkt stimmt. Nichts davon ist nämlich eine Bedingung für die Möglichkeit, den versprochenen Qualitätsjournalismus zu liefern.

Wenn am Ende jeden Tag geliefert werden soll, dann erwarte ich natürlich nicht, dass das in Form und Frequenz schon kurz nach Kampagnenende der Fall ist. Aber warum nicht jede Woche ein Artikel? Das müssen keine zehnseitigen Reportagen sein. Sogar work-in-progress würde ich womöglich nehmen. Ganz modern: Nicht immer alles nur in ultimativer Form perfekt am Ende dermaleinst ausliefern wollen, sondern dem Cult of Done folgen. Accept that everything is a draft! Laugh at perfection!

Warum stehen im Blog nicht schon Artikel? Achso, weil man ja nicht für alle Welt transparent sein will. Warum dann aber nicht einen eigenen Newsletter einrichten, mit dem ab und an schon erste Werke verschickt werden? Die können in HTML gesetzt sein. Oder man bietet einen Link zum Download eines perfekt gesetzten PDF an. Oder man macht es gleich digital lesefreundlich und veröffentlicht ePub/mobi Dateien. Achso, da fehlt dann ja noch die essenzielle Kommentarfunktion für die Leser? WTF!

Ist doch egal, wenn es nicht perfekt ist. So ist das bei Experimenten und in der Übergangsphase. Es geht nicht um Perfektion, sondern um Annäherung. Progress over completion sagt dazu das Elastische Manifest. Und reactivity over commitment.

Oder, achso, liegt es vielleicht daran, dass man erst überhaupt mit der Arbeit beginnen wollte, wenn das Fundingziel erreicht wurde? Hm... das wäre schade. Wer keine Vorleistung bringen will, wer nicht geben, aber nehmen will... der macht den Vertrauensaufbau schwer. Geschenke erhalten nicht nur, sondern stiften auch Freundschaft.

Doch auch wenn die Arbeit an Qualitätsartikeln erst nach erfolgreichem Funding begonnen hätte, sollte doch schon das eine andere Ergebnis bis heute zu sehen sein. Nicht jeder Qualitätsartikel braucht doch 3 Monate Arbeit. Das glaube und erwarte ich nicht.

Nun gut: Was die Krautreporter irgendwann mal wirklich als ihr ultimatives Produkt liefern... Wer weiß das schon? Das muss keiner dort wissen. Nein, ich möchte sogar, dass keiner dort meint, dass man das schon wisse. Denn da hätte ich meine Zweifel, dass man der Kraut wirklich zuhörte. Konstant.

Das Produkt der Krautreporter muss sich entwickeln. Vor und in den Augen der Nutzer. So ist das in digitalen Zeiten. Dazu passt aber weder Intransparenz noch Langsamkeit.

Fazit

Aus Fehlern kann man lernen. Schade, dass es gerade ein Projekt ist, das ich gesponsort habe, das nun Fehler macht. Aber so ist das halt mit Investitionen. Nicht alle tragen die Früchte, die man sich erwünscht. Deshalb Investitionen streuen.

In puncto Journalismus setze ich daher nicht nur auf die Krautreporter. Ich "sponsore" auch impulse, agora42, Hohe Luft, die Zeit und brand eins.

Das sind Publikationen in unterschiedlichen Entwicklungsstadien. Ich lese sie, solage sie mir Nutzen bieten. Die Bezahlmodelle sind flexibel genug, dass ich mich frei fühle, ultimatives Feedback durch Kündigung zu geben. Bisher stimmen die Inhalte aber. Also bin ich auch bereit, zu zahlen. Gute Arbeit darf etwas kosten. Die von anderen wie meine.

Dafür möchte ich aber auch, dass man liefert. impulse & Co tun das schon. Die Krautreporter zieren sich noch. Zu lange für meinen Geschmack.

Dass die Menschen dahinter motiviert sind, glaube ich gern. Ich ärgere mich auch nicht über mein Sponsoring. Doch das Ergebnis bleibt mager. Vielleicht können wir ja aber etwas daraus lernen und es besser machen, wenn die Reihe an uns ist, etwas Neues in die Welt zu bringen. Es braucht Transparenz und Geschwindigkeit, um Vertrauen nicht zu verspielen. Hier am eigenen Leib zu erfahren als Kunde des Journalismus. Im Projekt als Lieferant von Software zu beherzigen gegenüber dem Kunden.

PS

Kaum hatte ich diesen Artikel veröffentlicht und getwittert, meldete sich Krautreporter bei mir per Twitter und bat um Kontaktaufnahme zwecks Registrierung zum Newsletter. Das ist aufmerksam. Daraufhin habe ich mir dann nochmal die Postings bei Twitter und Facebook angeschaut. Vielleicht hatte ich ja Transparenz oder Geschwindigkeit übersehen.

Aber nicht wirklich. Auch die verpassten Newsletter haben meinen Eindruck nicht wirklich zerstreuen können. Formulierungen wie “Leider können wir noch keine Details verraten” oder “Mehr dazu bald!” liefern… wieder nichts.

Manchmal ist der Aufbau von Spannung ja spannend. Hier halte ich ihn für kontraproduktiv. Ich will nicht wissen, ob ein Krautreporter von einer Reise zurück ist. Ich will wissen, was er auf der Reise entdeckt hat. Wenn man dazu nichts sagen kann/darf/will, dann lieber schweigen.

Naja, nun warte ich mal ab. Im Oktober soll es wirklich, wirklich bestimmt mit der Betaphase losgehen. Und eine neue Plattform ist dann auch am Start. Es kann also nur besser werden ;-)

Samstag, 20. September 2014

Vom Problemtrichter zum Lösungsbaum

Software soll doch einfach nur laufen. Das wünscht sich der Kunde und meint damit, dass sie ein bestimmtes Verhalten zeigen soll. Sie soll gewünschte Funktionalität in bestimmter Qualität bieten, also z.B. rechnen, aber schnell, oder überweisen, aber sicher, oder Videos zeigen, aber für Millionen Benutzer gleichzeitig.

Verhalten wird hergestellt durch Logik. So nenne ich die Summe aus Ausdrücken (Berechnungen, Vergleiche, logische Verknüpfungen, Transformationen mit Sprach- bzw. Plattformmitteln), Kontrollflussanweisungen (if, for, while usw.) und Hardwarezugriffen (mit Plattformmitteln).

Experimentelle Zugabe:
Sie können diesen Artikel auch als eBook lesen: ePub, mobi/Kindle

Denken Sie sich Logik quasi als Programmierung auf Assembler-Niveau – allerdings abzüglich Unterprogrammaufrufe.

Damit Software gewünschtes Verhalten zeigt, braucht es keine Unterprogramme, keine Klassen, keine Vererbung, Polymorphie, Interfaces oder was der Sprachgimmicks mehr sind. Unterprogramme kommen später ins Spiel.

Die Aufgabe für uns Entwickler ist also „nur", für ein gestelltes Problem, die passende Logik zu finden. Das hat sich seit den Anfängen der Programmierung nicht geändert.

Und nochmal: Schon Unterprogramme gehören nicht mehr dazu. Sie sind nicht direkt verhaltensrelevant.

Die Frage ist nun: Wie kommt man vom Problem zu dieser Lösung? Ich sehe auf dem Weg dahin zwei Arbeitsphasen:

Phase 1: Probleme in feine Inkremente zermahlen

In der ersten Phase muss das Problem, muss der Anforderungsberg zermahlen werden. Aus einem Monolithen – „Ich will alles!" – muss ein feines Granulat werden. Wir können nicht „Logik herunterschreiben" für ein Lastenheft von 500 Seiten, nicht einmal für eines mit nur einer Seite.

Das ist uns allen auch klar. Wir bemühen uns um Anforderungszerlegung. Da werden Epics formuliert oder Use Cases oder User Stories. Wir versuchen, Software Feature für Feature zu realisieren.

Etwas fehlt mir dabei jedoch. Ich finde diese Zerlegungen oft wenig handfest. Ich finde sie für Kunde wie Programmierer schwer zu begreifen. Nicht weil ihre Sprache zu kompliziert oder die Beschreibungen zu ungenau wären – was natürlich immer der Fall sein kann –, sondern weil ihnen der Bezug zu dem fehlt, was Anwender wie Programmierer am Ende „anfassen".

Wo ist eine User Story in der Benutzeroberfläche einer Software? Keine Ahnung. Kann man mit dem Finger auf einen Use Case zeigen? Ich denke, nicht.

Oder wo ist eine User Story im Code? Keine Ahnung. Kann man mit dem Finger auf einen Use Case zeigen? Eher nicht.

All diese Zerlegungen lösen sich bei der Umsetzung auf, scheint mir. Sie sind irgendwie verschmiert über Benutzeroberfläche wie Codebasis.

Ist das eine gute Sache? Mir scheint, nicht. Denn damit verteilt sich die Logik, die zu ihrer Realisierung geschrieben wird. Wenn dann etwas zu ändern ist, wird es schwer zu lokalisieren, wo Eingriffe nötig sind. Der Widerspruch zum Single Responsibility Principle (SRP) ist vorprogrammiert.

Ich schlage daher eine andere Zerlegung von Problemen vor. Das bedeutet nicht, dass Sie keine User Stories & Co mehr schreiben sollen, wenn Chef/Kunde/Tradition es fordern. Doch Sie sollten dabei nicht stehenbleiben.

Ein für Benutzer wie Programmierer relevantes und greifbares Granulat besteht vielmehr mindestens aus diesen Größeneinheiten:

  • Anwendung
  • Dialog
  • Interaktion
  • Feature

Vor ein Problem gestellt, zerlegen wir es zunächst in Anwendungen. Die wiederum zerkleinern wir in Dialoge. Anschließend werden die zu Interaktionen zerrieben, welche wir zu Features zermahlen. Das ist kein linearer Prozess, das geht nicht geradlinig depth-first oder breadth-first. Dennoch ist es systematisch, weil es eben diese klare Hierarchie gibt, durch die wir uns vorarbeiten und in der wir die Ergebnisse verorten können.

Im Grunde geht es darum, das Gesamtproblem durch einen Trichter zu pressen, bis unten nur noch einzelne Features heraustropfen. Das hat natürlich auch mit dem cone of uncertainty zu tun: je größer ein Problem, desto unsicherer, ob und wie es gelöst werden kann.

Findling Anwendung

Eine grobe Zerlegung des Problems „ganzes Softwaresystem" findet zunächst statt in Anwendungen (App). Anwendungen sind separat startbare Programme. Denken Sie „Icon auf Desktop" oder „eigene URL" oder EXE, die man als Batch in einem Terminal-Fenster starten kann.

Eine App ist greifbar für den Anwender. Eine App ist aber auch greifbar für den Entwickler. Der weiß sofort, was er dafür tun muss: ein Projekt für seine Plattform in seiner IDE anlegen. Bei .NET gibt es da z.B. Konsolenprojekt, WinForms-Projekt, WPF-Projekt oder ASP.NET-Projekt. Bei Mono gibt es dann auch noch z.B. Xamarin.Forms für mobile Apps.

Jede App ist kleiner als das Ganze, das der Kunde will – zumindest solange es mehrere Apps gibt. Jede App ist relevant für den Kunden, sie stellt ein Inkrement dar, zu dem er Feedback geben kann. Und gleichzeitig – das ist mir wichtig – ist eine App als Inkrement klar im Code verortbar. Man kann nicht nur am Bildschirm, sondern auch in einem Code-Repository den Finger darauf legen.

Kiesel Dialog

Selbst wenn sich ein Lastenheft in mehrere Apps zerlegen lässt, sind die natürlich immer noch zu groß, als dass man für sie die Logik herunterschreiben könnte. Weitere Zerlegung ist nötig.

Deshalb sind für mich die nächste Granularitätsstufe Dialoge. Jede App erlaubt den Anwendern die Kommunikation durch einen oder mehrere Dialoge. Das können Fenster in einem GUI oder Seiten im Browser oder einfach Eingabeaufforderungen auf der Konsole sein. Oder es können sogar APIs sein, die per HTTP zugänglich sind.

Über Dialoge wird Softwareverhalten getriggert. Anwender „drücken Knöpfe" auf den Dialogen und die Software tut etwas (Funktionalität) hoffentlich effizient genug (Qualität).

Dialoge als kleinere Probleme, als Inkremente sind wieder für den Anwender greifbar – im wahrsten Sinne des Wortes, wenn wir an Touch-Displays denken. Er kann dazu Feedback geben.

Für Programmierer sind Dialoge aber auch greifbar. Dialoge werden gewöhnlich als Klasse (oder Modul) realisiert. Wenn die Anforderung lautet „Wir brauchen einen Anmeldedialog", weiß der Programmierer sofort, dass über einen Designer in seiner IDE eine Klasse anlegen muss.

Die Logik hinter einem Dialog wird kleiner sein als die der ganzen Anwendung sein. Ein Dialog hilft also, das Problem fassbarer, leichter lösbar zu machen. Gleichzeitig ist ein Dialog als Ausgangspunkt für diese Logik einfach im Code zu verorten. Man kann dort wie am Bildschirm den Finger darauf legen.

Sandkorn Interaktion

Doch Dialoge sind immer noch zu groß, um Logik für sie herunterschreiben zu können. Weitere Zerlegung tut Not.

Dialoge fassen nicht nur Anzeigeelemente zusammen, sondern auch das, was ich Interaktionen nenne. So wie in einer zwischenmenschlichen Kommunikation Dialoge aus einzelnen Interaktionen (der eine sagt etwas, die andere antwortet usw.) bestehen, ist das auch bei Software.

Eine Interaktion ist das Verhalten, das Software als Reaktion auf einen Trigger zeigt. Der Anwender gibt Daten in einem Registrierungsdialog ein, dann klickt er einen Button (Trigger) und die Software leistet Funktionalität in gewisser Qualität.

Von solchen Interaktionen kann es pro Dialog viele geben. Sie hängen an Buttons, Menüpunkten, Tastaturkürzeln, Mausbewegungen, Gesten usw.

Interaktionen sind für den Anwender relevant. Zu jeder kann er Feedback geben. Sie stellen Inkremente dar.

Und gleichzeitig sind Interaktionen konkret für den Programmierer. Ihre Entsprechung im Code ist immer eine Funktion. Das kann ein Event-Handler einer Dialogklasse sein oder eine Methode einer Webservice-Klasse.

Funktionen, also Unterprogramme kommen hier ins Spiel als Container für spätere Logik. Sie sind nicht Logik, sondern enthalten und ordnen Logik. Also dienen sie der Wandelbarkeit, nicht Funktionalität oder Qualität.

Zu jeder Interaktion können Eingaben und Ausgaben definiert werden inkl. ggf. darüber hinaus nötiger Zustand (in-memory oder in einer Datenbank) sowie Seiteneffekte.

Die Logik einer Interaktion wird kleiner sein als die eines Dialogs. Interaktionen machen es also leichter, das Problem zu lösen. Gleichzeitig sind die greifbar für Anwender wie Programmierer. Sie lassen sich im Code verorten.

Feinstaub Feature

Was ist ein Feature? Der Begriff „Feature" wird oft verwendet, wir haben ein intuitives Verständnis – doch was genau ist ein Feature?

Meine Definition ist simpel: Ein Feature ist ein Inkrement innerhalb einer Interaktion – das sich im Code mit einer Funktion repräsentieren lässt.

Features sind Softwareeigenschaften, zu denen der Anwender Feedback geben kann. Sie sind für ihn greifbar. Als Aspekte von Interaktionen sind sie nicht notwendig mehr Durchstiche durch die Software, sondern können in unterschiedlicher Tiefe des Verhaltens wirken.

Bei der Registrierung eines Benutzers repräsentiert durch eine Interaktion eines Dialogs einer Anwendung könnte es z.B. diese Features geben:

  • Benutzer speichern
  • Prüfen, ob Benutzername schon vergeben
  • Prüfen, ob Benutzername wohlgeformt
  • Fehler melden
  • Prüfen, ob Passwort wohlgeformt
  • Prüfen, ob Passwortwiederholung dem Passwort entspricht

Für jedes dieser Features ist für den Programmierer wieder sonnenklar, was er tun muss: eine Funktion schreiben.

Features sind mithin greifbar und relevant für Anwender wie Programmierer. Man kann einen Finger darauf legen während der Bedienung, man kann aber auch einen Finger darauf legen im Code.

Synthese

Wie gesagt, Use Cases oder User Stories können ihren Wert haben. Sie beschreiben gewünschtes Verhalten in einer Form, die nichts mit der Realisierung zu tun hat. Bei einer ersten Erkundung der Problemdomäne kann solche Unabhängigkeit nützlich sein, flüssiger zu kommunizieren.

Am Ende jedoch, wenn es darum geht, Software verlässlich zu realisieren, d.h. Problembeschreibungen in Logik-Lösungen zu übersetzen, da glaube ich, dass wir konkreter werden sollten.

Die Lösungsfindung sollte nicht beginnen, ohne ein Lastenheft, ohne Use Cases oder User Stories systematisch auf Apps, Dialoge, Interaktionen und schließlich Features zu mappen. Nur wird inkrementell für den Kunden vorangeschritten und gleichzeitig auch inkrementell im Code. Die Inkremente des Kunden lösen sich nicht mehr auf. Jedes auf allen Granularitätsebenen bleibt im Code als Einstiegspunkt für zukünftige Änderungen erhalten.

image

Das scheint mir grundlegend für hohe Wandelbarkeit, an deren Anfang Verständlichkeit steht. Und Code, in dem Inkremente sichtbar sind, ist verständlicher als Code, in dem sie aufgelöst sind.

Phase 2: Inkrementelle Probleme lösen

Um es auf den Punkt zu bringen: Ich glaube, dass wir mit dem Codieren, also letztlich dem Schreiben von Logik, nicht beginnen sollten, bevor wir nicht eine klar Vorstellung von mindestens einer Funktion haben. Nur dann lassen sich nämlich Akzeptanzkriterien sauber angeben. Die beantworten vor allem mit Beispielen die Frage: Welcher Input führt unter welchen Bedingungen (Zustand, Ressourcen) zu welchem Output und zu welchen Seiteneffekten?

Im Sinne agilen Vorgehens entspricht so eine Funktion natürlich einem Inkrement. D.h. die Codierung kann erst beginnen, wenn mindestens eine Interaktion aus dem Monolithen Lastenheft herausgepresst wurde. Besser aber noch, wir haben auch die noch weiter zermahlen in einige Features.

Mit Interaktionen und Features könnten wir losgehen. Aber ich glaube, wir sollten noch eine zweite Runde Nachdenken einlegen. Wer nach der Analysephase einfach eine Interaktion oder ein Feature herausgreift und darauf mit TDD einhämmert, um Logik auszutreiben, der arbeitet sicherlich hart – doch nicht notwendig smart.

Denn Analyse ist keine Problemlösung. Durch Analyse wurde nur aus Anforderungen herausgelesen, was ist. Das ist Forschung, geradezu Archäologie. Es wird gehoben, was der Kunde will – soweit er das a priori, d.h. vor der Erfahrung einer Lösung, überhaupt formulieren kann.

Wie jedoch die Lösung aussieht... welche Logik gebraucht wird... das ist nicht klar. Doch das ergibt sich nicht immer so einfach durch Vorlage von Akzeptanzkriterien selbst für ein Feature.

Dazu kommt, dass Features – selbst übersetzt in Funktionen gedacht – nach der Analyse nur lose nebeneinander liegen. Ein Feinstaubhaufen ohne weitere Struktur, selbst wenn er in Interaktionsbeutelchen getrennt gesammelt in Dialogkästen in App-Schränken liegt, ist noch keine Lösung.

Es muss ein Zusammenhang zwischen Features hergestellt werden. Und womöglich muss sogar noch weiter verfeinert werden, wodurch weitere Sub-Features entstehen, die dann wieder einen Zusammenhang brauchen.

Neben der Analyse, dem Erkennen ist deshalb noch Entwurf nötig. Das klein, fein, feinst zermahlene Problem muss im Rahmen einer Lösung wieder zu einem Ganzen zusammengesteckt werden. Features verweisen auf Logik, die zu finden ist. Doch wie hängt die Logik des einen mit der Logik des anderen zusammen?

Ich sehe Lösungen für das feine, inkrementelle Problemgranulat aus zwei Teilen bestehend. Sie beschreiben die Lösung auf unterschiedlichem Abstraktionsniveau: deklarativ, allgemein, grob und imperativ, konkret, detailreich.

Prozesse – Zusammenhang im Fluss

Die obere Ebene von Lösungen ist die der Prozesse. Dort besteht die Lösung auf einer Folge von Lösungsschritten. Was oben noch ungeordnete Aspekte (Features) waren, wird jetzt zu einer Sequenz:

  1. Prüfen, ob Benutzername schon vergeben, ggf. Fehler melden
  2. Prüfen, ob Benutzername wohlgeformt, ggf. Fehler melden
  3. Prüfen, ob Passwort wohlgeformt, ggf. Fehler melden
  4. Prüfen, ob Passwortwiederholung dem Passwort entspricht, ggf. Fehler melden
  5. Benutzer speichern

Oder eben zu einem Fluss:

image

Hier fließt allerdings nicht Kontrolle, sondern es fließen Daten. Deshalb ist dieser Fluss deklarativ. Er zeigt nicht Logik, er zeigt keinen Algorithmus, sondern eben „nur" einen Prozess.

Prozesse als Lösungen auf hoher Abstraktionsebene sind einfacher zu finden als Algorithmen. Hier kann man noch „Wünsch dir was" spielen. Man muss nicht genau wissen, wie die Schritte realisiert werden. „Wie geht das mit ‚Benutzer speichern'? Kommt da SQLite oder MongoDb zum Einsatz? Was ist das Datenbankschema?". Das muss nicht klar sein – auch wenn eine Idee davon hilft. Insofern auch bei den Lösungsphasen keine strickte Linearität.

Prozesse bringen Features im Rahmen von Interaktionen in eine Reihenfolge. Es wird sozusagen eine Kausalkette definiert, die vom Trigger bis zur Reaktion der Software über den Dialog reicht. Daten fließen vom Benutzer als Impulse, die Verarbeitungsschritte anstoßen und zu neuen Daten führen, die am Ende wieder beim Benutzer laden.

So existieren „Verhaltensprozesse" auf mehreren Ebenen:

image

Im Registrierungsdialog (Klasse) wird die Interaktion (Funktion) angestoßen, die aus einem Fluss von Features (Funktion) besteht. Solche hierarchischen Prozesse können viele Ebenen tief sein. Mit ihnen lassen sich die komplexesten Verhalten beschreiben – nicht obwohl, sondern weil sie deklarativ sind und eben nicht alle Details der Logik enthalten.

Prozesse fädeln Features in einen Fluss wie Perlen auf eine Schnur. Der kann eindimensional wie oben sein oder zweidimensional mit mehreren Armen oder dreidimensional durch Schachtelung. Die Summe der Features plus weitere Prozessschritte, auf die man während des Prozessentwurfs zur Lösungsentwicklung kommt, ergeben am Ende das gewünschte Gesamtverhalten im Hinblick auf Funktionalität wie auch Qualität.

Die Lösung ist damit vorhanden – allerdings noch nicht in Form von Code. Ein Prozess ist zunächst nur ein Plan, ein Entwurf. Allerdings ein für die Programmierung sehr geeigneter, weil jeder Prozessschritt in eine Funktion übersetzt werden kann, von der man auch schon weiß, in welchem Zusammenhang sie aufgerufen wird. Und das ist ja, wie oben schon gesagt, die Voraussetzung für die Codierung.

Algorithmen – Abarbeiten in Kontrolle

Ist der Prozess der „Herstellung von Verhalten zur Laufzeit" klar, dann – endlich – geht es an den Code. Denn nun bleibt nur noch die Logik zu finden und zu schreiben. Jetzt ist die unterste Ebene des Zerlegungsbaums erreicht, auf der die Lösung aus Algorithmen und Datenstrukturen besteht.

Jetzt erst ist imperative Programmierung gefragt. Hier geht es um Details. Algorithmische Details und technische Details. Das ist schwierig. Da ist der ganze Softwerker gefragt. Doch zum Glück sind die „Logikhappen" überschaubar. Durch das bisherige Zermahlen ist das Problemgranulat sehr fein geworden, so dass die unterste Ebene der Prozesshierarchie – ich nenne das die Operationen – je Schritt mit einer überschaubaren Anzahl Codezeilen auskommen sollte.

Zumindest sollte die inkrementelle Zerlegung mit anschließendem Prozessentwurf soweit gehen. Sie hören auf, wenn Sie meinen, dass ein Feature bzw. Prozessschritt mit vielleicht maximal einer Bildschirmseite Code in einer Funktion realisiert werden kann.

Aber was, wenn Sie auf eine solch feingranulare Zerlegung nicht kommen? Dann ist das das sichere Zeichen dafür, dass etwas im Argen ist. Entweder sind die Anforderungen noch zu schwammig. Oder Sie verstehen sie noch nicht wirklich – und können deshalb keine gute Vorstellung für eine Lösung entwickeln. Oder Ihnen fehlt technisches Know-how für den rechten Einsatz von API z.B. für die Persistenz oder Security.

Dann können Sie die Lösung noch nicht wirklich fertigstellen. Sie müssen eine Forschungsrunde einlegen, in der Sie Ihre Verständnis-/Wissenslücke irgendwie schließen.

Tun Sie das aber nicht am Produktionscode! Probieren Sie Technologie oder algorithmische Ansätze an Prototypen aus. Bauen Sie sich im Repository eine kleine Sandkiste für diese Zwecke (eigenes Verzeichnis, eigener Branch). Forschung sollte nicht mit Handwerk vermischt werden.

Wenn Sie später mehr wissen, kommen Sie zum Entwurf zurück und treiben ihn weiter, bis die Operationen alle überschaubare Größe haben. Dann und nur dann beginnen Sie mit der Codierung.

Zusammenfassung

Dass zur Softwareentwicklung irgendwie Zerlegung gehört, ist immer schon klar gewesen. Stepwise refinement oder functional decomposition klingeln bei Ihnen sicherlich im Hinterkopf. Das war so grundsätzlich immer richtig – ist mit der Objektorientierung jedoch etwas in Vergessenheit geraten und hat durch die Agilität einen neuen Blickwinkel erhalten.

Die Zerlegung der Anforderung sollte nicht nur Kundenrelevantes produzieren, das iterativ realisiert wird. Nein, ich halte es für ebenso wichtig, diese Inkremente so zu formulieren, dass sie eine Entsprechung im Code finden. Damit ist der Übergang von den Anforderungen in die technische Realisierung, vom SOLL zum IST möglichst bruchlos. Das kann die Verständlichkeit und Wandelbarkeit und auch Produktivität nur beflügeln.

Dann jedoch nicht stehenbleiben! Wenn die Probleme so klein wie Interaktionen und Features sind, lassen Sie die Tastatur am besten immer noch unterm Monitor. Nehmen Sie sich eine Feature oder eine Interaktion vor und entwerfen Sie die Lösung zunächst, statt sie sofort codieren zu wollen.

Sehen Sie die Lösung dabei auf zwei Abstraktionsebenen: der noch eher konzeptionellen Prozessebene und so spät wie möglich erst auf der konkret algorithmischen. Logik ist im doppelten Sinn das Letzte mit dem Sie sich beschäftigen wollen. Vermeiden Sie es, so lange es geht. Wo die Logik beginnt, wo es um Kontrollfluss und Code geht, wird es ekelig.

Das lässt sich nicht vermeiden, denn schließlich muss die Lösung irgendwann laufen. Dazu braucht es Code. Doch zu dem Zeitpunkt wollen Sie vorbereitet sein. Das, was zu codieren ist, sollte in kleine Häppchen portioniert sein. Imperative Programmierung ist schlicht die schwierigste.

„Teile und herrsche" ist also weiterhin das Mittel der Wahl, um Softwareentwicklung in den Griff zu bekommen. Die Frage ist nur, wie teilen? Da sehe ich zwei Sphären mit drei unterschiedlichen grundsätzlichen Ebenen:

  • Die Sphäre der Inkremente. Hier geht es um Probleme. Da muss zermahlen werden.
  • Die Sphäre der Funktionen. Hier geht es um die Lösungen. Da muss synthetisiert und realisiert werden.

Das Ergebnis der Problemzerlegung sind so fein geschnittene Probleme, dass jedes in eine Funktion übersetzt werden kann. Dafür können Akzeptanzkriterien festgelegt werden. Aber diese Funktionen müssen zu deklarativen Lösungen zusammengefügt und dann ausgefleischt werden. Das sind die Ebenen der Prozesse und Algorithmen.

image

Das ist nicht die functional decomposition von früher. Es ist anders, weil der Ausgangspunkt Inkremente sind. Es ist anders, weil Prozesse deklarativ und datenflussorientiert sind. Die Kontrollflüsse, die Algorithmen der functional decomposition sind eingehegt in kleine „Bläschen" ganz am unteren Ende der Zerlegungshierarchie. Auf keiner anderen Ebene gibt es Logik. Es gibt keine funktionalen Abhängigkeiten mehr. Das macht das Resultat sauber.

Wie kommen Sie dahin? Auch wenn die Zerlegung sehr geradlinig aussieht, läuft die Entwicklung wie ein Jojo über die Hierarchie hoch und runter. Breadth-first wechselt sich mit depth-first ab. Auf top-down folgt bottom-up.

Das Ziel ist jedoch klar: die feingranulare Zerlegung von Anforderungen und Entwurf nach diesem grundsätzlichen Schema. Problem und Lösung gehen in einander über.

Für mich funktioniert das seit Jahren gut und mit jedem Tag noch besser. Was früher irgendwie vielleicht früher oder später klappte und ansonsten holprig lief, ist nur systematisch und klar. Ich lasse mich nicht mehr von Dogmen ablenken („Agilität muss so und so laufen", „Objektorientierung muss so und so aussehen"). Es zählt, was zügig zu einem verständlichen und wandelbaren Resultat führt.

Ich kann Ihnen diese Sichtweise nur ans Herz legen.

Mittwoch, 20. August 2014

Konstruktivistische Softwareentwicklung für mehr Wandelbarkeit

imageCode sei ein Mittel, um die Realität abzubilden. Zumindest scheint mir das ein wesentlicher Antrieb hinter der Objektorientierung zu sein. Mit Objekten sollte es endlich möglich sein, Dinge und Verhältnisse der Welt möglichst 1:1 in Code zu fassen.

Das ist eine platonische Sichtweise der Welt. Sie geht davon aus, dass es etwas unabhängig von Code gibt, das irgendwie ist. Und dieses So-sein kann man mehr oder weniger getreu in Code abbilden. Und je getreuer es abgebildet wird, desto besser ist das für die Software in irgendeiner Weise.

Ich glaube, dieses Weltbild sollten wir nun hinter uns lassen. Es hat uns zu der Software geführt, die wir haben. Die funktioniert, die skaliert - aber die ist nur schwer wandelbar.

Vom Platonismus sollten wir zum Konstruktivismus wechseln.

imageWenn wir konstruktivistisch Software entwickeln, dann ist es nicht mehr wichtig, eine Realität getreu abzubilden.

Bei der platonischen Softwareentwicklung gibt es drei Bereiche, die in Deckung sind: die Realität (Domäne), unsere Wahrnehmung der Realität (Entwickler), Code. Als Entwickler einer Buchhaltungssoftware sehe ich eine Rechnung (Domäne) und lege dafür eine Klasse Rechnung und eine RDBMS-Tabelle Rechnungen an.

Bei der konstruktivistischen Softwareentwicklung hingegen, muss das, was ich als Entwickler in der Realität erkenne, nicht im Code auftauchen. Auch wenn ich eine Rechnung als Gegenstand in der Hand halte, führt das nicht Zwangsläufig zu einer Klasse oder einer einer Tabelle.

Leider muss ich hier von drei Bereichen sprechen, weil Software sich nicht selbst anpassen kann. Wir als Entwickler müssen das als Mittler tun. Deshalb haben wir bisher auch versucht, unsere Sicht der Welt in der Software zu manifestieren.

Aber es geht gar nicht um uns. Wenn wir für unser Überleben Rechnungen, Autos, Katzen, Götter, ein Ich in der Welt sehen wollen, dann ist das unsere Sache. Das ist unsere Konstruktion, die über das gelegt ist, was irgendwie ist.

imageDer Stoff aus dem die Konstruktionen sind, sind unsere sinnlichen Wahrnehmungen und a priori Grundkonzepte. Die haben wir uns nicht ausgesucht, sondern sie definieren uns. Die Evolution hat dazu geführt, dass wir als Organismen so sind, wie wir sind. Mit unserer Form und unseren Wahrnehmungen haben wir größere Stabilität in einer bestimmten Umwelt erreicht als mit einer anderen Form und anderen Wahrnehmungen.

So ist das mit Evolution. Es geht um größere Stabilität von Strukturen in einer (Um)Welt.

Und so ist es auch mit der Softwareevolution. Es geht darum, wie Software größere Stabilität erreicht in einer stetig im Wandel befindlichen Umwelt.

Wir haben es nun einige Jahrzehnte versucht, die Lebensfähigkeit durch getreuer Abbildung von durch uns wahrgenommener “dinglicher” Realität zu erhöhen. Das hat nicht so geklappt, wie gewünscht, würde ich sagen. Und nur das zählt.

Also sollten wir es anders versuchen. Befreien wir Software vom Abbildungszwang. Das einzige was zählt ist, dass Software als Ganzes ihren Zweck erfüllt (Funktionalität + Qualität) und sich zügig an eine gewandelte Umwelt anpassen lässt (Investitionssicherheit).

Die Struktur von Software muss also nicht zwangsläufig irgendetwas widerspiegeln, was wir als Menschen als Dinge in der Welt erkennen. Wenn wir ein Formular auf dem Tisch liegen haben, dann mag es noch hilfreich sein, das Formular in der Software als geDialog wiederzufinden. Ja, vielleicht ist das so. Vielleicht aber auch nicht. Machen wir uns da mal ganz locker.

Vor allem sollte uns ein Formular auf dem Tisch nicht dazu verleiten, unterhalb der Oberfläche im Code das Formular nochmal zu erschaffen. Und dann ein weiteres Mal auf der Festplatte - weder als Verbund von RDBMS-Tabelle noch als NoSql Dokument.

Damit will ich nicht sagen, dass das nicht so sein darf. Vielleicht ist es hier und da vorteilhaft, das Formular im Code wiederzufinden. Aber wir sollten das sich ergeben lassen und nicht im Sinne eines Weltbildes an den Anfang setzen.

Wenn wir schon mit dem Paradigma unserer Hauptwerkzeuge, den Programmiersprachen, Softwarestrukturen einem platonischen Weltbild unterwerfen, dann schränken wir die Freiheit der Evolution von Software ein. Wir machen es ihr schwer, in einer fluktuierenden Umwelt zu überleben, weil wir sie massiv fixieren.

“Abbildung der Realität” ist aber nicht, worum es geht. Einziger Markstein ist Zufriedenheit des Kunden - zu der gehört, dass Software quasi unsterblich ist, weil sie sich auf ewig anpassen lässt.

Natürlich haben wir wenig Erfahrung mit der Herstellung von Unsterblichen. Wer hat solche Erfahrung schon? ;-) In jedem Fall scheint mir jedoch ein schlechter Ausgangspunkt dafür, die Abbildung von objektbeladener Realität. Denn: Wenn sich diese Objekte in der Realität andauernd ändern, dann muss sich ja auch die Software andauernd ändern. Das umso häufiger und breiter, je tiefer die Objekte der Realität in der Software verankert sind.

Mir scheint, wir brauchen für evolvierbare Softwareentwicklung nicht mehr Objektorientierung, sondern das Gegenteil: Anti-Objektorientierung.

Java und C# und Ruby müssen wir deshalb nicht sofort auf den Müll werfen. Letztlich sind die Sprachen unschuldig. Auf den Müll muss das kontraproduktive platonische Weltbild. Denn das Weltbild steuert, wie wir die Sprachen einsetzen.

Mit einem neuen Weltbild können wir auch mit den überkommenen Werkzeugen durchaus neue, besser passende Strukturen schaffen. Konstruktivistische Softwareentwicklung ist mit C#, Java, Ruby, JavaScript, F# usw. möglich. Mal leichter, mal schwerer.

imageHören wir also auf, das Innen der Software so zu strukturieren, wie wir meinen, dass die dingliche Realität aussieht. In Ihrem Gehirn finden Sie den Computer nicht, auf den Sie schauen. Es darin auch nicht den Stuhl, auf dem Sie sitzen. Oder den Raum, in dem Sie sich befinden. Weder die Anatomie des Gehirns noch die Signale zwischen den anatomischen Strukturen haben irgendeine Ähnlichkeit mit der Umwelt. Innen ist nicht wie außen.

Allerdings befähigt Sie der Aufbau Ihres Gehirns (plus Körper), als Ganzes in der Umwelt zu überleben. Nur das ist es, was zählt.

Genau das müssen wir für Software auch erkennen. Ein erster Schritt: EventSourcing und EventStores.

Ein EventStore löst realweltliche Strukturen auf. Sie finden sich in der Software nicht mehr so wieder, wie wir sie als Entwickler in der Domäne gezeigt bekommen. Aber das ist nur der Anfang.

Ein nächster Schritt: Bounded Contexts und Aggregate.

Bounded Contexts und Aggregate lösen die Vorstellung von dem einen Datenmodell, von der einen Datenstrukturrealität auf.

Und noch ein Schritt: Inkrementelle Architektur. Das ist für mich die grundlegende Orientierung von Softwarestruktur an Durchstichen und Nutzen statt an Technologien, Infrastruktur und überkommenen Patterns (z.B. MVC, Layers).

Inkremente lösen die Vorstellung auf, dass es um Dinge ginge bei der Softwareentwicklung. Nicht jedoch das Dokument ist das Wichtigste, sondern der Prozess. Der ändert sich zuerst, ihm folgt ein eventuelles Dokument. Aber der Prozess, die Tätigkeit, das Verhalten sind weniger greifbar, sind keine Dinge. Deshalb tut sich die platonische Softwareentwicklung mit ihnen schwer.

Weitere Schritte werden wir noch erkennen, denke ich. Wenn wir uns darauf einlassen, unser Weltbild bzw. das von Software umzubauen.

In der Psychologie hat der Konstruktivismus “gewonnen”. Ich denke, davon sollten wir lernen als Softwareentwickler.

Was außerhalb von Software existieren mag, ist eine Sache. Eine ganz andere ist es, wie Software intern organisiert ist. Ähnlichkeit muss es zum Außen nicht geben. Nur Überlebenstauglichkeit. Dafür braucht es vor allem… Wandelbarkeit.

Sonntag, 17. August 2014

Warnung vor dem Microservice – Versuch einer Definition

Services sind wieder im Trend. Jetzt unter der Bezeichnung Microservice oder kurz: µService. Das empfinde ich grundsätzlich als Fortschritt. Schon vor einer “Moore-Periode” :-) (also 18 Monaten) habe ich das als konsequente Entwicklung beschrieben unter dem Titel “Software als Web of Services”. Und noch weiter zurück, im Jahr 2005, hatte ich mir in einer Artikelserie unter der Überschrift “Software Cells” Gedanken über eine grundsätzliche Anatomie von Software bestehend aus autonomen Einheiten gemacht, die heute aus einem gewissen Blickwinkel auch µService genannt werden könnten.[1]

Dennoch bin ich nicht ganz glücklich mit dem aktuellen Trend. Hatten vor 10 Jahren Services unter der Überschrift “Service Oriented Architecture” (SOA) etwas Politisches und schienen vor allem Sache von Managern und teuren Beratern. So sind Services heute als µServices demgegenüber eine Bewegung an der Softwareentwicklerbasis; nicht Manager interessieren sich dafür, sondern Geeks. Und damit ist der Schwerpunkt von der Politik zur Technologie geschwungen.

Das finde ich misslich. Denn so läuft die eigentlich gute Idee Gefahr, zu einem Cargo-Kult zu verkommen: Wenn es noch nicht recht klappt mit den Microservices, dann muss man nur noch RESTfuller werden oder mehr Netflix Open Source Infrastruktur zum Einsatz bringen. Oder?

Nein, mir scheint eine gewisse Warnung vor dem Microservice-Konzept angebracht. Es ist ein Werkzeug - und wie mit jedem Werkzeug kann man es zu Nutzen oder Schaden einsetzen. Die Gefahr, sich damit gehörig in den Fuß zu schießen, ist groß.

Es lohnt sich deshalb - wie so oft im Leben - vor dem enthusiastischen Einsatz ein bisschen nachzudenken.

Warum Microservices?

Als erstes stelle ich mal die Frage: Warum überhaupt µServices? Was bezwecken Netflix, otto.de, thoughtworks und andere damit?

Wenn ich Martin Fowler zum Thema lese, dann steht da zum Beispiel:

In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery.

Ah, es ist ein Architekturstil. Ja, aber warum? Der weitere Text redet viel über alles Mögliche - von Conway´s Law über Bounded Contexts bis “RESTish protocols” -, aber ich finde darin keine knackige Aussage zum Warum, d.h. zum Hauptantrieb hinter der Bewegung.

Hier und da kann man etwas heraushören wie hier:

Monolithic applications can be successful, but increasingly people are feeling frustrations with them - especially as more applications are being deployed to the cloud. Change cycles are tied together - a change made to a small part of the application, requires the entire monolith to be rebuilt and deployed. Over time it’s often hard to keep a good modular structure, making it harder to keep changes that ought to only affect one module within that module. Scaling requires scaling of the entire application rather than parts of it that require greater resource.

Aber was ist auf den Punkt gebracht der Hauptzweck? Oder anders: Welche Anforderungen des Kunden sollen µServices lösen helfen? Denn wie RDBMS, OOP, XML, MVC, WPF, Agilität usw. haben auch µService nur eine Berechtigung, wenn sie dem Kunden dienen.

Mein Unwohlsein mit der aktuellen µService-Diskussion rührt vor allem daher, dass mir genau dieser Punkt nicht geklärt zu sein scheint. Viele Köche rühren mit unterschiedlichen Vorstellungen am µService-Brei herum. Das ist kein Rezept für einen Erfolg der Idee, würde ich sagen.

Was Kunden wollen, hat für mich immer drei Aspekte:

  • Funktionalität, d.h. korrekte Operationen
  • Qualität, z.B. Performance, Skalierbarkeit, Usability, Security
  • Investitionssicherheit, d.h. hohe Produktivität des Teams und hohe Wandelbarkeit des Codes

Ausführlich habe ich das in meinem Buch “The Architect’s Napkin - Der Schummelzettel” und ein der Artikelserie “The Incremental Architect’s Napkin” erklärt.

Welchem dieser Aspekte sollen nun µServices dienen? Geht es um Qualität, sollen durch µServices nicht-funktionale Anforderungen wie Skalierbarkeit, Robustheit, Verfügbarkeit besser erfüllt werden? Oder geht es um Investitionssicherheit, indem µServices Code wandelbarer und Teams produktiver durch Wiederverwendbarkeit machen?

Mir ist das nicht klar nach dem, was ich bisher in der µService-Diskussion gehört habe. Deshalb glaube ich, dass es dazu keine einhellige Meinung gibt. Wahrscheinlich sind sich die meisten Diskutanten darüber selbst nicht klar.

Na gut, wenn das so ist, dann mache ich mal klar, was aus meiner Sicht der Zweck von Microservices sein sollte. Das kann nämlich nur einer sein. Auch als Konzept sollten µServices dem Single Responsibility Principle folgen.

Der Zweck von µServices ist, die Wandelbarkeit von Software zu erhöhen.

That´s it. Nicht mehr, nicht weniger. Vor allem: nur dies.

Microservices sollen es einfacher machen, Software über lange Zeit an neue Anforderungen anzupassen. Der Kunde mag sich neue Funktionalität oder bessere Qualität wünschen. Technologien mögen sich wandeln und sollen Eingang finden in eine Software. Der Arbeitsmarkt mag sich ändern. All diesen Fluktuationen soll Software möglichst leicht nachgeführt werden. Dabei sollen µServices helfen.

Eingegrenzt wird die Zweckerfüllung natürlich durch andere Anforderungen. Wie immer µServices aussehen mögen, sie dürfen Funktionalität und Qualitäten nicht einschränken. Denn eine wunderbar wandelbare Software, die entscheidende Operationen nicht beherrscht oder die zu langsam oder unsicher ist, wird kaum Gnade finden beim Kunden.[2]

µService dienen der Wandelbarkeit. Das bedeutet umgekehrt: µService sollten nicht zum Einsatz kommen, wenn es um Qualitäten geht. Wer z.B. denkt, “Die Skalierbarkeit unserer Web-Anwendung sollte besser werden. Lass uns doch mal µServices ausprobieren.”, ist aus meiner Sicht also auf dem besten Weg, sich mit µServices in den Fuß zu schießen.

Wenn zufällig und als Nebeneffekt durch Microservices auch noch eine Qualität steigt, dann ist das natürlich willkommen. Ziel sollte das jedoch nicht sein. Im “Softwarebaum” befinden sich µServices auf dem Evolvability Branch.

image

Sie sind Container, die zur vor allem dazu da sind, Code zur Entwicklungszeit zu kapseln. Dass sie auch noch gleichzeitig als Betriebssystemprozesse Hosts sind, also irgendwie im Quality Branch auftauchen… das ist halt so. Das muss dann auch bei der Erfüllung von Qualitätsanforderungen berücksichtigt werden. Wie gesagt, denen dürfen µServices nicht im Weg stehen.

Definitionsversuch

Nun da - aus meiner Sicht - geklärt ist, was Microservices eigentlich sollen, kann die Frage angegangen werden, wie sie ihre Aufgabe erfüllen können. Was sind die Charakteristika von µServices, wie lautet ihre Definition?

Aus dem Zweck leitet sich dafür eine Prämisse ab: Alle Microservice-Merkmale sollen dem Zweck dienen. Wer sagt, zu einem µService gehöre Merkmal X, der muss klar machen, wie X hilft, Wandelbarkeit zu erhöhen, ohne Qualitäten (über Gebühr) zu kompromittieren.

Wie am Softwarebaum abzulesen, sind Microservices nicht allein. Es gibt weitere Container, von denen sie sich unterscheiden müssen. Diese Container sollte jeder Entwickler auch im Blick haben, um abwägen zu können, ob sich µServices schon lohnen, oder Wandelbarkeit mit weniger Aufwand herstellen lässt. Denn soviel sollte schon jetzt klar sein: µServices sind technologisch keine Kleinigkeit und können leicht unerwünschten Einfluss auf Qualitäten haben. Warnung also davor, µServices “einfach mal” und “nebenbei” einzuführen. Mit ihnen angemessen umzugehen bedarf Übung. Und üben sollte man nicht am Produktionscode und nicht am Produkt, sondern in einem Übungsraum. Das Coding-Dojo der Clean Code Developer School bietet dafür eine Menge Aufgabenstellungen.

Die Container des Wandelbarkeitsastes sind für mich in wachsener “Größe”:

  • Funktion
  • Klasse
  • Bibliothek
  • Komponente
  • µService

Klassen enthalten Funktionen, Bibliotheken enthalten Klassen usw. Jede “Größenordnung” unterscheidet sich dabei von kleineren durch ein Merkmal. Bibliotheken z.B. sind opaque (oder gar binär), während Klassen noch als Quellcode vorliegen. Dadurch wird die Wiederverwendbarkeit stark erhöht. Komponenten hingegen haben wie Bibliotheken einen Kontrakt, doch der ist separat. Das ermöglicht quasi industrielle Arbeitsteilung bei der Entwicklung.

Und inwiefern gehen µServices über Komponenten hinaus? Der Kern der Definition von µServices sieht für mich so aus:

µServices sind Komponenten mit plattformneutralem Kontrakt.

Es sind also opaque Container (wie Bibliotheken) mit einem separaten Kontrakt (wie Komponenten), der aber eben auch noch plattformneutral ist.

Komponentenkontrakte sind plattformspezifisch. Eine CLR-Komponente kann eine andere CLR-Komponente benutzen, eine JS-Komponente kann eine andere JS-Komponente benutzen. Aber eine JVM-Komponente kann keine Ruby-Komponente benutzen.

Mit Komponenten zu arbeiten, bietet schon eine gehörige Portion Wandelbarkeit und Produktionseffizienz durch die Separation der Kontrakte von der Implementation. Solche Kontrakte entkoppeln mehr als die impliziten von Bibliotheken. Und sie ermöglichen eine parallele Implementation auf beiden Seiten des Kontrakts.

µServices gehen darüber hinaus. Indem die Kontrakte plattformneutral gehalten werden, können Client wie Server, Producer wie Consumer mit unterschiedlichen Plattformen entwickelt werden. Ein CLR-Microservice kann einen JVM-Microservice benutzen, ein JS-Microservice kann einen Ruby-Microservice aufrufen.

Plattformneutrale Kontrakte entkoppeln noch weitergehend. Sie eröffnen mehr Optionen für die Implementierung eines Containers. Plattformen und Sprachen können nach Zweckmäßigkeit, Toolverfügbarkeit, Frameworkangebot usw. gewählt werden. Oder man entscheidet sich nach Arbeitskräftelage dafür.

Alle µServices eines Softwaresystem können heute auch mit derselben Plattform realisiert sein - und erst im Laufe der Zeit wird man hier und da polyglott. In anderen Fällen mag man gleich z.B. auf der CLR mit C# und F# beginnen.

Ein plattformneutraler Kontrakt ist natürlich keine C++ .h-Datei und keine CLR Assembly mit Interfaces darin. Aber eine REST-Schnittstelle, über die Json-codierte Daten fließen, die ist plattformneutral. Oder eine WSDL-Servicedefinition. Oder eine Protokolldefinition wie SMTP oder POP3.

Ist das nun aber schon die ganze µService-Definition? Hm… ich würde sagen, ja. Alles andere ist Kommentar und Auslegung.

Kommentare

Kommentar #1: µServices sind Prozesse

Dass µServices in eigenen Betriebssystemprozessen laufen, ist eine Folge der Definition. Über Plattformen hinweg kann nicht im selben Prozess kommuniziert werden. Insofern sind µServices relevant für den Qualitätsast des Softwareuniversums.

Auf Qualitäten Einfluss zu nehmen, ist nicht der Zweck von µServices, aber es lässt sich auch nicht vermeiden. Von einer Komponentenarchitektur, die (zumindest konzeptionell) neutral in Bezug auf Qualitäten ist, zu einer µService-Architektur überzugehen, ist also kein Selbstgänger.[3]

Weil µServices Prozesse sind, muss natürlich dafür gesorgt werden, dass die überwacht werden. Und bei Bedarf neu gestartet. Und einfach zu deployen sind. Doch Achtung! Das ist kein Selbstzweck, sondern nur eine Folge der Form von µServices, die wiederum eine Folge ihres Zweckes ist.

µServices dienen dazu, monolithische Software aufzubrechen. Wo heute vielleicht ein Fat Client mit einem Fat Backend spricht, reden morgen ein Dutzend µServices im Frontend und Backend miteinander. Aus geringer Verteilung von Code wird hohe Verteilung von Code. Das bedeutet, auch wenn bisher die Fallacies of Distributed Computing noch kein großes Thema gewesen sein mögen, dann werden sie es jetzt. Wieder: Achtung! µServices sind kein Kinderspielzeug, sondern eine geladene Waffe.

Kommentar #2: Kontrakthürden

Besonderes Augenmerk verdient der Kontrakt von µServices. Er repräsentiert ihren “Zuschnitt”. Er definiert, wie oft in welchem Umfang Daten fließen. Das hat unmittelbare Auswirkung auf die Änderungsanfälligkeit und die Performance. Kein Wunder also, dass schon gebeten wird, “Please avoid our mistakes!”.

Wer bisher keine Erfahrung hat, mit verteilten Systemen, wer bisher keine Erfahrung hat mit expliziten Kontrakten… der wird keinen schmerzfreien Einstieg in µServices-Architekturen finden.

Soviel lässt sich über µService-Kontrakte sagen: sie sind zentrale Erfolgsfaktoren. Eines lässt sich jedoch aus meiner Sicht nicht sagen, dass es dringend RESTful-Kontrakte sein sollten oder dass Json oder Atom zu Einsatz kommen müssten. Und ich gehe noch weiter: Auch die Kommunikation über TCP halte ich nicht für definitionsrelevant.

Es ist Vorsicht geboten, bei der Diskussion über und der Planung von konkreten µService-Architekturen nicht über Bord zu gehen angesichts technologischer Wellen. Technologien oder gar Produkte sollten keine Triebfedern für µServices sein. Auch bei Microservices gilt: Keep it simple, stupid.

µServices sind Mittel zur Erhöhung von Wandelbarkeit. Dieser Zweck darf nicht durch Spaß am technologischen Feuerwerk vereitelt werden.

Kommentar #3: Asynchrone Kommunikation

µServices sollten asynchron kommunizieren, heißt es hier und da. Das sehe ich ähnlich. Es ist für mich eine Folge ihrer Form als Prozess. Zwischen Threads (auf denen Prozesse basieren) kann nur asynchron kommuniziert werden. Da sollte man ehrlich bis in den API sein.

Allerdings gefällt mir der technologische Unterton bei diesem Merkmal nicht. Deshalb ist Asynchronizität für mich derzeit auch nur ein Kommentar und Teil der Definition. Wichtiger noch als Asynchronizität finde ich auch Messaging, d.h. die Kommunikation über Einwegnachrichten zwischen unabhängigen Funktionseinheiten.

Zwischen µServices gibt es keinen Kontrollfluss mehr, sondern nur Datenfluss. Jeder µService läuft ja in einem anderen Prozess, hat also seinen eigenen Kontrollfluss in seinem Thread. Das sollte sich im Schnitt der µServices und damit in ihren Kontrakten niederschlagen. Ansonsten droht schnell eine Verletzung von Martin Fowlers First Law of Distributed Objects, vor der er selbst im Zusammenhang mit µServices warnt.

In diesem Zusammenhang stellt sich auch die Frage, was eigentlich der Unterschied zwischen µServices und den guten alten Servern ist. Bisher haben wir Client/Server-Anwendungen geschrieben - mit zunehmender Zahl an Servern. Sind µServices nicht einfach Server mit neuem Titel?

Nein. Das wäre eine Kategorienvermischung. Server gehören zur Kategorie der Hosts - jedenfalls in meiner Vorstellung vom Softwarebaum. Server dienen der Herstellung von Qualitäten. µServices hingegen sind Container und dienen der Herstellung von Wandelbarkeit.

Server und µServices passen aber gut zusammen: µService sind die Bausteine von Servern (und Clients). Um Qualitäten zu erfüllen, wird für gegebene Funktionalität im Rahmen des Architekturentwurfs zunächst bestimmt, wie die auf Hosts zu verteilen ist. Ist eine Verteilung überhaupt erforderlich, wenn ja, auf wieviele Server?

Erst anschließend sollte darüber nachgedacht werden, ob und welche Clients bzw. Server in µServices zerlegt werden. Nur dann ist sichergestellt, dass µServices nicht die Erfüllung von Qualitätsanforderungen behindern. Der Rahmen für Container sind immer Hosts. Der Softwarebaum wächst mithin nicht in alle Richtungen gleichzeitig, sondern in Phasen, die über seine Äste laufen:

image

Kommentar #4: Einfaches Deployment

Schon mit Bibliotheken wird die physische Codebasis auseinandergerissen. Es entstehen mehrere separat deploy- und versionierbare Einheiten. Komponenten machen das deutlicher. µServices legen hier nochmal nach. “Nur” möchte ich sagen, denn das Problem ist ja nicht neu. Deshalb gehört “easy deployment” auch nicht zur Definition von µServices.

“Easy deployment” von einzelnen sich immer öfter wandelnden Containern ist eine Folge ihres Zwecks und ihrer Definition. Hierin besteht ein Teil des Preises, den man für mehr Wandelbarkeit zahlen muss. “Easy deployment” von µServices ist technologisch aufwändiger als das von Komponenten, allemal, wenn womöglich dafür das Gesamtsystem nicht offline genommen werden soll. Also: Achtung!

Welcher Deployment-Aufwand rechtfertigt welchen Wandelbarkeitsnutzen?

Das ist sicherlich auch eine Infrastruktur und Plattformfrage. Wenn es im .NET-Ökosystem dafür weniger Unterstützung gibt als im JVM-Ökosystem, dann fällt bei CLR-Softwaresystemen die Entscheidung für µServices vielleicht schwerer. Aber lohnt deshalb ein Umstieg auf JVM? Oder ist die JVM-Welt später eine Hürde für weitere Flexibilisierung, weil die dortige Infrastruktur es schwer macht, sie noch weiter zu öffnen für non-JVM Sprachen in der Zukunft?

µServices sind eine Waffe zur Bekämpfung und Vermeidung von systemrelevanten Größen. Keine Sprache, keine Plattform, keine Datenbank, keine Technologie, kein Konzept usw. soll so groß und mächtig werden, dass sinnvolle Veränderung behindert wird. Also Vorsicht beim enthusiastischen Wechsel von Plattform A zu Plattform B, weil dort heute µServices irgendwie besser gehen. Der Wechsel kostet Zeit - Jahre womöglich - und wer weiß, wer dann den Preis für die beste Plattform hält.

Jede Form von Plattformfokus steht für mich im Widerspruch zur Grundbotschaft von Microservices. Wer sich für µServices also plattformmäßig strategisch einschränkt, läuft Gefahr, das Ziel zu verfehlen.

Kommentar #5: Die Größe von Microservices

Am Anfang der Diskussion stand der Umfang von µServices als ein Definitionskriterium. Sie sollten - sagen manche - nicht länger als 100 LOC sein.

Ich finde ein solches hartes Kriterium nicht hilfreich. Es fördert den Cargo-Kult. Besser gefällt mir “fits inside your head”. Damit wird nämlich betont, dass es um eine Sinneinheit geht.

Eine solche Umfangbeschreibung ist andererseits zu schwammig, um in die Definition aufgenommen werden. Stattdessen also Kommentar: Wenn Wandelbarkeit durch geringen Codeumfang begünstigt wird, dann sollten µServices natürlich nicht zu groß werden.

Aber wie groß? Verständlichkeit ist ein Kriterium. Ein anderes ist für mich der Aufwand, um einen µService komplett neu zu schreiben.

Wir haben ein Problem mit Software, wenn wir mehr und mehr refaktorieren müssen, um Änderungen anzubringen. Refaktorierung zeigt an, dass ein Sauberkeitsleck existiert. Das ist wie mit einem Speicherleck.

Darauch kann man auf zweierlei Weise reagieren. Man kann das Leck versuchen zu stopfen: Einfürallemal den Code korrigieren, der dafür sorgt, dass Speicher nicht wie gewünscht freigegeben wird. Einfürallemal die Unsauberkeit beheben und dann am besten nie wieder dreckigen Code schreiben.

Oder man kann auf das Stopfen verzichten und startet das Programm periodisch neu, bevor der Speicher “ausgelaufen” ist. In Bezug auf die Sauberkeit bedeutet das, man schreibt den unsauberen Code neu.

Wenn ich mich nicht irre, ist es Netflix, die ihre Hosts herunterfahren und neu starten, um gar nicht erst in Speicherlecks zu laufen. Sie anerkennen, dass es “irgendwie” immer wieder zu Speicherlecks kommen kann - in eigenem Code oder Infrastruktur - und dass es sehr teuer sein kann, diese Lecks zu stopfen. Viel teurer, als Hosts gelegentlich neu zu starten.[4]

Insofern glaube ich, dass wir zu Architekturen kommen müssen die uns erlauben, öfter Code neu zu schreiben: rewrite over refactor. Dafür sind µServices nicht nötig, das kann man auch schon mit Komponenten erreichen. Doch µServices fügen dem noch ein Level an Flexibilität hinzu. Denn mit µServices gibt es noch mehr Freiheit beim Rewrite. Und die Neuentwicklung kann zur Laufzeit in das System eingebracht werden.

Ein Rewrite kostet natürlich auch Geld. Wie groß kann ein µService also werden, damit ein Rewrite noch möglich ist? Das hängt von vielen Faktoren ab: Entwicklerkompetenz, Domänenkomplexität, Budget usw.

Ich glaube aber, dass eine gewisse Obergrenze bei einem Aufwand von 1–2 Monaten liegt. Für diesen Zeitraum kann ein Projekt zur Not mal die Füße still halten. Dabei bedenke man: Was in dieser Zeit neu geschrieben werden kann, hat wahrscheinlich 2 bis 10 Mal soviel Aufwand in der Erstentwicklung inklusive aller Refaktorisierungen gekostet. Und der resultierende Umfang ist 30% bis 80% geringer.

Rewrites sind aus meiner Sicht das beste Mittel, um die LOC einer Software zu reduzieren. Nur müssen dafür abgeschlossene Einheiten vorhanden sein, die man eben neu schreiben kann mit überschaubarem Aufwand. Das sind µServices.

Refactoring wird damit nicht verschwinden. Ein Rewrite soll ja nicht bei jeder kleinen Änderungen stattfinden. Das halte ich auch für unökonomisch. Bis zum Rewrite mag es mehrere Refactorings geben. Aber immer gibt es die Option, alternativ neu zu schreiben. Diesen Freiheitsgrad hat ein Projekt normalerweise nicht, weil es immer als Ganzes betrachtet wird. Mit µServices jedoch wird ein Horizont eingezogen, innerhalb dessen es sich anbietet, anders zu verfahren als auf das Ganze gesehen.

Und Refactoring wandert natürlich auf eine höhere Ebene. Das Gesamtsystem bestehend aus allen µServices lässt sich immer noch nicht neu schreiben. Das Zusammenspiel der µServices ist deshalb über die Zeit sicherlich zu refaktorisieren.

Es ist wie bei einem Organismus: Der ist ein Ganzes, dessen Struktur sich immer wieder anpasst (Refaktorisierung) und dessen Teile ständig ausgetauscht werden (Zelltod, Zellteilung).

Mit µServices bleiben Anwendungen als Ganzes bestehen, passen sich an - bestehen nach einer gewissen Zeit jedoch nicht mehr aus denselben Teilen wie zu Anfang. µServices unterliegen einer ständigen Erneuerung. Eine gewisse Zeit werden sie gepflegt; dann “sterben” sie und werden ersetzt durch eine komplett neu geschriebene Version.

Wie gesagt, das geht auch grundsätzlich schon mit Komponenten. Microservices bringen aber eben noch eine Portion Entkopplung und Autonomie mit - die auf der anderen Seite ihren Preis hat.

Kommentar #6: Scope

Was soll die Aufgabe von µServices sein? Ist sie dieselbe wie die von bisherigen Servern? Das kann nicht sein, weil µServices Bausteine von Servern sind (s.o.). Ein Teil kann nicht das Ganze sein.

Client/Server-Beziehungen werden aus Gründen der Qualitätssteigerung eingeführt. In µServices muss Software hingegen aus anderen Gründen zerschnitten werden. Ihr Zweck ist ja die Steigerung der Investitionssicherheit. Es geht vor allem um Wandelbarkeit. Deren Kernvoraussetzung ist Entkopplung.

Bei µServices müssen daher zwei Dinge zusammenkommen: 1. Der Schnitt durch die Anforderungen muss so gesetzt sein, dass ein mehr an Wandelbarkeit entsteht. 2. Der Schnitt muss so verlaufen, dass der erhöhte Kommunikationsaufwand zur Laufzeit insb. die primären Qualitäten Performance und Skalierbarkeit nicht beeinträchtigt.

Für mich folgt daraus, dass µServices Inkremente sind. µServices stehen für Interaktionen (im Sinne des Softwarebaums) oder für Use Cases (als Gruppen von Interaktionen) oder für Features, d.h. anwenderrelevante Aspekte von Interaktionen.

In jedem Fall repräsentiert ein µService eine Untermenge des Anwendungsscope. Er stellt ein vertikales Teilstück dar, eine Scheibe - womöglich sogar einen “kopflosen” Durchstich.

Damit ist ein µService eben kein Server. Denn Server sind horizontale Schichten. Schichten werden übereinander gelegt, um Inkremente herzustellen. Mit einer Schicht allein, kann ein Anwender nichts anfangen. Dazu kann ein Kunde kein Feedback geben. Schichten interessieren Kunden nicht, Scheiben hingegen schon.

Server im Sinne von Schichten als tiers, also als verteilte Funktionseinheiten, sind Sache der Qualitätsherstellung. Server sind Hosts. µServices sind keine Hosts, sondern Container. Als solche könnten sie zwar grundsätzlich auch als Schicht gedacht sein. Denn das Schichtenmodell ohne Verteilung war als Architekturmuster als Hilfe zur Steigerung der Wandelbarkeit gedacht. Doch ich glaube, dass das eben nicht die Aufgabe von µServices sein darf. Dafür gibt es Komponenten. Nein, µServices sollten für weitergehende Entkopplung vertikale Teilstücke einer Software repräsentieren.

Zwischen schichten gibt es vielfältige funktionale Abhängigkeiten. Zwischen Inkrementen jedoch sind die funktionalen Abhängigkeiten viel geringer oder gar ganz abwesend. Die Abhängigkeit zwischen Inkrementen wie Dialog, Interaktion und Feature reduzieren sich womöglich auf logische Abhängigkeiten, die sich in Daten manifestieren.

Deshalb ist es interessant, µServices nicht einmal von denselben physischen Daten abhängig sein zu lassen. Jeder µService kann sein eigenes Domänenmodell haben oder gar seine eigene Datenbank. Das führt zu Redundanz - aber deren Preis ist langfristig womöglich kleiner als der hoher Abhängigkeiten von einem gemeinsamen Modell und einer zentralen Datenbank.

Das eine Domänenmodell und die eine Datenbank sind im Grunde die Hauptsymptome von monolithischer, d.h. schwer wandelbarer Software. Wenn der Zweck von µServices darin besteht, die Wandelbarkeit nach vorne zu bringen, dann sollten sie es also genau in dieser Hinsicht anders machen.

Zum Datenmodell gehört auch das Thema Zustand im allgemeinen und Session im Speziellen. Dürfen µServices Zustand und/oder Sessions haben?

Ich sage Ja. Warum nicht? Unter einer Bedingung: Zustand und Session sollten die Wandelbarkeit nicht beeinträchtigen. Tun sie das, dann raus damit oder den µService anders schneiden.

Wenn über Zustand und Sessions gemeinhin kritisch nachgedacht wird, dann nicht mit Blick auf die Anforderung Wandelbarkeit. Es normalerweise um Qualitäten, d.h. Merkmale von Hosts. Sessions wirken sich z.B. negativ auf die Skalierbarkeit aus.

Wenn über µServices nachgedacht wird, sollten solche Fragen jedoch schon geklärt sein. µServices strukturieren Software im Rahmen von Hosts.

Was aber, wenn die Verteilung eines Host auf mehrere µServices neuerlich Qualitätsfragen aufwirft? Dann muss man sie in Balance bringen mit dem Zweck von µServices.

Das gilt für alle Fragen nach dem Motto “Darf/soll ein Microservice so und so aussehen?” Es ist immer sofort zurückzufragen: Widerspräche das dem Zweck von µServices? Widerspräche es der Definition von µServices?

Ableitungen

Was geeignete Schnitte durch den Scope eines Softwaresystems sind, muss im Einzelfall ausgetüftelt werden. Der Rahmen für die Schnitte ist nun jedoch klar:

  • Was herausgetrennt und zu einem µService gemacht wird, darf nur so groß sein, dass es sich vergleichsweise schnell immer wieder neu schreiben lässt.
  • Es soll ein Inkrement darstellen, das Hoheit über seine eigenen Datenmodelle hat.
  • Es muss per Messaging in das große Ganze eingebunden werden können.
  • Seine Autonomität als Prozess darf Performance und Skalierbarkeit des Ganzen nicht beeinträchtigen.

Das sind für mich Ableitungen aus der Zweck und Definition. Dass die möglichst simpel und klar gehalten werden, ist aus meiner Sicht sehr wichtig. Wir verlieren sonst Freiheitsgrade und laufen Gefahr, technikverliebt durch die Gegend zu entwickeln.

Dass simple Definitionen nicht immer einfach umzusetzen sind, steht auf einem anderen Blatt. Auch über ganz Simples lässt sich lange trefflich streiten. So kommt es dann zu Kommentaren, Auslegungen und unterschiedlichen Schulen. Im Kern sollten sich jedoch alle einig sein. Deshalb sollte der Kern klein und leicht fasslich sein.

Hier ist mein Vorschlag für einen Kern des µService-Konzepts:

  • Der Zweck von µServices besteht darin, die Wandelbarkeit von Software zu erhöhen. Sie gehen dabei über andere Container wie Komponenten hinaus.
  • Ein µService liegt formal vor, wenn ein opaquer Container durch einen separaten plattformneutralen Kontrakt beschrieben ist.

Der Rest ergibt sich… Aber immer Vorsicht: µServices haben ihren Preis.


  1. Seitdem hat sich allerdings meine Vorstellung von Softwarezellen weiterentwickelt. Ihre Struktur ist detaillierter geworden, ihr Einsatzgebiet für den Entwurf von Software spezifischer. Und insgesamt sind sie “untechnologischer” als das, worüber in der µService-Diskussion gesprochen wird.

  2. Anders herum ist es jedoch scheinbar kein Problem für Kunden: Software, die funktional, performant, skalierbar usw. auf Kosten der Wandelbarkeit ist, findet sich überall. Ich möchte fast sagen, sie ist die Norm. Warum das so ist, darüber lässt sich trefflich diskutieren. Aber es ist so und dem muss etwas entgegengesetzt werden - allerdings ohne denselben Fehler wieder zu begehen. Nur mit Balance lässt sich zukunftsfähige Software langfristig ökonomisch bauen. Die Anforderungsaspekte müssen sich in der Implementation die Waage halten. Auch deshalb: Vorsicht beim Aufspringen auf den µService-Trendzug.

  3. Dass viele Teams Erfahrung mit Komponentenarchitekturen haben, bezweifle ich. Das Denken in solchen Containern ist nicht weit verbreitet. Selbst Bibliotheken werden vergleichsweise selten eingesetzt, um Softwaresysteme mehr aus Black Boxes zusammenzusetzen. Insofern bin ich skeptisch, dass landauf-landab schon bald µServices mit Erfolg eingesetzt werden. Da hilft auch alle Open Source Infrastruktur nicht. Es fehlt einfach an Architektursystematik.

  4. Von außen betrachtet gibt es keinen Unterschied zwischen einem periodischen Neustart und einem Crash oder einer Nichtverfügbarkeit aufgrund von Leitungsproblemen. Die Gesamtarchitektur muss ohnehin darauf ausgelegt sein. Warum also diesen Umstand nicht nutzen, um ein Problem pragmatisch zu lösen? Softwareentwicklung ist eben eine ökonomische Tätigkeit. Wer Perfektion sucht, ist auf dem sicheren Pfad zu Frust.