Probleme mit den Checked Exceptions

Es ist einige Zeit vergangen, seit ich hier etwas für jEBC veröffentlicht habe. Das heißt aber nicht, dass ich mich mit dem Thema EBC nicht beschäftigt habe – nur war es beruflich und daher in C#.

Das Problem

Allerdings ist mir da ein Problem aufgestoßen, das ich im letzten Artikel herbei argumentiert habe: dass ein Pin keine Exceptions weiterleiten soll. Da C# ja keine Checked Exceptions hat, hat sich diese Dikussion im Bereich der C#-EBC-Gemeinde wohl noch nicht ergeben…

Bei den bisher von mir gezeigten gradlinigen Flüssen fällt das Problem nicht auf. Nimmt man aber eine Verzweigung hinzu, merkt man es schnell. Im nachfolgenden Diagramm ist der Kontrollfluss als grüner Pfeil eingezeichnet. Dieser Fluss ist der fehlerfreie Fall. Die Funktionseinheiten (FE) A, B, C und D werden in der gewünschten Reihenfolge abgearbeitet.

Normaler Kontrollfluss ohne Exception

Wird jetzt in der FE B eine Exception ausgelöst und aufgefangen, so wird sie an den Exception-Pin signalisiert, dort irgendwie behandelt… und dann? Dann kehrt der Kontrollfluss zurück zu B und läuft normal weiter. Es wird dann also noch D ausgeführt, was unter Umständen aber gar nicht gewünscht ist, weil es eine aufwändige FE ist, oder im schlimmsten Fall sogar ungewollte Nebeneffekte auslöst, die gar nicht auftreten sollen, wenn C nicht ausgeführt wird.

B löst eine Exception aus und behandelt diese auch gleich

In dieser Konstellation wäre es vielleicht sinnvoller, die Exception in FE A zu behandeln, damit D im Fehlerfall nicht ausgeführt wird.

A übernimmt die Behandlung der Exception

Allerdings ist A nicht wirklich der richtige Platz dafür, da A ja nichts davon weiß (und zu wissen hat), welche Exceptions denn in nachfolgenden FEs ausgelöst werden – das ist ja schließlich der Sinn der EBC, dass es egal ist, an wen sie gehängt werden.

Die Lösung

Somit ist es eigentlich am sinnvollsten, eigene Exception-Handling-FEs in den Fluss zu hängen, vergleichbar den Logging-FEs, die ich hier beschrieben habe. Das sähe dann so aus:

EIn expliziter Prozess für Exceptions hängt im Fluss

Damit gewinnt man wieder die volle Kontrolle darüber, wo und wie Exceptions behandelt werden. Nur tun muss man es natürlich immer noch…

Der Preis dafür

Natürlich benötigen wir dafür Anpassungen an unseren Pins, denn bisher können Pins keine Checked Exception weiterleiten. Da ein Pin ein genereller Ansatz ist, bleibt nur die Variante, bei den Pins jeweils throws Exception hinzuzufügen – da ist sie wieder, die throws-Kaskade, die ich eigentlich vermeiden wollte. Der Sinn der Checked Exceptions (so es denn einen gibt – die Meinungen gehen da ja wohl auseinander) geht hier vollends verloren. (source)

public interface InPin<T> {
    void receive(T message) throws Exception;
}
 
public interface OutPin<T> {
    void wire(InPin<T> in);
    void send (T message) throws Exception;
}

Dummerweise erlaubt es Java (ebenso wie C#, siehe Edit unten) nicht, eine Exception als generischen Typ anzugeben und in einer catch-Klausel zu verwenden. Etwas wie

public class ExceptionCatcher<T, E extends Exception> {
	public void receive(T message) throws Exception {
		try {
			Result().send(message);
		} catch (E e) {
			throw e;
		}
	}
}

ist also nicht möglich. Will man gezielt eine FE bauen, die eine bestimmte Exception fängt, muss man also für jeden Exceptiontyp eine eigene FE bauen.

public class SQLExceptionCatcher<T> extends Process<T, T> {
 
	private OutPin<Exception> exceptionpin = new OutPinImpl<Exception>();
 
	public OutPin<Exception> Exception() {
		return exceptionpin;
	}
 
	@Override
	protected void process(T message) throws Exception {
		try {
			Result().send(message);
		} catch (SQLException se) {
			Exception().send(se);
		}
	}
}

Baut man AdresseEinlesen aus dem letzten Post entsprechend um, so erhält man dann einen Prozess, der sich nicht um die Exception kümmert, sondern sie nach außen lässt, wo sie dann bearbeitet werden kann. Im Quellcode ist das “zentrale Exceptionhandling” ein lapidares Log-Statement (zu finden in de.jebc.adressbook.Main.java) (source)

EDIT: C# kann generische Exceptions fangen, siehe hier. Ich bin vermutlich in dieselbe Falle getappt wie der ursprüngliche Frager.

Notationsvorschlag für EBC

Ralf Westphal hat in seinem englischen Blog eine Notation vorgestellt, um Flow Design einheitlich darzustellen. Flow Design ist der Oberbegriff für das Design Paradigma, für das die Event Based Components gewissermaßen der Startschuss waren und das letztlich auch den Artikeln in diesem Blog zugrunde liegt.
Um das Paradigma voran zu bringen und Verwirrungen durch verschiedene Darstellungen zu vermeiden, werde ich für meine zukünftigen Artikel die von Ralf vorgeschlagene Notation verwenden.

Exceptions und eine DSL

Exceptions

Nach Logging ist die Ausnahmebehandlung ein weiteres ungeliebtes Kind der Programmierung. Wie oft versickert eine  Ausnahme in einem println() Statement und ward nie wieder gesehen.

Es ist vielleicht einigen aufgefallen, dass in der Signatur der Pin-Methoden keine Exception definiert ist. Somit können checked exceptions nicht über Pins weitergereicht werden. Das ist auch von mir beabsichtigt, damit eine Funktionseinheit (FE) sich ausdrücklich mit den bei ihr auftretenden Exceptions befasst und diese, ebenso ausdrücklich, weiterreicht, wenn eine eigene Behandlung nicht möglich ist.

Die FE “Datenbankabfrage ausführen” führt mittels einer Connection die in der vorhergehenden FE erstellte Abfrage aus. Beim Datenbankzugriff kann so einiges schief gehen, weswegen die SqlException eine checked exception ist, um die sich jemand kümmern muss. Der “klassische” Ansatz wäre, diese einfach an den Aufrufer weiter zu reichen. (source)

public ResultSet executeQuery(Abfrage message) throws SQLException {
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery(message.getQuery());
    return rs;
}

Mit EBCs ist dieser Ansatz nicht möglich. Die Lösung besteht darin, Fehlerzustände der FE über einen expliziten Fehlerpin nach außen zu geben. Das hat den Vorteil, dass es nicht die berüchtigten throws-Kaskaden in der Aufrufhierarchie gibt. Vielmehr wird die Verantwortung an einer definierten Stelle an jemanden übergeben, der sich (hoffentlich) darum kümmert. Im Diagramm sieht das dann so aus:

An so ein zentrales Exception-Handling können dann alle Exception-Pins aller Platinen angehängt werden. Damit hat man auch hier eine saubere Trennung der Verantwortlichkeiten. In der FE ist ein einfacher try-catch-Block. Im normalen Programmfluss wird der Ausgangspin aufgerufen, im Ausnahmefall der Exceptionpin. (source)

private InPin<Abfrage> inpin = new InPin<Abfrage>() {
 
  @Override
  public void receive(Abfrage message) {
    try {
      ResultSet rs = fuehreAbfrageAus(message);
      Result().send(rs);
    } catch (SQLException e) {
      Exception().send(e);
    }
  }
 
  private ResultSet fuehreAbfrageAus(Abfrage message) throws SQLException {
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery(message.getQuery());
    return rs;
  }
};

Vereinfachungen

Die beiden Bauteile, die wir bisher entwickelt haben (AbfrageErstellen und DatenbankabfrageAusfuehren), folgen einem bekannten Schema: EVA – Eingabe, Verarbeitung, Ausgabe. Ein- und Ausgabe sind die Daten an den Pins und das Bauteil selber übernimmt die Verarbeitung. Es bietet sich an, dieses in vielen Bauteilen zu findende Schema in eine Basisklasse auszulagern. Ich nenne diese Basisklasse Process. Sie hat, entsprechend der zwei Pins, zwei generische Typen, um die Datentypen der Pins abzubilden. (source)

public abstract class Process<TInput, TOutput> {
 
 private OutPin<TOutput> outpin = new OutPinImpl<TOutput>();
 private InPin<TInput> inpin = new InPin<TInput>() {
 
   @Override
   public void receive(TInput message) {
     process(message);
   }
 };
 
 protected abstract void process(TInput message);
 
 public InPin<TInput> Start() {
   return inpin;
 }
 
 public OutPin<TOutput> Result() {
   return outpin;
 }
 
}

Damit vereinfachen sich auch unsere Bauteile. Beispielhaft hier AbfrageErstellen

public class AbfrageErstellen extends Process<Schluessel, Abfrage> {
 
 @Override
 protected void process(Schluessel message) {
   Abfrage result = create(message);
   Result().send(result);
 }
 
 private Abfrage create(Schluessel key) {
   Abfrage result = new Abfrage(String.format(
     "SELECT * FROM Adressen WHERE id = %1$s", key.getId()));
   return result;
 }
}

DSL

Für die Bauteile haben wir jetzt ein praktisches Muster gefunden. Platinen verdrahten finde ich aber immer noch recht unübersichtlich. Wie sieht unsere Platine AdresseEinlesen denn jetzt aus:

public AdresseEinlesen(Connection conn) {
 abfrageAusfuehren = new DatenbankabfrageAusfuehren(conn);
 logSchluessel.Out().wire(abfrageErstellen.Start());
 abfrageErstellen.Result().wire(logAbfrage.In());
 logAbfrage.Out().wire(abfrageAusfuehren.Start());
 abfrageAusfuehren.Result().wire(adresseErstellen.Start());
 }

Da ist nicht wirklich klar zu erkennen, was die wesentlichen Bauteile sind und was die Aspekte – hier Logging. Ich finde das unschön. Der Ablauf “Abfrage erstellen – Abfrage ausführen – Adresse erstellen” wird auch nicht deutlich. Solange es keinen Designer mit Codegenerierung gibt, ist trotz aller Diagramme immer noch der Code der “single point of truth“. Daher sollte er die wesentlichen Informationen deutlich präsentieren. Wie wäre es denn also damit:

private void wire() {
 wire(abfrageErstellen.Start(), watcher(logSchluessel));
 wire(abfrageErstellen.Result(), abfrageAusfuehren.Start(), watcher(logAbfrage));
 wire(abfrageAusfuehren.Result(), adresseErstellen.Start());
}

Damit wird die Sequenz deutlich, und das Logging wird als Aspekt betont. Da wäre als “direkter Draht” der Aufruf von wire, der einen Ausgangspin mit einem Eingangspin verbindet. Soll der Draht zusätzlich beobachtet werden, kann mit einem dritten Parameter ein Watcher-Objekt in den Draht gehängt werden. Der Aufruf von watcher ist nur syntaktischer Zucker, um den Watcher abzugrenzen. wire mit einem InPin und einem Watcher hängt den Watcher vor den InPin, der vom Board nach außen gegeben wird (Achtung: das ist kein Automatismus – das Board muss den InPin des Watchers dann natürlich hinausreichen – siehe Sourcen).
Die DSL-Elemente kommen in eine abstrakte Basisklasse Board, die die Basis für alle Platinen ist.

public class Board {
 
  protected <T> void wire(OutPin<T> out, InPin<T> in, Watcher<T> watcher) {
    wire(out, watcher.In());
    wire(watcher.Out(), in);
  }
 
  protected <T> void wire(InPin<T> in, Watcher<T> watcher) {
    wire(watcher.Out(), in);
  }
 
  protected <T> void wire(OutPin<T> out, InPin<T> in) {
    out.wire(in);
  }
 
  protected <T> Watcher<T> watcher(Watcher<T> watcher) {
    return watcher;
  }
}

Ich bin sicher, das Vokabular dieser EBC-DSL wird sich noch erweitern.

Quelltexte

Der aktuelle Commit ist c088db. Die kleinen (source) Links innerhalb des Posts verweisen auf den jeweils im dazugehörigen Beispiel gezeigten Stand. Durch das Refactoring und schrittweise Entwickeln findet man nicht immer alle Codebeispiele im letzten zu diesen Post gehörenden Commit.

(S)Pinnen

In meinem letzten Post habe ich die Idee der EBCs vorgestellt und eine grafische Notation (ich benutze dafür Dia) sowie eine Schnittstellendefinition angeboten, die ich für die weiteren Posts benutzen werde. Der Code, der dabei entstanden ist, ließ sich zwar kompilieren, aber nicht ausführen. Heute soll ein bischen “Butter bei die Fische” kommen, wie wir hier im Norden sagen – sprich: es gibt Code, der auch ausführbar ist. Als Ziel des Codings wird sich – wie man aus meinen grafischen Beispielen vielleicht schon erahnen konnte – eine kleine Adressbuch-Anwendung ergeben. Ich werde in späteren Posts noch etwas mehr auf das Design eingehen.

Ralf hat in seinem Kommentar zum ersten Post darauf hingewiesen, dass “Component” eigentlich kein passender Bezeichner mehr ist. In der Tat ist der Begriff Komponente in der Programmierung anders belegt. Sein Vorschlag, als Oberbegriff für Bauteile und Platinen Funktionseinheiten (FEs) zu verwenden, finde ich zwar ein wenig sperrig, auch wenn es die Sache vielleicht trifft. In Ermangelung eines besseren Vorschlags werde ich ihn aber erstmal übernehmen.

Pins

Allen FEs ist gemeinsam, dass sie Pins haben. Ohne diese ist kein Nachrichtenversand möglich. Art und Anzahl der Pins kann natürlich stark variieren. Es mag FEs geben, die nur Ausgabepins haben, solche, die nur Eingabepins haben und natürlich welche mit beidem. Das ist stark von der Aufgabe der FE abhängig. Ich möchte hier nochmal darauf hinweisen, dass der Pin vorrangig ein Design-Element ist, also nicht deine bestimmte Umsetzung impliziert. Auch die klassischen Java-Eventhandler erfüllen dieses Designmuster vorbildlich:

private javax.swing.JButton btnOK = new JButton();
public ListenerAsPinExample() {
  btnOK.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent arg0) {
      // mach was
    }
  });
}

Hier stellt der Button einen Ausgangspin bereit, an den ein ActionListener als Eingangspin gehängt wird. Als Datum wird das ActionEvent übergeben. Das Muster ist sauber eingehalten: keine der FEs ist fest an die andere gebunden und es wird genau ein Datum übergeben. (source)

Will  man Pins implementieren, so fällt auf, dass der InPin immer direkt funktional implementiert wird – er hat keinerlei Verwaltungsoverhead. Die direkteste Implementierung ist daher eine anonyme Klasse. (source)

OutPin<String> outPin;
public Funktionseinheit() {
  outPin.wire(new InPin<String>() {
    public void receive(String message) {
      // mach was
    }
  });
}

Ein OutPin hat etwas Verwaltungsoverhead, da er für die Verbindung zum InPin verantwortlich ist. Dies ist ein simpler 1:1 Pin. Einen anderen Typen werden wir später kennen lernen. (source)

public void OutPinImpl<T> implements OutPin<T>
{
  private InPin<T> inPin;
 
  public void wire(InPin<T> pin) {
    inPin = pin;
  }
 
  public void send(T message) {
    inPin.receive(message);
  }
}

Logging klassisch

"Abfrage erstellen" mit Kontext

Logging ist ein wichtiges, aber (zumindest bei mir) ungeliebtes Thema. Logging müllt den Programmcode zu mit Dingen, die nichts mit der eigentlichen Aufgabe der Klasse zu tun haben. Und erzeugt neue Abhängigkeiten. Die Implementierung von “Abfrage erstellen” mit Logging könnte in etwa so aussehen (ich benutze die Logging-Fassade slf4j, um unabhängig vom konkreten Logging-Framework zu bleiben). (source)

Logger logger = LoggerFactory.getLogger(AbfrageErstellen.class);
 
 private Abfrage create(Schluessel key) {
   logger.info("erstelle Abfrage für {}", key.getId());
   Abfrage result = new Abfrage(String.format("SELECT * FROM Adressen WHERE id = %1$s", key.getId()));
   logger.info("Abfrage ist: {}", result.getQuery());
   return result;
 }

Der eigentliche Code ist kaum noch zu erkennen. Loggt man nun auch noch “Selektierte Adresse bestimmen” und “Abfrage ausführen” jeweils beim Ein- und Ausgang, so hat man etliche Infos auch noch doppelt geloggt.

Logging mit AOP

Logging ist ein klassischer Cross Cutting Concern, für den AOP erfunden wurde. Logging ist daher auch ein gerne genutztes Beispiel für AOP. Ich persönlich konnte mich mit AOP nie so richtig anfreunden, deswegen kenne ich es nicht allzu sehr, aber ein typisches Beispiel zum Thema Logging ist dieses:

public aspect AutoLog{
 
  pointcut publicMethods() : execution(public * de.jebc..*(..));
 
  pointcut logObjectCalls() :
    execution(* Logger.*(..));
 
  pointcut loggableCalls() : publicMethods() && ! logObjectCalls();
 
  before() : loggableCalls(){
    Logger.entry(thisJoinPoint.getSignature().toString());
  }
 
  after() : loggableCalls(){
    Logger.exit(thisJoinPoint.getSignature().toString());
  }
}

Damit wird für jede öffentliche Methode (außer beim Logger selber) der Beginn und das Ende der Methode protokolliert mitsamt der Signatur. Leider werden nicht die Parameter ausgegeben, nur ihre Typen. Und selbst wenn, könnte man, weil es ein genereller Ansatz ist, nur toString() bei den Parametern aufrufen, womit man dann zwar Code aus den Geschäftsklassen herausgeholt hat, aber potentiell Analysecode in die toString()-Methode der Datenklassen verschiebt.

Logging mit EBC

Wie sieht’s denn dann mit EBCs aus? Nun, hier ist die Logging-Funktionalität in einfachen Bauteilen versteckt, die einfach in die Drähte zwischen den Pins eingehängt werden:

Log-Bauteile werden in den Draht gehängt und "belauschen" das Datum

Damit kann Logging ganz explizit an gewünschten Stellen eingesetzt werden, ohne den Code der eigentlichen Geschäftsklasse zu beeinträchtigen. Auch das übetragene Datum wird nicht beeinträchtigt. Trotzdem hat die Loggingklasse Zugriff auf die komplette Schnittstelle des Datums und kann so durchaus komplexe Informationen loggen. Eine Loggingklasse sähe dann z.B. so aus:

public class LogSchluessel {
  private final Logger log;
  private OutPin<Schluessel> out = new OutPinImpl<Schluessel>();
  private InPin<Schluessel> in = new InPin<Schluessel>() {
 
    @Override
    public void receive(Schluessel message) {
      log.debug("erstelle Abfrage für {}", message.getId());
      out.send(message);
    }
  };
 
  public LogSchluessel(Logger log) {
    this.log = log;
  }
 
  public InPin In() {
      return in;
  }
 
  public OutPin Out() {
      return out;
  }
 
}

Die Klasse zum Loggen der erstellten Abfrage sähe entsprechend aus. In AdresseErstellen werden jetzt die Logging-Klassen wie andere Bauteile verdrahtet. (source)

public AdresseEinlesen() {
    logSchluessel.Out().wire(abfrageErstellen.Start());
    abfrageErstellen.Result().wire(logAbfrage.In());
    logAbfrage.Out().wire(abfrageAusfuehren.Start());
    abfrageAusfuehren.Result().wire(adresseErstellen.Start());
}
 
public InPin<Schluessel> Start() {
    return logSchluessel.In();
}
 
public OutPin<Adresse> Result() {
    return adresseErstellen.Result();
}

Hier bietet sich etwas Refaktorierung an, um die ewige Wiederholung der Pins zu vermeiden. Im Prinzip beobachten die Log-Klassen den Datenstrom, ohne ihn zu verändern. Dieses Muster kann sicher auch anders einen Zweck erfüllen. Deswegen möchte ich es ganz allgemein Watcher nennen. Alle Logging-unabhängigen Teile können dort rein.

public abstract class Watcher<T> {
 
 private OutPin<T> out = new OutPinImpl<T>();
 private InPin<T> in = new InPin<T>() {
 
   @Override
   public void receive(T message) {
     inspect(message);
     out.send(message);
   }
 };
 
 public InPin<T> In() {
   return in;
 }
 
 public OutPin<T> Out() {
   return out;
 }
 
 protected abstract void inspect(T message);
 
}

Die Logging-Klassen bleiben dann unaufwändige Restklassen. Verschoben in ein eigenes Package, ist das Logging fast vollständig aus dem Weg und verschmutzt die eigentliche Geschäftslogik nicht mehr. Hier kann man jetzt auch sehr gut sehen, warum die Pin-Schnittstelle auf einen Parameter beschränkt bleiben sollte: Generische Klassen wie der Watcher wären sonst nicht möglich. (source)

public class LogSchluessel extends Watcher<Schluessel> {
 
 private final Logger log;
 
 public LogSchluessel(Logger log) {
   this.log = log;
 }
 
 protected void inspect(Schluessel message) {
   log.info("erstelle Abfrage für {}", message.getId());
 }
}

Im Diskussionsforum zu den EBC kam die Anmerkung, dass es ja nichts nützen würde, nur die Parameter (sprich: Daten) zu loggen. Schließlich sei es ja nötig, auch innerhalb der FE zu loggen. Ich denke aber, dass es nicht nötig ist. FEs sollten kompakte, durchgetestete Einheiten sein, die mit allen gültigen Daten umgehen können. Insofern kann das Problem nur darin liegen, dass das übergebene Datum nicht valide ist und das kann ja grade am Pin geloggt werden. Ausnahmen mögen die Regel bestätigen, aber dann bleibt immer noch – wie im Logging-Bauteil selber – die Constructor Injection. Ich denke aber, dass das die Ausnahme bleiben wird.

Ah, ich höre es schon: wie kann man denn z.B. Exceptions loggen? Oder sie überhaupt sinnvoll behandeln? An einem Pin geht das ja eigentlich nicht? Wir werden sehen…

Quelltexte

Der aktuelle Commit ist 68034f. Die kleinen (source) Links innerhalb des Posts verweisen auf den jeweils im dazugehörigen Beispiel gezeigten Stand. Durch das Refactoring und schrittweise Entwickeln findet man nicht immer alle Codebeispiele im letzten zu diesen Post gehörenden Commit.

Event-Based Component in Java

In den letzten Monaten hat sich eine interessante Diskussion entwickelt über ein design pattern, das vom “Erfinder” Ralf Westphal “Event-Based Components” (EBC) getauft wurde. In diesem Blogpost stellt er sie erstmals vor. Da Ralf aus dem .NET-Umfeld kommt, findet die gesamte Diskussion zurzeit in der .NET-Community statt und konzentriert sich auf eine Umsetzung in C#. Da ich auch in Java programmiere, habe ich eine Implementierung des Konzeptes umgesetzt in Java. Das möchte ich in diesem Blog nach und nach vorstellen. Da die Diskussion noch recht neu ist, hat sich noch kein fester Wortschatz für EBC etabliert. Es kann daher sein, dass einzelne Artikel über EBC eine andere Begrifflichkeit benutzen als ich hier. Das ändert aber nichts am Prinzip.

Grundgedanken

Der Grundgedanke der EBCs ist die vollständige Loslösung von anderen Komponenten. Diese Entkopplung ist auch in aktuellen OOP Diskussionen ein wichtiger Punkt, wie nicht zuletzt das Inversion of Control (IoC) pattern und die Vielzahl der Dependency Injection (DI) Frameworks, die zurzeit um die Gunst der Programmierer buhlen, zeigen. Dennoch sind bei IoC Abhängigkeiten vorhanden, und DI ist nur ein Mittel, die Befriedigung der Abhängigkeiten zu vereinfachen. IoC zielt auf die Entkopplung von der Implementierung, nicht von der Schnittstelle. Damit werden zweifellos einige wichtige Ziele erreicht: Implementierungen lassen sich problemlos austauschen, ggf. sogar zur Laufzeit, die Objekte können leicht getestet werden, weil hinter den abhängigen Schnittstellen test doubles versteckt werden können, die leicht zu erstellen sind. Diese Komponenten auf Basis von IoC nennt Ralf, und ich möchte das übernehmen, IBC – Interface Based Components.

Wenn man Component mal mit Baustein übersetzt und dann an Bausteine in der physischen Welt denkt, dann landet man z.B. bei elektronischen Bauteilen wie Widerständern, bei Ziegelsteinen oder vielleicht auch bei Legosteinen. Bei diesen gibt es einen eklatanten Unterschied zu IBCs: Die physischen Bausteine sind unabhängig von anderen Bausteinen. Natürlich gibt es eine Vereinbarung, wie verschiedene Bausteine zusammengesetzt werden, aber keiner der Bausteine ist von bestimmten anderen abhängig. Lediglich die Art, wie sie zusammengesetzt werden, bestimmt das Ergebnis. Um beim Legostein zu bleiben: es ist dem Stein völlig egal, ob er auf die Bodenplatte gesetzt wird oder den Abschlussstein einer Lego-Kathedrale bildet. Solange einige Noppen der richtigen Größe vorhanden sind, passt er.

Ganz neu sind die Gedanken übrigens nicht. Das ganze Konzept weist auffällige Ähnlichkeiten zum Flow Based Programming auf, das J. Paul Morrison bereits 1994 beschrieben hat und dessen Buch zu dem Thema gerade in einer neuen Auflage erschienen ist.

Nachrichten

Die Vereinbarung, wie EBCs zusammengesetzt werden, sind Ereignisse oder Nachrichten. Eine EBC kann Nachrichten empfangen und Nachrichten senden. Woher diese kommen und wohin sie gehen, ist der Komponente egal. Lediglich die Nutzlast, also das in der Nachricht enthaltenen Datum, muss passen, damit die Komponente ihre Aufgabe erfüllen kann. Das macht eine EBC ebenso simpel wie elegant. Es ist nichts weiter als:

Eine einfache Komponente

Eine Komponente zum Einlesen einer Adresse. Die Symbolik ist ein Vorschlag von mir. Der offene Halbkreis ist ein Eingangspin, der volle Kreis ein Ausgangspin. Das am Pin anliegende Datum steht in spitzen Klammern, entsprechend der Generic-Syntax in Java oder C#.

Ein- und Ausgabedatum können natürlich unterschiedlich sein, sowohl vom Inhalt her als auch vom Typ. Ein Grundprinzip ist allerdings, dass auf jedem Ein- oder Ausgang nur ein Datenobjekt übergeben wird. Mehr dazu später. Mehrere Komponenten werden aneinander gehängt, wobei der Ausgang einer Komponente an den Eingang einer anderen Komponente gehängt wird. In der Diskussion hat sich der Begriff “Pin” ausgeprägt, womit das Bild der Elektrotechnik wieder aufgenommen wird.

Obwohl EBC als design pattern keine direkte Implmentierung impliziert, so finde ich es doch hilfreich, auch für die Umsetzung ein Muster zur Hand zu haben. Das soll nicht heißen, dass die hier vorgestellten Muster der Weisheit letzter Schluss sind. Es mögen für spezielle EBC spezielle Umsetzungen sinnvoll sein, oder ganz generell andere Varianten geben.

In C# können Pins sehr direkt umgesetzt werden. Ein Ausgangspin ist ein event:

public event Action<Adresse> Result;

Ein Eingangspin ist eine einfache Methode mit einem Parameter und ohne Rückgabewert. Stattdessen wird das Ergebnis über einen Ausgangspin, also ein event, weitergereicht.

public void Start(Schluessel schluessel) {
  // erstelle Adresse
  Result(adresse);
}

Es gibt also Eingangspins und Ausgangspins und es wird ein Eingangspin mit einem Ausgangspin verdrahtet:

Verbindung von Komponenten

Die Verdrahtung geschieht in C# entsprechend einfach:

AdresseBestimmen.Result += AdresseEinlesen.Start;

Da Java weder Events noch Funktionsreferenzen (delegates) als elementare Sprachbestandteile hat, kommt man nicht umhin, für die Pins Typen zu definieren. Das hat in meinen Augen aber auch den Vorteil, dass Pins als solche sofort zu erkennen sind, während sie in C# als “normale” Elemente in ihrer speziellen Funktion nicht so leicht zu erkennen sind. Pins können klassisch als Interface definiert werden, wobei sie einen genrischen Typen erhalten, der das zwischen den Pins zu übertragende Datum darstellt. Der Ausgabepin enthält eine Methode zum Verdrahten mit einem Eingabepin. Und beide haben je eine Methode für die Kommunikation.

public interface InPin<T> {
    void receive(T message);
}
 
public interface OutPin<T> {
    void wire(InPin<T> in);
    void send (T message);
}

Verdrahtet wird dann mit

outPin.wire(inPin);

und gesendet mit

outPin.send(message);

Bauteile und Platinen

Komponenten können auf jeder gewünschten Abstraktionsebene entworfen werden. So ist “Adresse einlesen” sicherlich aus mehreren kleineren Komponenten zusammengesetzt, die einzelne Aufgaben wahrnehmen. “Adresse einlesen” könnte so aussehen:

Eine Komponente kann aus anderen zusammen gesetzt sein

Der Ein- und Ausgangspin bleiben natürlich unverändert. Im Detail sieht man aber, dass der Eingangspin zu einer Komponente führt, die die Datenbankabfrage erstellt. Diese Abfrage wird an eine Komponente weitergereicht, die Abfragen ausführt und daraus Resultsets erstellt. Eine dritte Komponente erhält das Resultset und baut daraus die fertige Adresse. Eine Komponente kann also entweder atomar oder zusammengesetzt sein. Von außen sieht man ihr das nicht an. Eine atomare Komponente möchte ich als Bauteil bezeichnen, eine zusammengesetzte als Platine. Eine Platine hat keine andere Funktion, als Bauteile oder andere Platinen miteinander zu verdrahten (und sie, sofern nicht von außen mitgegeben, zu erstellen). Ein Platine hat somit also keinerlei fachlichen Programmcode. Dementsprechend einfach ist sie aufgebaut. Wenn wir das linke Beispiel in etwas Code übersetzen wollen, käme in etwa das dabei heraus:

public class AdresseEinlesen {
  private AbfrageErstellen abfrageErstellen = new AbfrageErstellen();
  private DatenbankabfrageAusfuehren abfrageAusfuehren = new DatenbankabfrageAusfuehren();
  private AdressobjektErstellen adresseErstellen = new AdressobjektErstellen();
 
  public AdresseEinlesen() {
    abfrageErstellen.Result().wire(abfrageAusfuehren.Start());
    abfrageAusfuehren.Result().wire(adresseErstellen.Start());
  }
 
  public InPin<Schluessel> Start() {
    return abfrageErstellen.Start();
  }
  public OutPin<Adresse> Result() {
    return adresseErstellen.Result();
  }
}

Es kann das Diagramm also fast 1:1 in Programmcode übersetzt werden. Jede Verbindung innerhalb der Platine entspricht einem Aufrufe von wire(), jeder Pin nach außen entspricht einer Methode, die den Pin bereit stellt. Dabei muss die Platine keine eigenen Pin-Variablen bereit halten – sie kann einfach die “losen Enden” der Bauteile weiterreichen.

Diese Übersetzbarkeit von Diagramm in Code schreit eigentlich nach einem grafischen Designer, nicht?

Ausblick

Das waren einige Grundgedanken zu EBCs. Ich werde in den nächsten Blog-Posts die Umsetzung in den Vordergrund stellen, weil die Theorie von Ralf in senem o.g. Blog besser verständlich beschrieben wird, als ich es je könnte. Ich werde dabei einige weitere interessante Vorteile der EBCs vorstellen, z.B. die elegante Umsetzung von Aspekten der AOP, oder die einfache Testbarkeit im Vergleich zu IBCs.

Quelltexte

Die Quelltexte zum Blog – immer in einer zumindest kompilierbaren Form – finden sich bei Github, für diesen Post ist es der Commit 913ad01. Noch ohne wirklichen Inhalt…