Sie sind hier: Weblog

ESB-Lösungen mit Windows Communication Foundation (WCF) entwickeln

Foto ,
26.08.2012 19:45:00

WCF bietet mit dem Routing-Service und Discovery einige Möglichkeiten zur losen Kopplung von Systemen, die sich auch in ESB-Lösungen wiederfinden.
Für das Transformieren von Nachrichten enthält WCF jedoch keine Lösung – die für solch ein Vorhaben benötigten Bausteine jedoch schon. Beispielsweise ist es ein Leichtes, einen Service zu schreiben, der eine Nachricht entgegennimmt und mit den Informationen dieser Nachricht einen weiteren Service aufruft. Soll dieser Service jedoch universell einsetzbar sein, muss jede beliebige Nachricht entgegengenommen, transformiert und an den gewünschten Zielservice weitergeleitet werden können.
Das nächste Listing zeigt einen Service-Vertrag für einen generischen Transformationsservice. Für den Übergabeparameter sowie für den Rückgabewert kommt hier der Typ Message zum Einsatz. Dies erlaubt das Entgegennehmen und Zurückliefern einer beliebigen Nachricht, mit der auf generischer Art hantiert werden kann. Damit diese Methode für sämtliche an den Service gesendete Nachrichten herangezogen wird, beinhaltet das Attribut Action einen Stern, der als Wildcard fungiert. Dasselbe gilt analog für ReplyAction.
[ServiceContract(Namespace = "http://softwarearchitekt.at/samples/Gateway")]
public interface IGateway
{
    [OperationContract(Action = "*", ReplyAction = "*")]
    Message ProcessMessage(Message requestMessage);
}
Zur Implementierung einer generischen Transformationslogik bietet sich XSLT (Extensible Stylesheet Language Transformation, tinyurl.com/buoxh2) an. Für den Einsatz dieser Sprache zum Transformieren von XML-Nachrichten spricht, dass sie zum einen etabliert und vom Prinzip her leicht anzuwenden sowie zum anderen von vielen Produkten unterstützt wird. Allerdings bedingt diese Entscheidung, dass die verarbeiteten Nachrichten in XML vorliegen. Bei SOAP-basierten Web-Services oder bei Services, die sich auf XML und REST abstützen, wird dies auch keine Hürde darstellen. Soll jedoch ein Service, der sich nicht auf XML-basierte Nachrichten abstützt, angebunden werden, muss ein entsprechender Konverter herangezogen werden. Diese Vorgehensweise findet sich auch in vielen kommerziellen ESB-Implementierungen: Intern wird XML verwendet; extern XML oder ein beliebiges anderes Format, das nach XML konvertiert wird.
Das folgende Listing zeigt eine beispielhafte Implementierung eines Transformationsservice, der zum einen XSLT verwendet und zum anderen den zuvor diskutierten Service-Vertrag implementiert. Zur Vereinfachung wurden Konfigurationsinformationen in Konstanten untergebracht. Die gezeigte Methode spiegelt die prinzipielle Struktur der Implementierung wider; die Implementierungsdetails der aufgerufenen Methoden werden weiter unten besprochen.
Zunächst extrahiert die gezeigte Methode den Nachrichtenkörper aus der Nachricht (ExtractBody) und transformiert ihn (Transform). Anschließend kreiert sie mit dem transformierten Nachrichtenkörper unter Berücksichtigung der unterstützten Nachrichtenversion sowie des erwarteten XML-Namespace eine Nachricht für den Zielservice (CreateMessage) und sendet sie an diesen (SendMessage). Die daraufhin erhaltene Antwortnachricht transformiert sie auf dieselbe Weise für den Quellservice. Hier wird allerdings zusätzlich geprüft, ob es sich bei der Antwortnachricht um einen SOAP-Fault handelt (CheckFault), der zur Vereinfachung ohne Transformation zurückgeliefert wird.
private readonly string REQUEST_XSLT = "A-to-B.xslt";
private readonly string RESPONSE_XSLT = "AResponse-to-BResponse.xslt";

private readonly string DESTINATION_NAMESPACE = "softwarearchitekt.at/samples/B/OrderService/IOrderService/SendOrder";
private readonly MessageVersion DESTINATION_MSG_VERSION = MessageVersion.Soap11;
private readonly string DESTINATION_ENDPOINT_NAME = "OrderServiceB_ClientEndpoint";

private readonly string SOURCE_NAMESPACE = "softwarearchitekt.at/samples/A/OrderService";
private readonly MessageVersion SOURCE_MSG_VERSION = MessageVersion.Soap11;


public Message ProcessMessage(Message request)
{
    MemoryStream buffer;

    Message transformedRequest;
    Message response;
    Message transformedResponse;

    bool isFault;

    buffer = ExtractBody(request);
    buffer = Transform(buffer, REQUEST_XSLT);
    transformedRequest = BuildMessage(buffer, 
                             DESTINATION_MSG_VERSION, 
                             DESTINATION_NAMESPACE);
    response = SendMessage(transformedRequest, 
                             DESTINATION_ENDPOINT, 
                             DESTINATION_BINDING);
    buffer = ExtractBody(response);
    isFault = CheckFault(buffer);
    if (isFault) return BuildMessage(buffer, 
                                      SOURCE_MSG_VERSION, 
                                      SOURCE_NAMESPACE);
    buffer = Transform(buffer, RESPONSE_XSLT);
    transformedResponse = BuildMessage(buffer, 
                              SOURCE_MSG_VERSION, 
                              SOURCE_NAMESPACE);
    return transformedResponse;
}
Nun sollen die von der Service-Implementierung verwendeten Methoden genauer betrachtet werden. Die Methode ExtractBody erstellt einen MemoryStream sowie einen sich darauf abstützenden XmlDictionaryWriter. Über diese Instanz schreibt sie die übergebene Nachricht in den MemoryStream. Anschließend setzt die Position des Streams auf 0, sodass dessen Inhalt in weiterer Folge gelesen und zurückgeliefert werden kann.
private MemoryStream ExtractBody(Message requestMessage)
{
    MemoryStream sourceBuffer = new MemoryStream();
    using (XmlDictionaryWriter w = XmlDictionaryWriter.CreateDictionaryWriter(XmlWriter.Create(sourceBuffer)))
    {
        requestMessage.WriteBodyContents(w);
    }
    sourceBuffer.Position = 0;
    return sourceBuffer;
}
Die Methode Transform nimmt eine Nachricht in Form eines MemoryStream sowie den Namen der zu verwendenden XSLT-Datei entgegen. Sie ummantelt die Nachricht zur Transformation mit einem XPathDocument und lädt die XSLT-Datei mit einer Instanz von XslCompiledTransform. Die Transformation erfolgt anschließend mit der Methode Transform dieser Instanz. Das Ergebnis schreibt sie in den MemoryStream mit dem Namen resultBuffer, der danach, wie weiter oben beschrieben, auf die Position 0 gesetzt und zurückgegeben wird.
private static MemoryStream Transform(MemoryStream sourceBuffer, string templateFileName)
{
    XPathDocument xpdoc = new XPathDocument(sourceBuffer);

    string xsltPath = System.Web.Hosting.HostingEnvironment.MapPath("~/xslt/" + templateFileName);

    XslCompiledTransform transform = new XslCompiledTransform();
    transform.Load(xsltPath);

    MemoryStream resultBuffer = new MemoryStream();
    transform.Transform(xpdoc, new XsltArgumentList(), resultBuffer);

    resultBuffer.Position = 0;
    return resultBuffer;
}
Die Methode BuildMessage erzeugt zum Zugriff auf den Nachrichtenkörper im übergebenen MemoryStream einen XmlReader und übergibt diesen zusammen mit der gewünschten Nachrichtenversion und dem gewünschten Namespace an die Methode Message.CreateBody, die eine neue Nachricht kreiert. Anschließend erzeugt sie mit CreateBufferedCopy eine Kopie dieser Nachricht. Dies ist notwendig, damit der Nachrichtenkörper an Ort und Stelle aus dem XmlReader gelesen wird. Diese Kopie liefert sie zurück.
private Message BuildMessage(MemoryStream resultBuffer, MessageVersion msg_version, string destination_namespace)
{
    Message m;
    using (XmlReader r = XmlReader.Create(resultBuffer))
    {
        var temp = Message.CreateMessage(msg_version, destination_namespace, r);
        m = temp.CreateBufferedCopy(int.MaxValue).CreateMessage();
    }
    return m;
}
Die Methode SendMessage nimmt die zu sendende Nachricht zusammen mit dem in der Konfiguration hinterlegten Client-Endpoint-Name des Zielservice entgegen. Mit diesem Namen erzeugt sie über eine ChannelFactory einen Proxy. Dieser wird zum Versenden der Nachricht verwendet.
Als Service-Vertrag wird hierbei der Typ des Transformationsservice und nicht jener des Zielservice verwendet. Der Grund hierfür ist, dass es sich bei der zu versendenden Nachricht um eine beliebige, nicht näher bekannte Nachricht handelt und somit ein generischer Service-Vertrag, der in Form des Vertrages für den Transformationsservice bereits vorliegt, zu verwenden ist. Alternativ dazu könnte auch ein anderer Service-Vertrag, dessen Aufbau dem des Transformationsservice gleicht, herangezogen werden.
private Message SendMessage(Message m, string destinationEndpointName)
{
    using (ChannelFactory<IGateway> f = new ChannelFactory<IGateway>( destinationEndpointName))
    {
        var proxy = f.CreateChannel();
        return proxy.ProcessMessage(m);
    }
}
Die Methode CheckFault prüft auf eine sehr vereinfachte Art, ob es sich bei der übergebenen Nachricht um einen SOAP-Fault handelt. Dazu wird mit einem XPath-Ausdruck geprüft, ob die Nachricht einen entsprechenden XML-Knoten beinhaltet.
private static bool CheckFault(MemoryStream buffer)
{
    bool isFault;
    XPathDocument doc = new XPathDocument(XmlReader.Create(buffer));
    var nav = doc.CreateNavigator();
    XmlNamespaceManager nsmgr = new XmlNamespaceManager(nav.NameTable);
    nsmgr.AddNamespace("s", "http://schemas.xmlsoap.org/soap/envelope/");
    var expr = nav.Compile("//s:Fault");
    expr.SetContext(nsmgr);

    isFault = (nav.Select(expr).Count > 0);
    buffer.Position = 0;
    return isFault;
}