Archiv für den Monat Dezember 2010

XML-Datentyp im SQL Server

In meinem Artikel zu DTOs beschrieben habe, lassen sich Datenobjekte sehr leicht als XML serialisieren. Ich habe das Konzept aufgegriffen und zur Datenhaltung von Konfigurationen verwendet. Die XML Daten speichere ich in einer dedizierten DB-Tabelle (die Tabellenstruktur hierfür werde ich in einem späteren Blogeintrag veröffentlichen). Jetzt musste ich mir überlegen, welchen Datentyp ich für die entsprechende Spalte mit den XML Werten verwende: Nehme ich varchar(max) oder XML.

XML als vollwertigen Datentyp gibt es bereits seit SQL Server 2005. Da wir SQL Server 2008 R2 einsetzen, wäre es also durchaus zu erwägen. Um eine sondierte Entscheidung treffen zu können, habe ich mir ein SQL Server 2008 Buch (nicht R2!) zur Hand genommen. Zunächst einmal einige Vorteile:

  • Constraints &  Indizes können darauf erstellt werden
  • Setzen eines Defaultwertes ist möglich
  • Validierung gegen ein im SQL Server hinterlegtes Schema
  • Ausführung von in SQL-Befehlen eingebettetes XQuery

Hört sich zunächst einmal ganz gut an. Nun aber ein gravierender Nachteil, der zumindest für den SQL Server 2008 gilt (bzgl. R2 kann ich keine Aussage machen, allerdings glaube ich nicht, dass sich etwas geändert hat):

Wegen der binären Speicherung können XML Dokumente nicht zu 100% identisch erhalten werden, sodass etwa Leerzeichen entfernt und die Reihenfolge der Attribute verändert werden.

Nun ja, bei einer Config-Tabelle ist dies vielleicht nicht wesentlich von Bedeutung, aber wenn exakte Kopien eines XML Dokumentes z.B. aus rechtlichen Gründen benötigt werden, dann wäre dies keine Option. Diese Information, auch wenn Sie vielleicht zunächst einmal nicht relevant für meine technischen Anforderungen ist, stieß mir doch ein wenig auf. Hinzu kommt, dass die Vorteile für mich keine zwingende Notwendigkeit sind.

To make a long story short: Ich entschied mich beim Persistieren von Konfigurationsdaten, die als XML vorliegen, für den Datentyp varchar(max).

Advertisements

Legacy Code robust machen – Teil 2

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.

clip_image001[6]

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).

clip_image002[4]

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

clip_image003[4]

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.

Data Transfer Objects verwalten

Wer in seinen Programmen auch mit DTOs (Data Transfer Objects) arbeitet, der steht vor dem Problem sich um deren Persistenz kümmern zu müssen. Der Namensraum System.Xml.Serialization bietet hierfür alles nötige. Und da wir gemäß dem DRY-Prinzip (Don’t Repeat Yourself) nicht für jedes DTO eine entsprechende Persistenzverwaltung schreiben möchten, machen wir das generisch:

VB.NET Code:

Imports System.Xml
Imports System.IO

Public Class ObjectPersistenceStore(Of T)
    “‘ <summary>
    “‘ Stellt den Zustand des Objekts anhand der übergebenen XML Daten wiederher
    “‘ </summary>
    “‘ <param name=“state“>Der Zustand des Objekts in Form eines XML Strings</param>
    “‘ <returns>Eine Instanz des DTOs</returns>
    “‘ <remarks></remarks>
    Public Function LoadObjectState(ByVal state As String) As T
        Dim result As T = Nothing

        Try
            Dim reader As New Serialization.XmlSerializer(GetType(T))
            result = CType(reader.Deserialize(New StringReader(state)), T)
        Catch ex As InvalidOperationException
            Console.WriteLine(„Die XML Daten in der Datei sind fehlerhaft: “ + ex.Message)
            Throw
        Catch ex As FileNotFoundException
            Console.WriteLine(„Datei nicht gefunden: “ + ex.Message)
            Throw
        Catch ex As NotSupportedException
            Console.WriteLine(„Objekt „)
            Throw
        Catch ex As Exception
            Console.WriteLine(ex.Message)
            Throw ex
        End Try

        Return result
    End Function

    “‘ <summary>
    “‘ Liefert den Zustand eines DTO zurück
    “‘ </summary>
    “‘ <param name=“dataTransferObject“>Das zu serialisierende Objekt</param>
    “‘ <returns>Ein XML String</returns>
    “‘ <remarks></remarks>
    Public Function GetObjectState(ByVal dataTransferObject As T) As String
        Dim result = String.Empty

        Try
            Dim xmlSerializer = New Serialization.XmlSerializer(GetType(T))
            Dim stringWriter = New System.IO.StringWriter
            xmlSerializer.Serialize(stringWriter, dataTransferObject)

            result = stringWriter.ToString
        Catch ex As InvalidOperationException
            ‚das Objekt muss einen leeren Konstruktor haben
            Console.WriteLine(„Das Objekt kann nicht serialisiert werden: “ + ex.Message)
            Throw
        Catch ex As NotSupportedException
            ‚Interfaces können nicht serialisiert werden
            Console.WriteLine(„Es wurde kein gültiges Objekt zum Serialisieren übergeben: “ + ex.Message)
            Throw
        Catch ex As Exception
            Console.WriteLine(ex.Message)
            Throw
        End Try

        Return result
    End Function
End Class

 

C# Code:

using System;
using System.IO;

    public class ObjectPersistenceStore<T>
    {
        /// <summary>
        /// Stellt den Zustand des Objekts anhand der übergebenen XML Daten wiederher
        /// </summary>
        /// <param name=“state“>Der Zustand des Objekts in Form eines XML Strings</param>
        /// <returns>Eine Instanz des DTOs</returns>
        /// <remarks></remarks>
        public T LoadObjectState(string state)
        {
            T result = default(T);

            try
            {
                var reader = new System.Xml.Serialization.XmlSerializer(typeof(T));
                result = (T)reader.Deserialize(new StringReader(state));
            }
            catch (InvalidOperationException ex)
            {
                Console.WriteLine(„Die XML Daten in der Datei sind fehlerhaft: “ + ex.Message);
                throw;
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine(„Datei nicht gefunden: “ + ex.Message);
                throw;
            }
            catch (NotSupportedException ex)
            {
                Console.WriteLine(„Objekt „);
                throw;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                throw;
            }

            return result;
        }

        /// <summary>
        /// Liefert den Zustand eines DTO zurück
        /// </summary>
        /// <param name=“dataTransferObject“>Das zu serialisierende Objekt</param>
        /// <returns>Ein XML String</returns>
        /// <remarks></remarks>
        public string GetObjectState(T dataTransferObject)
        {
            var result = string.Empty;

            try
            {
                var xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
                var stringWriter = new System.IO.StringWriter();
                xmlSerializer.Serialize(stringWriter, dataTransferObject);

                result = stringWriter.ToString();
            }
            catch (InvalidOperationException ex)
            {
                //das Objekt muss einen leeren Konstruktor haben
                Console.WriteLine(„Das Objekt kann nicht serialisiert werden: “ + ex.Message);
                throw;
            }
            catch (NotSupportedException ex)
            {
                //Interfaces können nicht serialisiert werden
                Console.WriteLine(„Es wurde kein gültiges Objekt zum Serialisieren übergeben: “ + ex.Message);
                throw;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                throw;
            }

            return result;
        }
    }

 

Bei der Verwendung ist zu beachten, dass Objekte, die deserialisiert werden sollen, einen leeren Konstruktur benötigen. Interfaces lassen sich per se nicht serialisiert.

Wenn man die oben geschilderte Logik z.B. in einem Interfaces deklariert und mit Dependency Injection bei dem Serializer arbeitet, dann kann man für beliebige DTOs verschiedene Persistenz-Manager implementieren (z.B. einen für Daten aus Dateien, einen anderen für Daten aus Datenbanken), die noch dazu unterschiedliche Serialisierungsformate verwenden. Und wenn man das ganze noch einen Schritt weiter treibt, kann man die Logik auch in einer ExtensionMethod kapseln und jedes DTO um diese erweitern.

Die Einsatzmöglichkeiten sind vielfältig, z.B. könnte man die Implementierung für eine Sitzungsverwaltung im eigenen ERP System verwenden. Ich verwende das Konzept für das Konfigurationsmanagement.

Legacy Code robust machen – Teil 1

Wie in vielen Unternehmen existiert auch bei uns noch jede Menge Legacy Code, der an manchen Stellen nicht weiter stört, in anderen Bereichen aber die Geschäftsprozesse erheblich verlangsamt oder gar lahmlegt. Bei uns sind das v.a. die Druckdienste unseres ERP Systems. Vor etlichen Jahren wurden diese in VB6 geschrieben und es fehlt an so manchem essentiellen Feature, wie z.B. einer adäquaten Fehlerbehandlung oder einem Logging-System. Das fällt besonders ins Gewicht, da der Dienst sehr instabil läuft.

Deswegen haben wir uns jetzt intern entschieden genau diesen Störfaktor auszuräumen. Das Problem ist nur, dass zu viel Code und zu viele Abhängigkeiten darin verstrickt sind. Deshalb gehen wir mit einer anderen Methodik heran: Wir beheben das Symptom, aber nicht die Ursache. Konkret bedeutet das die Implementierung eines auf .NET basierenden Windows Dienstes, welcher den alten Legacy Code steuert. Dazu haben wir die reine Anwendungslogik aus dem alten Dienst herausgelöst und sauber in einer EXE-Datei verpackt, welche sich per Kommandozeile aufrufen lässt.

Der neue .NET Dienst ist nun dafür zuständig die Druckjobs zu verwalten, diese an den alten VB6 Prozess zu übergeben und die Verarbeitung zu überwachen. Das folgende EPK Diagramm gibt den groben Ablauf wieder (von oben nach unten lesen…). Es ist nicht ganz vollständig, enthält aber alle wichtigen Informationen.

 

Druckdienst Prozesskette

 

Wie man sieht, setzen wir auf Parallelisierung. Durch das Starten des Service werden mehrere Threads erzeugt, die das Verarbeiten der Jobs übernehmen. Im weiteren Verlauf des EPKs zeige ich den Prozess allerdings nur anhand eines Threads.

Wird ein Thread gestartet, so versucht er sich zunächst einen Druckjob aus der Datenbank zu holen. Wenn er keinen findet, dann legt sich der Thread für eine beliebig definierte Zeit X (die Information kommt aus der Konfiguration) schlafen und beginnt danach erneut.

Wenn er dann einen Job zum Abarbeiten findet, startet er den VB6 Prozess, indem er die ID des gefunden Druckauftrags an diesen übergibt, woraufhin dieser zu arbeiten anfängt. Nun können 3 Ereignisse eintreten:

  • Der Job wird erfolgreich verarbeitet: In diesem Fall muss der .NET Service nur noch den Job in der Datenbank als erledigt kennzeichnen. Danach kann er von vorne beginnen
  • Der Prozess antwortet nicht, d.h. das Programm „hängt“
  • Der Job wurde vom Prozess nicht erfolgreich verarbeitet: In diesem Fall wurde der Prozess korrekt beendet, allerdings war der Job fehlerhaft

Die beiden letzten Ereignisse drücken zwar unterschiedliche Ergebnisse aus, führen jedoch zum selben Resultat: Der Druckjob wurde nicht erfolgreich verarbeitet. In diesem Fall wird geprüft, wie oft schon versucht wurde diesen Job zu drucken. Ist eine definierte Anzahl x (die Information kommt aus der Konfiguration) an Wiederholungen erreicht, so wird der Job als fehlerhaft in der Datenbank markiert, sodass er nicht erneut von einem Thread abgeholt werden kann. Ist das nicht der Fall, so wird der Druck durch den VB6 Prozess ein weiteres Mal angestoßen.

Marktübersicht: Web-Analyse-Tools

Kaum zu glauben, aber wahr: Nachdem wir letzte Woche selbst eine Evaluierung für Web-Analyse-Tools gemacht haben, gibt es jetzt einen brandaktuellen Beitrag von Chip, der eine profunde Übersicht zu diesem Thema liefert. Interessanterweise deckt sich die Auswertung nahezu identisch mit der unseren. Es wurden sogar die gleichen Ressourcen (z.B. eine 30 Seiten lange PDF zum Thema welches Tool Datenschutzkonform ist) dazu einbezogen bzw. aufgeführt. Lediglich ein Kriterienpunkt fehlt, den wir bei uns aufgenommen hatten: Thema Performance bzw. Seitenbelastung durch den zusätzlichen Code. Piwik hatte hier den besten Wert, gefolgt von ETracker mit ca. 3-4x so großem Overhead und als Schlusslicht Google Analytics mit ca. dem 5-fachen von ETracker.

Nach der Evaluierung in unserem Haus haben wir uns übrigens für ETracker, sowie ergänzend dazu Piwik (da sehr geringe Belastung der eigenen Seite und Datenhaltung auf unserem eigenen Server) entschieden. Hier also der Link zu diesem sehr empfehlenswerten Artikel:

http://business.chip.de/artikel/Marktuebersicht-Web-Analyse-Tools_45960223.html

%d Bloggern gefällt das: