Die Retusche - EBC im Brownfield

vincent-van-gogh-retusche Lange hat es gedauert, dass ich mich an dieses Thema gewagt habe. Nun ist es aber soweit, weil die aktuelle Projektsituation es verlangt.
Ich möchte EBC’s in einem Brownfield Projekt einfügen. Die Schwierigkeit ist das Projekt an sich, das durch seine sehr “interessante” Struktur das anlegen von EBC’s quasi verhindert. Dennoch bin ich der Meinung, das ich das Projekt mit Hilfe von EBC’s verbessern kann und zwar im Bereich der Infrastruktur, respektive dem Logging.

Derzeit existieren viele tausend Programmzeilen im Projekt, die das Logging durchführen und damit den Code “verunreinigen”oder besser vom Wesentlichen der Funktionen ablenken. Mein Plan ist es also mit Hilfe der AOP einen Loggingaspekt zu integrieren, der dann mit EBC Technologie eben diese Aufgabe erfüllt. Nun könnte der ein oder andere meinen: “So ein Quatsch, das ist doch nicht nötig, die traditionelle Herangehensweise ist doch vollkommen ausreichend”. Dem kann ich im Prinzip nichts dagegensetzen, es sei denn ich erwähne die Entkopplung zwischen Aspekt und Logging Framework oder der stark vereinfachten asynchronen Logik. Aber das sind ja Attribute, die in keiner sorgfältig geplanten Software etwas zu suchen haben.

Nein, im Ernst, ich habe vor den Aspekt und den Loggingprozess als EBC’s zu formulieren, damit ich die Vorteile der EBC ausnutzen kann. Auf diese Weise erreiche ich möglicherweise noch etwas anderes: Nebenläufigkeit der Applikationsinfrastruktur. Na mal sehen, wohin das führt.

Istzustand

Zur Verdeutlichung hier mal eine Darstellung des Istzustandes einer beliebigen Datenzugriffsmethode:

Daten laden

  • Daten werden gelesen
    • Falls ein Fehler auftritt wird eine Fehlermeldung erstellt und an einen Logger gesendet, der ihn wegschreibt
  • Daten werden in DTO’s gemappt und zurückgegeben
Sollzustand

An der Stelle des Schrittes “Handle Exception” möchte ich nun statt des berühmten try-catch-Blockes einen Aspekt verwenden, der die gleiche Aufgabe erfüllt, aber den Code nicht verunreinigt.

Handle ExceptionLog ist der Aspekt, der in der Applikation verwendet wird und im Falle einer Exception die Nachricht an den Logger sendet . Diese Kommunikation sollte asynchron geschehen, damit das Logging die Applikation nicht blockiert oder verlangsamt.

Vorgeschichte zur Lösung

Am Anfang stand die Aussage “AOP für das Logging”. Dies wollte ich mit PostSharp umzusetzen, weil ich damit schon seit Jahren herumhantiere. Aber dann kam die Ernüchterung: die Aspekte kann ich auf keiner Platine instanziieren und verdrahten. Das führte dazu, dass ich einen kleinen Ausflug zu  SNAP machte, weil hiermit Aspekte und Instanzen in ein und dem selben DI-Container behandelt werden, ...seht selbst:

Lösungsversuch 1: PostSharp Aspekt und die Platine

PostSharp Aspekt + EBC Output
  1. [Serializable]
  2. public class Log : OnMethodBoundaryAspect, ILogAspect
  3. {
  4.     
  5.     public override void OnException(MethodExecutionArgs args)
  6.     {
  7.         OutExceptionLog(new ExeptionLogMessage(args.Exception)
  8.                                  {
  9.                                      Method = args.Method.Name
  10.                                  });
  11.     }
  12.  
  13.     public Action<ILogMessage> OutExceptionLog
  14.     {
  15.         get; set;
  16.     }
  17.  
  18. }


Logger als EBC
  1. public class Logger : ILogger
  2. {
  3.     public void InLogMessage(ILogMessage logMessage)
  4.     {
  5.         System.Diagnostics.Debug.WriteLine(logMessage.ToString());
  6.     }
  7. }

Die Verdrahtung
  1. ObjectFactory.Configure( x => x.For<ILogger>().Use<Logger>());
  2. ObjectFactory.Configure( x => x.For<ILogAspect>().Use<Log>());
  3.  
  4. ILogger logger = ObjectFactory.GetInstance<ILogger>();
  5. ILogAspect logaspect = ObjectFactory.GetInstance<ILogAspect>();
  6.  
  7. logaspect.OutExceptionLog = _logger.InLogMessage;

Das sieht alles total einfach aus, ist es auch. Aber wie das so oft ist, der Teufel steckt im Detail, denn der Aspekt wird von PostSharp gesteuert und steht für diese Art der Verdrahtung gar nicht zur Verfügung. Wenn ich also eine neue Instanz zum Zwecke des Verdrahtens erzeuge, verbinde ich nicht wirklich die Pins der Komponenten, die tatsächlich arbeitet, sonder eben einfach nur eine neue Instanz. Der Aspekt sendet also seine Nachricht in das elektrische Nirvana.

Lösungsversuch 2: SNAP und StructureMap

Dieser Versuch scheiterte an der Anforderung im Brownfield zu funktionieren und an der Tatsache, dass ich auch hier nicht auf die tatsächlichen Instanzen der Aspekte zugreifen und damit die Verdrahtung realisieren kann.

Lösungsreview

In beiden Fällen kann ich die arbeitende Instanz nicht mit dem Logger Verdrahten, weil ich keinen Einfluss auf den Instanziierungszeitpunkt habe.

Eine Lösung wäre also ein globaler EventListener – für diesen Gedanken müsste ich mir das Gehirn mit Seife waschen –, der die Events der Aspekte einsammelt und verarbeitet. Das ist aber nicht EBC, das ist unerwartetes Verhalten, das ist meiner Meinung nach nicht Clean Code. Daher muss etwas anderes her: Statische Out Pins. Die sind sehr einfach zu realisieren und darüber hinaus auch noch verständlich.
Was aber schmerzt bei diesem Gedanken? Richtig, das Schlüsselwort “static”. Ich habe einfach zu viele schlechte Erfahrungen mit öffentlichen statischen Methoden oder Feldern gemacht. Nur in diesem Fall scheint es nichts Besseres zu geben, oder?

Erklärungsversuch – Beschönigung

Wenn ich also ein statisches Field am Aspekt habe und damit den EBC Charakter einer Komponente erzeuge, dann kann dies doch nicht so schlecht sein. Zudem passt dies auch in das Konzept der Infrastruktur. Logging gehört zur Infrastruktur, ist ein eigner Concern und kann somit auch als großes Ganzes innerhalb der Applikation existieren. Quasi als globale Komponente, die von allen Seiten die Nachrichten empfängt und verarbeitet.

Wenn nun jemand einen bitteren Geschmack im Mund hat, sollte einen Kommentar zu diesem Thema hinterlassen, denn auch ich bin mit dem statischen Field nicht so glücklich und vielleicht finden wir gemeinsam einen besseren Weg.

Der Lösung

Ich habe mich nun erst einmal für die Lösung Nummer 1 entschieden und verändere nur den Scope des Out Pins von “public Action” auf “public static Action”. Auf diese Weise kann ich Verdrahten und die Nachrichten verarbeiten.

Aspekt mit statischem Out Pin
  1. [Serializable]
  2.     public class Log : OnMethodBoundaryAspect
  3.     {
  4.         
  5.         public override void OnException(MethodExecutionArgs args)
  6.         {
  7.             OutExceptionLog(new ExeptionLogMessage(args.Exception)
  8.                                      {
  9.                                          Method = args.Method.Name
  10.                                      });
  11.         }
  12.  
  13.         public static Action<ILogMessage> OutExceptionLog
  14.         {
  15.             get; set;
  16.         }
  17.     }


Verdrahten
  1. ObjectFactory.Configure( x => x.For<ILogger>().Use<Logger>());
  2.  
  3. ILogger logger = ObjectFactory.GetInstance<ILogger>();
  4.  
  5. Log.OutExceptionLog = _logger.InLogMessage;

Das sieht schon richtig einfach aus. Wie sich das in der Praxis macht, werde ich nun erst einmal beobachten.

Nachtrag

Oben erwähnte ich noch die asynchrone Verarbeitung. Die baue ich natürlich auch einfach mal eben so ein, wie Ralf Westphal es in seinem Blog  für Asynchrone Kommunikation mit EBC’s beschrieben hat.
Ob ich schon Rx verwende, werde ich auch noch sehen. Heute möchte ich erst einmal das Grundgerüst aufstellen.

 

~Jan