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 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.
Log 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
- [Serializable]
- public class Log : OnMethodBoundaryAspect, ILogAspect
- {
- public override void OnException(MethodExecutionArgs args)
- {
- OutExceptionLog(new ExeptionLogMessage(args.Exception)
- {
- Method = args.Method.Name
- });
- }
- public Action<ILogMessage> OutExceptionLog
- {
- get; set;
- }
- }
- public class Logger : ILogger
- {
- public void InLogMessage(ILogMessage logMessage)
- {
- System.Diagnostics.Debug.WriteLine(logMessage.ToString());
- }
- }
- ObjectFactory.Configure( x => x.For<ILogger>().Use<Logger>());
- ObjectFactory.Configure( x => x.For<ILogAspect>().Use<Log>());
- ILogger logger = ObjectFactory.GetInstance<ILogger>();
- ILogAspect logaspect = ObjectFactory.GetInstance<ILogAspect>();
- 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.
- [Serializable]
- public class Log : OnMethodBoundaryAspect
- {
- public override void OnException(MethodExecutionArgs args)
- {
- OutExceptionLog(new ExeptionLogMessage(args.Exception)
- {
- Method = args.Method.Name
- });
- }
- public static Action<ILogMessage> OutExceptionLog
- {
- get; set;
- }
- }
- ObjectFactory.Configure( x => x.For<ILogger>().Use<Logger>());
- ILogger logger = ObjectFactory.GetInstance<ILogger>();
- 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