Aufbauend auf dem EPK Diagramm meines ersten Beitrags zu dem Thema war der nächste Schritt das Aufsetzen der Architektur. Gemäß dem CCD Prinzip „Entwurf und Implementation überlappen nicht“ (siehe hier) habe ich dazu als erstes die folgenden Kontrakte definiert:
Das Interface IPrintServiceController stellt den Einstiegspunkt für die eigentliche Anwendungslogik des Dienstes dar. Mit StartPrintJobs wird eine dynamische Anzahl (in der Konfiguration definiert) an Prozessen (Stichwort Threads) zum Verarbeiten der Druckjobs gestartet. Die Methode StopPrintJobs ist dafür zuständig diese wieder korrekt zu beenden.
Die den (Arbeiter-)Prozessen zu Grunde liegenden Objekte implementieren das Interface IPrintJobWorker, welches lediglich die öffentliche Methode Print und das Property WorkerName vorgibt. Letzteres ist nur fürs Logging von Interesse, wohingegen die Print-Methode genau das tut, was ihr Name schon sagt: Sie kümmert sich darum einen Druckjob zu verarbeiten, d.h. zu drucken (Anmerkung: Hier wird der VB6 Prozess aufgerufen).
Die Verwaltung der Druckjobs übernimmt die Implementierung des Interface IPrintJobController. Das Interface deklariert folgende Methoden:
- GetNextJobId: Holt den nächsten Druckauftrag aus der Warteschlange (=> Datenbank)
- MarkJobState: Die Methode setzt den Zustand eines Druckauftrags, d.h. ist er gerade in der Bearbeitung oder wurde er erfolgreich verarbeitet oder …
- ProcessIsStillWorking: Da wir unseren alten instabilen VB6 Legacy Code in einem separaten Prozess laufen lassen, muss stetig geprüft werden, ob dieser abgestürzt ist.
- StopWorking: Über diese Schnittstelle lässt sich der PrintJobController beenden, sodass die Methode GetNextJobId keine Druckjobs mehr zurückgibt
Wichtig: Da es sich um eine parallelisierte Verarbeitung der Druckjobs handelt, muss die konkrete Implementierung des IPrintJobController ein Singleton sein, da sonst z.B. mehrfach der gleiche Druckjob für die Verarbeitung zurückgeliefert werden könnte.
Um den gesamten Kontext nochmal formal aufzuzeigen:
Der PrintServiceController startet einen oder mehrere Druckprozesse. Jeder Druckprozess läuft so lange bis dieser wieder vom PrintServiceController beendet wird (siehe Anmerkung unten). Außerdem kümmert sich jeder Prozess selbstständig darum, dass er vom PrintJobController neue Druckaufträge erhält. Ebenso liegt auch die Verantwortung für die Jobverarbeitung, d.h. der Aufruf und die Kontrolle des externen VB6 Prozesses, bei ihm. Der PrintServiceController kümmert sich nur darum den PrintJobWorker zu starten und zu stoppen und der PrintJobController dient allein der prozesssicheren Kommunikation mit der Datenbank. Demzufolge sind die Verantwortlichkeiten klar definiert und sauber getrennt, was der Einhaltung des „Single Responsibility Principle“ Prinzips (siehe hier) entspricht. Außerdem ist die Kohäsion meines Erachtens sehr stark ausgeprägt, was man auch an den Interfaces sehen kann (im Prinzip existieren keine Properties). Dementsprechend ist das CCD Prinzip „tell don’t ask“ (siehe hier) auch eingehalten.
Anmerkung: Eine Unschönheit ist hier, dass aus der Architektur nicht hervorgeht geht wie das Beenden realisiert wird. Das liegt daran, dass dies auf Grund der Implementierung von Threads nicht nötig ist (die Task Parallel Library arbeitet mit CancellationToken). Hierauf will ich nicht weiter eingehen, jedoch wäre das durchaus ein Diskussionspunkt, den man erörtern könnte. Auf der anderen Seite kann ich entgegen halten, dass das Stoppen des PrintJobController völlig ausreichend ist. Und dies wiederum spiegelt sich in der Architektur wieder.
Kommentar verfassen