Follow my new blog

Dienstag, 23. Februar 2010

Verbindungsstücke – Event-Based Components abhören

Auf zum vorläufigen Endspurt mit den Event-Based Components (EBC). Wie die grundsätzlich definiert werden, habe ich hier beschrieben. Dann ging es darum, wie sie grundsätzlich zusammengesteckt werden. Und schließlich galt es, die Kommunikation noch etwas zu vereinfachen. Um mit EBC zu arbeiten, ist jetzt alles auf dem Tisch.

Aber es kommt noch besser! Denn bisher haben Sie nur gesehen, wie EBC das leisten, was “traditionelle” Komponenten auch leisten. Die EBC tun das natürlich architekturell sauberer, finde ich, aber funktional haben sie keinen Vorsprung. Das möchte ich nun ändern. Dazu müssen wir uns nochmal ansehen, wie EBC “zusammengesteckt” werden.

Ein ganz einfaches Szenario soll da genügen. Komponente Quelle verschickt eine Nachricht Output und Komponente Senke empfängt sie.

image

Hier die Kontrakt-Interfaces und die Nachricht:

interface IQuelle

{

    event Action<Output> OnOutput;

}


interface
ISenke

{

    void ProcessOutput(Output msg);

}


class
Output

{…}

Die Bindung im Rahmen einer “Software-Platine” geschieht dann so:

// Build

IQuelle q = new Quelle();

ISenke s = new Senke();

// Bind

q.OnOutput += s.ProcessOutput;

Anschließend kann die Quelle angestoßen werden und kommuniziert mit der Senke:

((Quelle) q).Run("hello");

…

class Quelle : IQuelle

{

    public void Run(string text)

    {

        this.OnOutput(new Output {Text = "<"+text+">"});

    }


    public
event Action<Output> OnOutput;

}

Alles easy. Nichts neues, wenn Sie die bisherigen Blogartikel zu EBC verfolgt haben. Jetzt halten Sie sich aber fest…

Nachrichten abfangen

Haben Sie schon mal versucht, die Kommunikation zwischen “traditionellen” Komponenten zu verfolgen? Vielleicht ist eine komponentenorientierte Anwendung mal nicht so gelaufen, wie Sie wollten, und Sie haben gedacht, wenn Sie wüssten, mit welchen Argumenten die Komponenten sich gegenseitig aufrufen, dann könnten Sie leicht heraus finden, wo es hakt, ohne zum Debugger zu greifen.

Ich jedenfalls möchte so ein Tracing in Anwendungen einschalten können. Natürlich ohne dafür eine Änderung in meinem Quellcode vornehmen zu müssen. Das funktioniert auch mit einem DI Container wie Structure Map und automatisch generierten Proxies. Stefan Lieser hat das mal für die Anwendungen gemacht, die wir innerhalb der Clean Code Developer Seminare mit den Teilnehmern entwickeln. Dafür war allerdings schon einiges Spezialwissen nötig.

Mit EBC geht das hingegen ganz einfach. Wenn Sie auf einer Verbindung zwischen zwei EBC lauschen wollen, um z.B. den Nachrichtenfluss zu protokollieren, dann tun Sie das ganz einfach so:

// Bind

q.OnOutput += o => Console.WriteLine("tracing {0}", o);

q.OnOutput += s.ProcessOutput;

Registrieren Sie einen zweiten Event-Handler am Output-“Pin” einer EBC. Der empfängt alle Nachrichten wie die eigentliche Zielkomponente für die Nachrichten. Schließlich sind Events multi-cast Delegaten.

image

Wenn Sie den Lauscher vor der Zielkomponente registrieren, bekommt er zuerst die Nachrichten. Registrieren sie ihn hinterher, dann bekommt er die Nachrichten erst, wenn die Zielkomponente schon fertig ist. Das können Sie auch dynamisch zur Laufzeit tun. Stellen Sie das Abfangen für jeden Output-“Pin” separat ein und aus, wenn Sie mögen.

Nix Reflection, keine dynamischen Proxies, kein Hexenwerk… alles ganz einfach mit dem Abfangen von Nachrichten bei EBC (Interception). EBC schenkt Ihnen sozusagen Aspektorientierte Programmierung (AOP) für manche Szenarien.

Verbindungsstücke

Denken Sie das noch ein Stück weiter: Die Verbindung zwischen Quelle und Senke ist explizit für jede Nachricht. Output-“Pin” wird mit Input-“Pin” “verdrahtet”… Wer sagt da eigentlich, dass diese “Drähte” direkt zwischen den Komponenten verlaufen müssen?

Sie könnten ein Tracing in Form einer generischen Funktionseinheit als Zwischenstück in die Verbindung zwischen Quelle und Senke einsetzen:

image

Das ist bei der Bindung ja leicht möglich:

q.OnOutput += Tracer.Create<Output>(s.ProcessOutput);

Der Tracer ist dann nichts als eine Indirektion zwischen Output-“Pin” und Input-“Pin”:

class Tracer

{

    public static Action<T> Create<T>(Action<T> processor)

    {

        return t =>

            {

                Console.WriteLine("tracing: {0}", t);

                processor(t);

            };

    }

}

Wo hier Console.WriteLine() steht, können Sie natürlich beliebig viel Aufwand für ein generisches Tracing treiben – oder Sie delegieren die Aufgabe an log4net oder SmartInspect.

Oder überlegen Sie ein anderes Szenario für ein Zwischenstück. Wie wäre es, wenn Sie Nachrichten zur Laufzeit aufhalten wollen, bis eine Senke sie (wieder) aufnehmen kann? Sie könnten ein Ventil zwischen Quelle und Senke einsetzen.

image

Das ist bei der “Verdrahtung” ganz einfach:

var valve = new Valve<Output>();

q.OnOutput += valve.Register(s.ProcessOutput);

Weder Quelle noch Senke merken etwas davon, dass der Fluss zwischen ihnen über das Ventil gesteuert werden kann:

((Quelle)q).Run("1");

valve.Close();

((Quelle)q).Run("2");

((Quelle)q).Run("3");

valve.Open();

Die Nachrichten “2” und “3” werden erst bei Aufruf von Open() an die Senke weitergeleitet. Und die Implementation eines solchen Ventils ist trivial:

class Valve<T>

{

    private bool isOpen = true;

    private Queue<T> buffer = new Queue<T>();

   
    public void ProcessMessages(T msg)

    {

        if (this.isOpen)

            this.OnMessage(msg);

        else

            this.buffer.Enqueue(msg);

    }


    public
event Action<T> OnMessage;


    public
Action<T> Register(Action<T> processor)

    {

        this.OnMessage += processor;

        return this.ProcessMessages;

    }


    public
void Open()

    {

        foreach (T msg in this.buffer)

            this.OnMessage(msg);

        this.isOpen = true;

        this.buffer.Clear();

    }


    public
void Close()

    {

        this.isOpen = false;

    }

}

Jetzt denken Sie mal weiter… Was ließe sich mit so einem “Ventil” oder einem vergleichbaren Steuerelement zwischen Komponenten noch alles machen? Sie könnten Ziel-Komponenten dynamisch laden und austauschen. Sie könnten Ziel-Komponenten ihren Zufluss selbst steuern lassen. Sie könnten Load-Balancing betreiben. Sie könnten Nachrichten per TCP verschicken zu einer entfernten Komponente… Das alles und noch mehr würde Nachrichten 1:1 weiterleiten. Früher oder später ;-)

Jetzt denken Sie noch weiter… Was könnten Sie mit den Nachrichten in “Zwischenstücken” alles machen? Sie könnten Sie transformieren, filtern, zerlegen, aggregieren… Und wieder alles, ohne dass die Komponenten selbst davon etwas merken würden.

Wer schon mal davon geträumt hat, wiederverwendbare Komponenten zu entwickeln, der hat jetzt endlich etwas in der Hand. Nicht Geschäftslogik-Komponenten sollten auf Wiederverwendbarkeit getrimmt werden, sondern Infrastrukturkomponenten wie ein solches Ventil. Dafür gibt es aber erst einen Markt, wenn Sie Ereignisorientierung und Nachrichtenorientierung denken.

Solch flexibler Umgang mit nachrichtenindividuellen Komponentenverbindungen ist für mich dann auch der Grund, Nachrichten einen eigenen Typ zu geben. Dann sehen nämlich alle Verbindungen zwischen Komponenten gleich aus. Sie sind vom Typ Action<T>. Und dann kann man generische Infrastruktur bauen, die mit diesen T-Nachrichten etwas tut.

Wer da an Enterprise Integration Patterns denkt, der liegt nicht falsch. Mir gehts aber erstmal nur um eine Flexibilisierung der synchronen Kommunikation zwischen Komponenten.

Jetzt sind Sie dran. Wohin trägt Sie Ihre Phantasie nun, da sie Event-Based Components kennengelernt haben?

Mustergültig – Event-Based Components aufbohren

Wie Sie Event-Based Components (EBC) grundsätzlich “zusammenstecken” habe ich hier gezeigt. Das ist nicht schwer, oder? Nur etwas gewöhnungsbedürftig. Dafür winken aber einige Vorteile gegenüber “traditionellen” Komponenten, finde ich. Meine “Bauchschmerzen”, die ich mit denen bei aller Liebe immer noch latent hatte, sind jetzt weg.

Ob ich Sie damit jedoch automatisch auch für EBC begeistert habe, weiß ich nicht. Das streng nachrichtenorientierte Programmiermodell und die “Verklausulierung” von Serviceaufrufen als Events sind recht weit weg vom Üblichen. Ich will deshalb hier noch ein paar Kommunikationsmuster beschreiben, die Ihnen das Konzept schmackhafter machen.

One-way Nachrichten

Das Grundmuster der EBC-Kommunikation ist die one-way Nachricht. Als solche bezeichne ich eine Nachricht, die vom Empfänger keine Antwort an den Absender erwartet. Bisher habe ich nur Beispiele für one-way Nachrichten gegeben. Selbst die Nachricht ParseResult, die vom Parser als Antwort zurück fließt zum CompilerCoordinator, ist eine solche one-way Nachricht:

interface ICompilerCoordinator

{

    …

    event Action<ParseRequest> OnParse;

    void ProcessParseResult(ParseResult result);

}


interface
IParser

{

    void ProcessParseRequest(ParseRequest request);

    event Action<ParseResult> OnParseResult;

}

Formal besteht zwischen ParseRequest und ParseResult kein Unterschied. Die Semantik, dass ein ParseResult als Ergebnis eines ParseRequest versandt wird, ist nur in unserem Kopf. Deshalb sind beide Nachrichten formal gleich.

Wie gesagt: Die Kommunikation zwischen Interaktionspartnern ist bei der Ereignisorientierung grundsätzlich symmetrisch.

One-way Nachrichten sind im Sinne der Command Query Separation das Mittel, um Kommandos zu versenden. Die Implementation ist denkbar einfach: Der Sender definiert einen Event für eine one-way Nachricht als Output; der Empfänger definiert einen passenden Event-Handler. Auf einer “Software-Platine” werden beide mit einander verbunden.

One-way Sender:

event Action<T> OnT;

One-way Empfänger:

void ProcessT(T msg);

Request/Response V1

Auch wenn es in der Ereignisorientierung formal nur one-way Nachrichten gibt, hilft es ja nichts: viele Interaktionen erwarten eine Antwort. Request/Response ist ein typisches Muster in der Kommunikation zwischen Funktionseinheiten. Dafür sind Funktionen gemacht. Auch die obige ParseRequest-Nachricht ist ein solcher Aufruf, der die Produktion eines Resultats anstoßen soll. Ansehen tun sie ihm das aber nicht. Das ist misslich, weil der ereignisorientierte Code dadurch schlechter lesbar ist als üblicher methodenbasierter.

Das können Sie jedoch leicht ändern, indem Sie den Request “aufbohren”. Sie verschicken mit ihm nicht nur Argumente für die Bearbeitung – sondern auch noch den Empfänger für die Antwort:

class ParseRequest

{

    public string Source;

    public Action<ParseResult> ProcessParseResult;

}

Jetzt wird die Nachricht ihrem Namen gerecht. Sie ist ein echter Request, eine Anforderung von Daten. Source steht für die Input-Daten beim Empfänger und die Methode ProcessParseResult für den Empfänger des Outputs.

Die Kontrakte für CompilerCoordinator und Parser werden dadurch einfacher:

interface ICompilerCoordinator

{

    void ProcessCompilationRequest(CompilationRequest request);

    event Action<ParseRequest> OnParse;

}


interface
IParser

{

    void ProcessParseRequest(ParseRequest request);

}

Der Produzent von Resultaten braucht bei solchen Aufträgen keinen “Pin” mehr eigens für ihren Versand. Das macht ihn auch flexibler, weil er dadurch von vielen Clients als Service benutzt werden kann. Jeder Client schickt ja mit seinem Request einen Request-individuelle Antwort-“Pin” mit. Der funktioniert wie eine Return-Adresse bei normalen Funktionsaufrufen.

class CompilerCoordinator : ICompilerCoordinator

{

    public void ProcessCompilationRequest(CompilationRequest request)

    {

        ParseResult pr;

        this.OnParse(new ParseRequest

            {

                Source = request.Source,

                ProcessParseResult = r => pr = r

            });

        …

    }

    …

Im einfachsten Fall stecken Sie in den nachrichtenindividuellen Event-Handler für das Resultat eine Lambda-Funktion wie hier gezeigt. Ja, das ist etwas umständlicher als ein Funktionsaufruf – aber ich glaube immer noch daran, dass am Ende der Gewinn aus solch ereignisorientierter – oder besser: nachrichtenorientierter – Kommunikation die Umständlichkeit mehr als kompensiert. Außerdem ist der Umstand ja nur für inter-komponenten Aufrufe zu treiben. Die stellen gewöhnlich nur eine Minderheit in Ihrem Code dar.

Das Muster einfach: Die Request-Nachricht bekommt einen eigenen Event (oder auch nur einen Delegate), den ihr Empfänger mit einem Resultat aufruf. Genersich sieht das z.B. so aus:

class Request<TInput, TOutput>

{

    public TInput Input;

    public Action<TOutput> ProcessResult;

}

Client:

event Action<Request<T0, T1>> OnT0WaitingForT1;

Service:

void TransformT0IntoT1(Request<T0, T1> request);

Notifkationen

Den Empfänger in einer Nachricht zu transportieren ist der universelle Weg, um Nachrichten kontextabhängig zuzustellen. Das gilt nicht nur für Resultate, sondern auch für Zwischenergebnisse jeder Art. Das können z.B. Fortschrittsinformationen sein. Wenn Sie den Fortgang einer Operation dem Auslöser mitteilen möchten, versenden Sie am besten einen Endpunkt, dem darüber Bescheid gegeben werden kann:

class ParseRequest

{

    …

    public Action<double> ShowProgress;

}

Ein Service muss dann nicht permanent an einen Event-Handler gebunden sein, sondern bekommt für jeden Auftrag womöglich einen neuen mitgeteilt.

ParseResult pr;

this.OnParse(new ParseRequest

    {

        Source = request.Source,

        ProcessParseResult = r => pr = r,

        ShowProgress = p => Console.WriteLine("Fortschritt: {0}", p)

    });

Hier zeigt sich für mich sehr schön die Simplizität der Nachrichtenorientierung. Sie kennt nur wenige Konzepte, die man aber ganz flexibel immer wieder neu kombinieren kann. In einer einführenden Serie zum Application Space in meinem englischen Blog können Sie sehen, was das für verteilte Anwendungen bedeutet. Notifikationen sind sozusagen “first class citizens” bei der Nachrichtenorientierung.

Das Muster ist ganz einfach und unterscheidet sich nicht von dem für Resultate in Request/Response-Szenarien.

Request/Response V2

Ich verstehe, wenn Ihnen eigene Events für Resultats oder Notifikationen nicht so leicht runtergehen. Sie sollten sie allerdings als Muster “drauf haben”. Allemal für die synchrone ereignisbasierte Kommunikation sehe ich jedoch noch eine Alternative. Wir können einen Delegaten speziell für Requests definieren:

delegate void Request<TInput, TOutput>(TInput message,

Action<TOutput> processResult);

Der Kontrakt wird damit wieder einfacher:

  1. Es ist kein Resultat-Event in der Request-Nachricht mehr nötig.
  2. Die Signaturen können sich auf das Wesentliche konzentrieren und werden verständlicher.

Hier der überarbeitete CompilerCoordinator als Client:

interface ICompilerCoordinator

{

    …

    event Request<string, ASTNode> OnParseRequest;

}

Ich habe jetzt sogar auf den ParseRequest-Nachrichtentypen verzichtet, weil darin ohnehin nur ein Argument verpackt würde. Die Gesamtsignatur scheint mit jetzt so spezifisch, dass der Typ nicht mehr nötig ist. (Allerdings würde ich immer noch dazu raten, nicht mehr als einen Parameter für die Argumente einer Operation zu benutzen. Fallen Sie nicht zurück in die Gewohnheiten “traditioneller” Komponenten mit ihren ausgefeilten Signaturen. Sie machen es sich damit unnötig schwer, später auf die asynchrone Ereignisorientierung umzusteigen.)

Und hier der Parser als Service:

interface IParser

{

    void ProcessParseRequest(string source, Action<ASTNode> processResult);

}

Beachten Sie, wie der Endpunkt für den Versand des Resultats als Delegat in den Event-Handler hinein gereicht wird. Das ist besser lesbar, weil schon beim Blick auf die Signatur klar ist, dass ein Resultat geliefert werden soll.

Und auch der Client wird in der Implementation einfacher:

class CompilerCoordinator : ICompilerCoordinator

{

    public void ProcessCompilationRequest(CompilationRequest request)

    {

        ASTNode program;

        this.OnParseRequest(request.Source, r => program = r);

        …

    }

    …

Das Muster ist einfach: Für jeden kontextspezifischen Notifikationsendpunkt übergeben Sie einen an den Nachrichtenempfänger Delegaten als Parameter. Der kann dann ein Resultat liefern oder Fortschrittsinformationen oder noch ganz anderes…

Request/Response V3

Wenn Sie spezielle Delegaten für Requests denken können, dann können Sie das Spiel natürlich auch noch weiter treiben. Auch Folgendes ist möglich:

public void ProcessCompilationRequest(CompilationRequest request)

{

    ASTNode program = this.OnParseRequest.Request(request.Source);

    …

}

Huch! Wo ist denn die Ereignisorientierung hin? Der Code feuert den Event mit der Aufforderung zum Parsen, denn er benutzt ja dessen Membervariable OnParseRequest. Aber die Aufforderung liefert ein Resultat. Wie kann das sein?

Eine Erweiterungsmethode auf dem Request<>-Delegatentyp machts möglich:

public static class RequestExtension

{

    public static TOutput Request<TInput, TOutput>(this Request<TInput, TOutput> fireEvent, TInput msg)

    {

        TOutput output = default(TOutput);

        fireEvent(msg, r => output = r);

        return output;

    }

}

Und schon ist sie dahin die schöne Nachrichtenorientierung…

Auch wenn das möglich ist, empfehle ich doch, es nicht zu tun. Sie verschleiern das grundlegende Programmiermodell bis zur Unkenntlichkeit. Das halte ich für Kontraproduktiv im Sinne der Verständlichkeit. Man sieht einfach nicht mehr so deutlich, wo Komponentengrenzen verlaufen. Und es baut eine Hürde für die spätere Migration zu asynchroner Kommunikation auf.

Mit einer anderen Variante könnte ich mich allerdings anfreunden:

public void ProcessCompilationRequest(CompilationRequest request)

{

    this.OnParseRequest

        .Request(request.Source)

        .Receive(astNode =>

            {

                …

            });

}

Hier wird dem Request explizit eine Continuation mitgegeben, d.h. Code, der nach seiner Erfüllung ausgeführt werden soll. So ist der Code immer noch lesbar; die bisherige lokale Variable für das Resultat wird gespart; und vor allem funktioniert dieser Stil auch in asynchronen Szenarien.

Möglich wird mit einer Erweiterungsmethode und einer kleinen Fluent Interface Klasse:

public static class RequestExtension

{

    public static RequestContinuation<TOutput> Request<TInput, TOutput>(this Request<TInput, TOutput> fireEvent, TInput msg)

    {

        return new RequestContinuation<TOutput>(processResult =>

            fireEvent(msg, processResult));

    }


     public
class RequestContinuation<TOutput>

    {

        private readonly Action<Action<TOutput>> fireEvent;

    
       
public RequestContinuation(Action<Action<TOutput>> fireEvent)

        {

             this.fireEvent = fireEvent;

        }

   
        public void Receive(Action<TOutput> processResult)

        {

            fireEvent(processResult);

        }

    }

}

Hervorhebenswert ist hier, dass die Erweiterungsmethode eine Funktion höherer Ordnung aus dem Event-Delegaten macht. Der wird also nicht in der Erweiterungsmethode aufgerufen, sondern erst in Receive(). Ein wenig Funktionale Programmierung ist hier also hilfreich, um die Ereignisorientierung angenehmer zu gestalten.

Soweit für dieses Mal mit den Event-Based Components. Ich hoffe, inzwischen konnte ich Ihre Bedenken zerstreuen, dass die unhandlich und schwierig sind. Beim nächsten Mal geht es weiter mit einem Blick auf die Verdrahtung von EBCs.

Montag, 22. Februar 2010

Steckspiele - Event-Based Components verbinden

Wie ich hier erklärt habe, liegen mir die “traditionellen” Komponenten – bei aller Liebe – doch auch ein wenig im Magen. Sie passen noch nicht ganz in meine Vision von wirklich “zusammensteckbaren” Software-Bausteinen. Von Event-Based Components (EBC) erwarte ich mir nun einen Fingerzeig, wie es besser werden kann.

Das Grundmuster für Ihre Implementierung ist denkbar einfach:

  • EBC kommunizieren nur über Nachrichten. Anders als bei der Event-Driven Architecture (EDA) ist diese Kommunikation allerdings (zunächst) synchron. Am Programmablauf ändert sich durch EBC also erst einmal nichts.
  • EBC erhalten Aufforderungen zu einer Dienstleistungen über Input-Nachrichten; sie erteilen Aufforderungen zu Dienstleistungen über Output-Nachrichten.
  • Die Übersetzung dieses Nachrichtenflusses in Code ist schematisch:
    • Nachrichten werden durch eine je eigene Klasse repräsentiert
    • Input-Nachrichten werden von Methoden ohne Rückgabewert verarbeitet
    • Output-Nachrichten werden über Delegaten versandt

Eine Komponente mit einer Input- und einer Output-Nachricht

image

hat damit einen Kontrakt wie folgt:

interface IEventBasedComponent

{

    void ProcessIncomingCommand(IncomingCommand cmd);

    event Action<OutgoingCommand> OnOutgoingCommand;

}

Das ist quasi alles, was ich derzeit zum formalen Aufbau von EBC sagen kann. Der Rest sind Folgerungen.

Symmetrische unidirektionale Kommunikation

Zunächst ist z.B. zu bemerken, dass der Nachrichtenfluss immer unidirektional ist. Input und Output fließen immer von der Quelle zum Ziel; im Falle von Aufforderungen bedeutet das, vom Client zum Service.

Nachrichten “haben” keine Resultate wie Methodenaufrufe. Es kann nichts automatisch zurück fließen. Die Kommunikation zwischen EBC ist grundsätzlich symmetrisch. Client und Service unterscheiden sich nicht. Für eine Aufforderung ist der Client die Quelle einer Nachricht an das Ziel Service. Die Aufforderung ist der Output des Clients und der Input des Service. Und falls der Service ein Resultat an den Client zurück geben soll, dann schickt er ihm ebenfalls eine Nachricht, die sein Output und der Input der Clients ist.

Die Begriffe Client und Service, so natürlich sie in der Welt der “traditionellen” Komponenten sind, bekommen in der Welt der EBC also einen neuen Stellenwert. Früher war die Funktionseinheit, die einen Methodenaufruf absetzt, automatisch der Client. Sie fordert damit eine Dienstleistung von einer Funktionseinheit, von der sie abhängt. Client und Service stehen in einem asymmetrischen Verhältnis. Argumente in einem Methodenaufruf sind etwas anderes als der Rückgabewert.

Zwischen EBC fließen jedoch nur Nachrichten. Das fundamentale Verhältnis zweier Funktionseinheiten ist nicht mehr das zwischen Client und Service, sondern das von Quelle und Ziel. Ob die Quelle ein Client und das Ziel ein Service sind, ist nicht mehr eine Frage der Syntax, sondern der Semantik.

Unabhängige Komponenten

Aus der symmetrischen unidirektionalen Kommunikation mit der obigen Codierung ergibt sich, dass Event-Based Components unabhängig von einander sind. Das bedeutet, EBC sind wahrhaft “composable”. Wir können sie ohne Eingriff zusammenstecken. Wo keine Services mehr aufgerufen werden, da gibt es einfach keine Abhängigkeit mehr. Output-Nachrichten fließen einfach nur aus einer EBC hinaus. Ob und von wem sie empfangen werden… das weiß die Komponente nicht.

Wie das geht, zeigt eine erste Version der Interaktion zwischen zwei Komponenten, die ich schon im ersten EBC-Blogartikel benutzt habe. Das Szenario ist unvollständig, weil der Codegenerator fehlt, aber das möchte ich im Moment vernachlässigen:

image

Hervorhebenswert ist dabei, dass CompilerCoordinator und ParserWorker in einer Client-Service-Beziehung stehen, Anfrage und Antwort jedoch auf getrennten “Kanälen” fließen. Formal gibt es keinen Unterschied zwischen der Aufforderung zum Parsen und deren Resultat. Die Nachrichten fließen nur in die entgegengesetzte Richtung.

Auf den Input “Compile” reagiert der CompilationCoordinator mit dem Output “Parse”; der ist Input für den ParserWorker, der daraus “ASTGenerated” als Output erzeugt; der wiederum für den CompilationCoordinator Input ist und über einen hier nicht sichtbaren weiteren Schritt zum Output “CompilationResult” führt.

Die Kontrakte sehen dafür so aus:

compiler.contract.dll:

interface ICompilerCoordinator

{

    void ProcessCompilationRequest(CompilationRequest request);

    event Action<CompilationResult> OnCompilationResult;

    event Action<ParseRequest> OnParse;

    void ProcessParseResult(ParseResult result);

}

parser.contract.dll:

interface IParser

{

    void ProcessParseRequest(ParseRequest request);

    event Action<ParseResult> OnParseResult;

}

Zusätzlich gibt es noch einen Kontrakt für die Nachrichten, d.h. die gemeinsame Sprache der Komponenten:

message.contract.dll:

class CompilationRequest

{

    public string Source;

}

class CompilationResult

{

    public Func<double, double> Function;

}

class ParseRequest

{

    public string Source;

}

class ParseResult

{

    public ASTNode Root;

}

Die Interfaces erscheinen schon ein wenig anders als von den “traditionellen” Komponenten gewohnt – aber der wesentliche Unterschied zeigt sich erst bei Betrachtung der Abhängigkeiten zwischen Komponenten und Kontrakten.

Klassisch sehen die so aus:

image

Die Compiler-Implementation ist von zwei Interfaces abhängig: einem, das sie exportiert, und einem, das sie importiert. Je mehr Abhängigkeiten eine Komponente hat, desto mehr Kontrakte muss sie also referenzieren.

Dem steht nun die EBC-Variante entgegen:

image

Zwei Unterschiede fallen auf: Es gibt einen Kontrakt für die Nachrichten, der in diesem Szenario “traditionell” noch nicht nötig war. Viel wichtiger ist jedoch, dass jede Komponentenimplementation nur noch abhängig ist von einem Kontrakt. Das bedeutet, jede Komponente ist nur noch durch einen Kontrakt spezifiziert und nicht durch 1+n. Damit ist schon mal mein 4. Problem mit der “traditionellen” Komponentenorientierung gelöst. (Insgesamt fünf Probleme oder “Bauschmerzen” hatte ich im ersten EBC-Blogartikel genannt.) EBC-Komponenten lassen sich kompakter spezifizieren.

Aber nicht nur ist mein Problem 4 gelöst. Auch Problem 1 hat sich in Luft aufgelöst. Ich hatte bei aller Entkopplung von “traditionellen” Komponenten durch die separaten Kontrakte immer noch Bauchschmerzen, weil Clients von Services abhängig waren. Das ist bei EBC-Komponenten nun nicht mehr der Fall. Auch wenn EBC-Komponenten zusammengesteckt werden können und müssen, sind sie nicht mehr abhängig. Sie produzieren Output und erwarten Input. Das ist aus meiner Sicht etwas anderes; vielleicht nur subtil, aber immerhin.

Jetzt zur Frage, wie EBC-Komponenten “zusammen spielen”? Einen Container gibt es dafür bisher nicht, wenn ich es recht sehe. Aber es ist auch nicht schwer, EBC-Komponenten zusammen zu stecken. Sehen Sie selbst:

// Build

ICompilerCoordinator cc = new CompilerCoordinator();

IParser p = new Parser();

// Bind

cc.OnParse += p.ProcessParseRequest;

p.OnParseResult += cc.ProcessParseResult;

cc.OnCompilationResult += cr => Console.WriteLine(cr.Function(2));

// Run

cc.ProcessCompilationRequest(new CompilationRequest {Source = "2*x"});

Am besten instanziieren Sie zuerst alle Komponentenimplementationen (Build-Phase). Das ist im Gegensatz zur “traditionellen” Komponentenorientierung möglich, da es ja keine Abhängigkeiten mehr gibt, die über Konstruktoren injiziert werden müssten. (Ja, ich weiß, es ginge auch “traditionell” anders über Property-Injection, aber in der Praxis kommt die nicht sehr häufig vor. Sie fühlt sich im Vergleich zur ctor-Injection nicht so natürlich an.)

Wenn alle Komponenten instanziert sind, setzen Sie sie zum größeren Ganzen zusammen (Bind-Phase). Dazu registrieren Sie Input-Verarbeitungsfunktionen als Handler für Output-Events. Beispiel: Der CompilerCoordinator definiert mit dem OnParse-Event den Output ParseRequest. Auf dem registriert die Bindungsphase die Input-Methode ProcessParserequest() der Parser-Komponente. Damit sind CompilerCoordinator und Parser in eine Richtung zusammen gesteckt.

Dass der ParseRequest eine Antwort erwartet, sieht man ihm nicht an. Folglich muss für das ParseResult eine ebensolche Verbindung in umgekehrter Richtung hergestellt werden. EBC-Kommunikation ist eben wahrhaft symmetrisch.

Die Bindung von EBC-Komponenten ist also denkbar simpel. Input-Handler werden auf Output-Events registriert. Fertig. Sie müssen nur die Datenflüsse zwischen EBC-Komponenten identifizieren und wissen sofort, wie Sie sie umsetzen.

Dass das Nachricht für Nachricht in Handler-Event-Paaren geschieht, finde ich nicht schlimm. Im Gegenteil! Verbindungen zwischen Funktionseinheiten lassen sich dadurch viel detaillierter steuern. Sie müssen sich beim Entwurf einer Komponente keine Gedanken machen, welche andere Komponenten Outputs weiter verarbeiten oder Inputs liefern.

So löst sich für mich auch mein Problem Nr. 3 mit der “traditionellen” Komponentenorientierung in Luft auf. Ob Inputs und Output von einer anderen Komponente bedient werden oder von vielen… Das ist egal. Es kann sich deshalb auch während der Entwicklungszeit problemlos ändern. Betroffen sind dann allein die Build-Bind-Phasen, aber nicht die Komponentenimplementationen. Die haben ja keine Abhängigkeiten zu anderen Kontrakten.

Damit ist auch dem Prinzip Genüge getan, dass Komplexes wenige Abhängigkeiten haben sollte und Einfaches durchaus viele Abhängigkeiten haben kann. Denn komplex sind Komponentenimplementationen, die durch EBC quasi abhängigkeitsfrei sind. Build und Bind hingegen sind trivial und dürfen deshalb viele Abhängigkeiten haben.

Mit EBC-Komponenten sind wir also bei Platinen und Bauteilen angekommen. Build+Bind entsprechen einer elektronischen Platine, die Bauteile verbindet. Welche Bauteile das sind, hängt vom Zweck eines Gerätes ab. Dem muss sich die Platine anpassen und die Bauteilauswahl. Die Bauteile jedoch selbst, die bleiben unverändert. Ich nehme den Begriff der Wiederverwendbarkeit nur ungern in den Mund, aber hier passt er mal. Das Potenzial für Wiederverwendbarkeit scheint mir bei EBC-Komponenten dank ihrer Entkopplung und zentralen Definition mittels nur eines Kontraktes höher als bei “traditionellen” Komponenten.

Geschachtelte Komponenten

Hm… vielleicht ist die Analogie mit den elektronischen Bauteilen passender als zunächst gedacht. Ein Software-Bauteil ist dann immer eines, für das es einen EBC-Kontrakt mit einer zugehörigen Implementation gibt. Ob diese Implementation selbst Geschäftslogik ist oder nur Geschäftslogik-Bauteile verdrahtet… das ist egal. Im letzteren Fall wäre sie eine Platine, also ein Aggregat von Bauteilen, das wieder ein Bauteil auf höherer Ebene darstellt.

Software-Platinen könnten hybride Komponenten sein: Einerseits implementieren Sie einen EBC-Kontrakt, andererseits werden sie initialisiert wir “traditionelle” Komponenten. Hier eine “Compiler-Platine” als Beispiel. Deren Kontrakt könnte so aussehen:

interface ICompiler

{

    void ProcessCompilationRequest(CompilationRequest request);

    event Action<CompilationResult> OnCompilationResult;

}

Dass es da keinen Unterschied zum CompilerCoordinator gibt, ist völlig ok. Wenn der CompilerCoordinator die ganze Interaktion des Compilers mit der Umwelt verkörpert, dann werden dessen “Signalleitungen” zu Inputs- und Outputs der umfassenden Platine.

Die ist selbst eine EBC-Komponente qua Kontrakt – ist jedoch abhängig von ihren Bauteilen. Und die injiziert ihr ein DI Container wie üblich:

class Compiler : ICompiler

{

    private ICompilerCoordinator cc;

    public Compiler(ICompilerCoordinator cc, IParser p)

    {

        this.cc = cc;

        // Bind

        cc.OnParse += p.ProcessParseRequest;

        p.OnParseResult += cc.ProcessParseResult;

        // Output “von innen” weiterleiten zur “Platine”

        cc.OnCompilationResult += cr => this.OnCompilationResult(cr);

    }

   
    public void ProcessCompilationRequest(CompilationRequest request)

    {

        // Input der “Platine” weiterleiten “nach innen”

        this.cc.ProcessCompilationRequest(request);

    }


    public
event Action<CompilationResult> OnCompilationResult;

}

EBC und “traditionelle” Komponenten sind also kein Widerspruch, sondern ergänzen sich. “Software-Platinen” sind hybride Komponenten, die nur den Zweck haben, EBC zu aggregieren. Sie dürfen Abhängigkeiten haben, weil sie so simpel sind. Nach außen verhalten Sie sich aber wie EBC.

Im folgenden Bild sind die schwarzen Kästen “atomare” EBC; die offenen Kästen sind “Platinen” – die jedoch genauso wie die schwarzen Kästen Input- und Output-Pins haben. Formal kann man beide also nicht auseinander halten:

image

Ob die Implementation eines EBC-Kontrakts in einer “atomaren” EBC besteht oder in einer hybriden, das muss auch einerlei sein, denn das kann sich jederzeit ändern, weil ein “Software-Bauteil” keine sichtbaren Abhängigkeiten hat.

Mir scheint solche Schachtelung von Funktionsbausteinen mit EBC einfacher als rein mit “traditionellen” Komponenten. Deshalb lösen EBC auch mein Problem Nr. 5. Die Schachtelungsregeln sind ganz einfach:

  • Eine EBC ist ein “Software-Bauteil”.
  • Wo zwei oder mehr “Software-Bauteile” mit einander verdrahtet werden, entsteht ein neues “Software-Bauteil”. Dessen Kontrakt besteht aus den in ihm unverdrahtet gebliebenen Input- und Output-Pins seiner Konstituenten.

Bin ich damit bei einem neuen Begriff angekommen, dem “Software-Bauteil”? Gibt es EBC und EBP, also Event-Based Parts? Ist eine Component ein bestimmte Art von Part. Hm… darüber muss ich mal nachdenken. Aber als nächstes will ich erstmal beschreiben, wie die Kommunikation zwischen Parts einfacher werden kann.

PS: Von meinen 5 Problemen sind bisher 4 gelöst. Was ist aber mit Problem Nr. 2? Sind EBC “besser” als “traditionelle” Komponenten, weil in sie nichts mehr injiziert wird? Oder wird noch injiziert, nur anders?

Eine Ctor-Injection oder auch eine Property-Injection eines ganzen Interface scheint mir schwergewichtiger zu sein als die Injektion einzelner Eventhandler. Ganz einfach, weil die übliche DI sich auf ganze Interfaces bezieht. Da wird immer eine Menge auf einmal in eine Komponente hinein gesteckt. Wie schlank ist dem gegenüber die Registrierung einiger Eventhandler.

Deutlich wird das für mich, wenn ich daran denke, was passiert, wenn sich die Zusammensetzung eines importierten Kontrakts ändert. Wird eine Operation herausgelöst in eine andere “traditionelle” Komponente, dann müssen alle abhängigen ihre Injektionsstellen nachführen.

EBC merken davon nichts. Wird ein Output von einem anderen EBP verarbeitet, ändert sich etwas an der Platinen, die beide verdrahtet, aber nichts an der Implementation dort, wo der Output heraus kommt.

Für mich fühlt sich das an wie eine Lösung für mein Problem Nr. 2.

Ha! Wer hätte das gedacht. So entpuppen sich EBC als den “traditionellen” Komponenten überlegen. Die werden, wie oben bei den hybriden Komponenten gezeigt, allerdings nicht ersetzt, sondern bekommen einen neuen Platz zugewiesen.

Wer abhängig ist, der darf nur einfaches tun, z.B. zusammenstecken. Wer unabhängig ist, der darf kompliziertes tun, z.B. Geschäftslogik implementieren. Die Unterscheidung zwischen EBC und “traditionellen” Komponenten scheint mir damit ganz in der Linie des Separation-of-Concerns-Prinzips zu liegen.

Sonntag, 21. Februar 2010

Event-Based Components – Der nächste Schritt der Komponentenorientierung?

Wie wäre es eigentlich, wenn wir Komponenten zwar synchron, aber nachrichtenorientiert koppeln würden? Wie wäre es, wenn EDA – Event-Driven Architecture – nicht nur eine Sache großer Anwendungen wäre, sondern in Form von Event-Based Components (EBC) auch kleine Applikationen evolvierbarer gestalten helfen würde?

Über die Vorteile von Komponentenorientierung an sich möchte ich mich hier nicht schon wieder auslassen ;-) Komponenten als binäre Codeeinheiten mit separatem Kontrakt sind für mich schlicht die Basis jeder bewussten Anwendungsarchitektur.

Wie solche Komponenten aber definiert und dann in Code gegossen werden, darüber lässt sich immer wieder nachdenken. Bisher habe ich den folgenden Ansatz vertreten:

  • Die Architektur einer Software resultiert in einem Komponentenabhängigkeitsdiagramm.
  • Die Implementation der Komponenten verteilt sich auf zwei Assemblies: eine für den Kontrakt, eine für die Funktionalität.
  • Wenn Komponenten andere brauchen, d.h. von ihnen abhängig sind, dann werden diese Komponenten der abhängigen von einem DI Container injiziert.

Komponentenorientierung “traditionell”

Hier eine ganz einfache Architektur mit drei Komponenten für ein Szenario, dass Ihnen bekannt vorkommen sollte ;-)

image

Daraus ergäben sich die Assemblies:

  • compiler.contract.dll
  • compiler.dll
  • parser.contract.dll
  • parser.dll
  • codegenerator.contract.dll
  • codegenerator.dll

compiler.dll würde compiler.contract.dll referenzieren, weil sie deren Kontrakt exportiert, d.h. implementiert. Und compiler.dll würde parser.contract.dll sowie codegenerator.contract.dll referenzieren, weil sie deren Kontrakte importiert. Zur Laufzeit braucht compiler.dll dann natürlich Implementationen dieser Kontrakte. Kennen tut sie deshalb parser.dll und codegenerator.dll nicht. Dependency Injection macht es möglich.

Und wie sehen die Kontrakte aus? Das hängt von den Diensten der Komponenten ab. Üblicherweise wird jede Komponente mindestens durch ein Interface für seine “Hauptdienstleistung” definiert.

compiler.contract.dll:

interface ICompiler

{

    Func<double, double> Compile(string formel);

}

parser.contract.dll:

interface IParser

{

    ASTNode Parse(string formal);

}

codegenerator.contract.dll:

interface ICodeGenerator

{

    Func<double, double> Translate(ASTNode program);

}

Und wo ist der ominöse ASTNode definiert? Da zwei Komponenten ihn brauchen, die nicht von einander abhängen, brauchen wir noch eine weitere Assembly:

compiler.datenmodell.contract.dll:

class ASTNode

{

    …

}

Diese reine Kontraktassembly wird von allen anderen referenziert. Sie enthält einen allen gemeinsame Vorstellung davon, wie Code im Compiler repräsentiert werden sollte: als abstrakter Syntaxbaum.

Die Implementationen der Komponenten ist wenig spannend. Nur den Compiler möchte ich hervorheben. Er ist ja abhängig von den anderen beiden Komponenten und muss daher Instanzen von ihnen zur Laufzeit bekannt gemacht bekommen:

class Compiler : ICompiler

{

    private readonly IParser parser;

    private readonly ICodeGenerator codegenerator;

    public Compiler(IParser parser, ICodeGenerator codegenerator)

    {

        this.parser = parser;

        this.codegenerator = codegenerator;

    }

    public Func<double, double> Compile(string formel)

    {

        var program = this.parser.Parse(formel);

        return this.codegenerator.Translate(program);

    }

}

Diese Bekanntmachung ist kein Hexenwerk. Mit einem DI Container ist das ganz einfach. Der übernimmt die Befüllung der Ctor-Parameter bei Instanzierung eines Compiler-Objektes. Hier ein Beispiel mit Microsoft Unity:

IUnityContainer uc = new UnityContainer();

uc.RegisterType<IParser, Parser>();

uc.RegisterType<ICodeGenerator, CodeGenerator>();

uc.RegisterType<ICompiler, Compiler>();

ICompiler c = uc.Resolve<ICompiler>();

var f = c.Compile("2*x");

Soweit meine bisherige Vorstellung von Komponentenorientierung. Das funktioniert alles wunderbar. Ist erprobt. Läuft. Alles kein Problem.

Und doch… irgendwie bin ich nicht so ganz zufrieden damit. Mich sticht der Zweifel, ob das schon der Weisheit letzter Schluss ist in Bezug auf “Composability”. Kann man Komponenten, also die architekturellen Grundbausteine von Software, in dieser Weise schon optimal “zusammenstecken”?

Kritik der “traditionellen” Komponentenorientierung

Wenn Komponenten Bausteine sein sollen wie Lego-Bausteine oder elektronische Bauteile, dann, so glaube ich, widerspricht dem, wie wir mit ihren Abhängigkeiten umgehen.

Erstens dürfen Komponenten beim “traditionellen” Ansatz überhaupt Abhängigkeiten haben. Das scheint mir nicht ganz passend in Bezug auf den Baustein-Begriff. Ein Lego-Baustein braucht keine anderen. Er passt mit anderen zusammen, aber er braucht sie nicht. Dasselbe gilt für einen Transistor oder einen Widerstand oder auch einen Prozessor. Keines dieser Bauteile braucht ein anderes zum Funktionieren. Es braucht Signale (Input) im Rahmen einer Spezifikation, aber woher diese Signale kommen und wohin der eigene Output geht… das ist den Bauteilen egal. Davon wissen sie nichts. Dafür ist die Platine zuständig, in der sie stecken.

Zweitens injizieren wir die Abhängigkeiten in Komponenteninstanzen. Wir verändern die Komponenten also zur Laufzeit, um ihre dynamischen Abhängigkeiten zu befriedigen. Das fühlt sich zumindest irgendwie merkwürdig im Vergleich zu realweltlichen Bausteinen an. Und es kann für Testzwecke relativ aufwändig sein, wenn dafür Attrappen gebaut werden müssen.

Drittens sind die Abhängigkeiten gewöhntlich definiert in Form von Interfaces. Es geht also immer um Bündel von Operationen. Eine abhängige Komponente läuft damit aber Gefahr, Zugriff auf Operationen zu bekommen, die sie nichts angehen. Im obigen Beispiel wird das nicht deutlich, aber sobald Servicekomponenten mehreren Clientkomponenten dienen, wachsen ihre Kontrakte schnell über das hinaus, was einer dieser Clients von Ihnen braucht.

Viertens ist die Spezifikation einer Komponente nicht sehr kompakt, wenn ich dafür mehrere Kontrakte anschauen muss. Sie besteht ja aus dem exportierten und allen importierten Kontrakten. Wenn ich wissen will, wie eine Komponente zu implementieren ist, dann sind 1+n Kontrakte zu konsultieren. Es gibt keinen einen Ort, an dem kompakt beschrieben ist, wie eine Komponente mit ihrer Umwelt interagiert.

Fünftens tue ich mich immer noch schwer mit der Schachtelung von Komponenten. Im Beispiel oben habe ich zwar drei hübsche Komponenten, aber eigentlich würde ich sie anders bezeichnen und in eine umfassendere einschachteln wollen:

image

Erst diese umfassende Codeeinheit wäre der Compiler. Die bisherigen sind nur seine Bausteine. Mit der bisherigen Komponentenorientierung finde ich das zu planen aber nicht so einfach. Wie soll die umfassende Codeeinheit auch eine Komponente sein, wenn es ihre Konstituenten auch sind?

Das sind für mich genügend Gründe, weiter darüber nachzudenken, wie Komponenten noch besser gebaut werden können, so dass Software wahrhaft aus Bausteinen besteht.

Ein Ansatz dafür, den ich gerade spannend finde, ist die konsequente Ereignisorientierung.

Komponentenkommunikation über Ereignisse

Fünf Gründe, über die “traditionelle” Komponentenorientierung nachzudenken. Aber geht es anders irgendwie auch fünf Mal besser? Ich glaube, schon. Mit der Ereignisorientierung lösen sich manche Probleme in Luft auf und anderes wird klarer, wie mir scheint.

image Auf geht´s… Was bedeutet Ereignisorientierung für Komponenten? Zum Thema gibt es ein interessantes Buch: Event-based Programming von Ted Faison. Das hat mich auf die Spur von Event-Based Components gebracht. Allerdings weicht meine Vorstellungen in einigen Punkte von der des Buches ab. Mir ist seine Darstellung gerade bei der Praxis ein wenig zu allgemein. Aber die Grundgedanken darin finde ich sehr spannend… Hier deshalb meine Version ihrer Implementation.

Ich fange mal mit einer etwas anderen Notation an als der bisherigen. Die ist den Wire-Diagrams (Signaldiagramme) des Buches angelehnt. Hier zum warm werden eine ganz simle Event-Based Component (EBC), die als Dienstleistung die Verarbeitung eines Kommandos anbietet. Kommando bedeutet dabei im Sinne der Command-Query Separation, dass kein Resultat an den Aufrufer geliefert wird.

image

Ein Rechteck statt eines Kreises bzw. einer Ellipse ist nur ein oberflächlicher Unterschied zwischen den Diagrammen. Die wahre Andersartigkeit steckt in der Implementation. Hier der Kontrakt für die Komponente:

interface IEventBasedComponent

{

    void ProcessIncomingCommand(IncomingCommand cmd);

}

Wie bei der “traditionellen” Komponentenorientierung ist der Kontrakt einer EBC ein Interface. Für jede eingehende Nachricht, die eine Komponente "versteht”, gibt es darin eine Methode mit einem Parameter. Der hat einen für jede Nachricht spezifischen Typ.

Ob die Methode dann wie oben den Typ ihrer Nachricht im Namen widerspiegeln sollte oder nicht, weiß ich noch nicht so recht. Im Augenblick ist das mal meine Konvention inklusive eines Prefixes wie “Process”/”Execute” (für Kommandos) oder “Fulfill”/“Inquire” (für Queries).

EBC-Methoden für eingehende Nachrichten haben damit keine normale Signatur mit vielen Parametern oder einem Return-Wert. Es sind immer nur void-Methoden mit nur einem Parameter.

Jetzt zu ausgehenden Nachrichten oder besser zum Output. Denn um nichts anderes handelt es sich, auch wenn solche Nachrichten eine Antwort erwarten, wenn sie eine Komponente verlassen.

image

Hier unterscheiden sich Event-Based Components nun deutlich von den “traditionellen”. Output-Nachrichten werden ausschließlich über Events verschickt. Der Kontrakt sieht dafür dann so aus:

interface IEventBasedComponent

{

    event Action<OutgoingCommand> OnOutgoingCommand;

}

Das ist der Trick an der ganzen Sache mit der Ereignisorientierung: Nachrichten an andere Komponenten werden als Events verschickt. Wieder haben die Methoden nur einen Parameter und keinen Rückgabewert.

Bei der Benennung der Delegaten folge ich wieder einem Muster. Als Name verwende ich wieder den Nachrichtentyp und setze einen Präfix davor, z.B. “On” oder “Issue” (für Kommandos) oder “Request” (für Queries).

Hört sich irgendwie nicht so spektakulär an, oder? Ich glaube aber nach erster Evaluation, dass die Beschränkung der Kommunikation auf synchrone Input- und Output-Nachrichten in dieser Weise grundsätzliche und positive Folgen hat. Doch davon ein andermal…