EGL-Services und Libraries sind beide EGL-Parts, die entsprechend ILE-Modulen und Service-Programmen ein Bündel von Funktionen für einen Aufruf aus anderen Parts bereitstellen. Im Unterschied zu einer Library wird bei einem Service eine Funktion aus einer Library nicht direkt, sondern über ein Proxy-Objekt aufgerufen, für das man den Aufruf konfiguriert.

Dies bewirkt, dass sich der Aufruf eines lokalen Service von dem eines Web Service nur durch wenige Details in der Konfiguration unterscheidet. Ein Service lässt sich aber auch ganz einfach als Web Service bereitstellen, so dass man insgesamt über die EGL Services einen außerordentlich einfachen und potenten Zugang zur einer Service-orientierten Architektur bekommt. Dieser Artikel behandelt die Grundlagen für die Definition von Services und ihre Konfiguration zur Nutzung in einem Client sowohl konzeptionell als auch praktisch am "lebendigen" Beispiel.

Auch diesmal werden wir zuerst die Lösung zur Übung des vorherigen Artikels besprechen. Mit dieser werden Funktionen derjenigen Libraries weiterentwickelt, die vom Konzept her prädestiniert sind, in Services migriert zu werden.

Übung des vorherigen Artikels

Die Library math.Verteilung soll um zwei Funktionen ergänzt werden, die beide die vorhandenen Funktionen mittelWert() und standardAbweichung() um eine Kontrolle aller im Array übergebenen Werte auf >= 0.0 erweitern. Beide Funktionen heißen genauso wie die vorhandenen, haben jedoch einen zusätzlichen Input-Parameter namens allesPositiv vom Typ boolean, der im Fall von true die Kontrolle durchführen soll.

Die Funktionen sollen dann beim ersten Entdecken eines negativen Werts eine Ausnahme UngültigeDatenException werfen, die in einem Paket exceptions des Projekts EGLAllgemein als Exception-Record zu definieren ist. Ebenso soll die Ausnahme ein Feld fehlerInfo vom Typ string besitzen, das eine Information über den zum Abbruch führenden Wert enthält, sowie die ID "TKL0001" und die Nachricht "Abbruch wegen ungültiger Daten: ".

Beim Codieren der beiden Funktionen soll möglichst wenig redundanter Code entstehen.

Die beiden neuen Funktionen sollen im Programm ZahlungsAuswertung aufgerufen werden, jeweils mit und ohne Kontrolle, wobei die Ausnahme abgefangen und ggf. eine Fehlermeldung auf die Konsole ausgegeben werden soll.

Lösungshinweise:

  1. Zuerst neues Paket exceptions anlegen
  2. Im neuen Paket neue Quelle anlegen mit dem Namen des Ausnahme-Record
  3. Record editieren
    • rec eingeben und die Record-Definition durch die Inhaltshilfe vervollständigen lassen
    • Den generierten Record und die schon generierte Variable umbenennen
  4. Quelle sichern und generieren
  5. Library Verteilung editieren 
    • Beide Funktionen kopieren und bei den Duplikaten den zusätzlichen Parameter einfügen
    • In der neuen Funktion mittelWert() die folgende Abfrage in der Schleife einfügen:
      if (positiv && werte[i] < 0.0)
          ausnahme UngültigeDatenException = new UngültigeDatenException;
          ausnahme.messageID = "TKL0001";
          ausnahme.message = "Abbruch wegen ungültiger Daten: ";
          ausnahme.fehlerInfo = i ::
              ". Wert des Array hat unzulässigen Wert ::  werte[i];
          throw ausnahme;
       end
    • Import realisieren (z. B. durch das Verwalten der Importe über das Kontextmenü der Quelle)
    • In der neuen Funktion standardAbweichung() die Funktion mittelWert() mit zwei Argumenten aufrufen
    • In den alten Funktionen statt der ursprünglichen Anweisungen die neuen Funktionen mit false als zweites Argument aufrufen
  6. Einfügen von Aufrufen der neuen Funktionen ins Programm ZahlungsAuswertung
  7. Alles sichern und generieren
  8. Falls keine Ausnahme geworfen wird, in Fristen.zahlungsDauer() ein Zahlungsdatum vor das entsprechende Zahlungsziel legen

Nach der Durchführung der oben beschriebenen Aufgaben sehen die beiden bereits vorhandenen Parts und der neu entstehende Record Part etwa so aus, wie es in den Abbildungen 1 bis 3 zu sehen ist. Die Quellen sind diesmal als Text dargestellt. Damit können Sie Ihr selbst entwickeltes Projekt synchronisieren.

package exceptions;
record UngültigeDatenException type Exception
            fehlerInfo string;
end

Abbildung 1: Der Record Part UngültigeDatenException für die eigene Exception

package math;
import exceptions.UngültigeDatenException;
library Verteilung type BasicLibrary{}
    function mittelWert(werte float[] in) returns(float)
        return(mittelWert(werte, false));
    end
    function standardAbweichung(werte float[] in) returns(float)
        return(standardAbweichung(werte, false));
    end
    function mittelWert(werte float[] in, allesPositiv boolean in)
                                    returns(float)
        mittel float = 0.0;
        for(i int from 1 to werte.getSize())
            if(allesPositiv && werte[i] < 0.0)
                ausnahme UngültigeDatenException =
                                       new UngültigeDatenException;
                ausnahme.messageID = "TKL0001";
                ausnahme.message = "Abbruch wegen ungültiger Daten: ";
                ausnahme.fehlerInfo = i ::
                     ". Wert des Array hat unzulässigen Wert " :: werte[i];
                throw ausnahme;
            end
            mittel = mittel + werte[i];
        end
        return(mittel / werte.getSize());
    end
    function standardAbweichung(werte float[] in, allesPositiv boolean in)
                                    returns(float)
        abweichung float = 0.0;
        mittel float = mittelWert(werte, allesPositiv);
        for(i int from 1 to werte.getSize())
            abweichung +=(werte[i] – mittel) ** 2;
        end
        return((abweichung / werte.getSize()) ** 0.5);
    end

end

Abbildung 2: Die Library Verteilung mit zusätzlichen Funktionen

package eglkunden;
import eglkunden.logik.Fristen;
import exceptions.UngültigeDatenException;
import math.Verteilung;
program ZahlungsAuswertung type BasicProgram{}
    function main()
        tageArrayInt int[] = Fristen.zahlungsDauer("");
        tageArrayFloat float[0];
        for(i int from 1 to tageArrayInt.getSize())
            tageArrayFloat.appendElement(tageArrayInt[i]);
        end
        writeStdout("Mittelwert: " + Verteilung.mittelWert(tageArrayFloat));
        writeStdout("Standardabweichung: " ::
                        Verteilung.standardAbweichung(tageArrayFloat));
        writeStdout("Mittelwert: " + Verteilung.mittelWert(tageArrayFloat,
                                false));
        writeStdout("Standardabweichung: " ::
                   Verteilung.standardAbweichung(tageArrayFloat, false));
        try
            writeStdout("Mittelwert: " ::
                   Verteilung.mittelWert(tageArrayFloat, true));
        onException(exception UngültigeDatenException)
            SysLib.writeStdout(exception.messageID + " " +
                                    exception.message + exception.fehlerInfo);
        onException(exception AnyException)
            throw exception;
        end
        try
            writeStdout("Standardabweichung: " ::
                    Verteilung.standardAbweichung(tageArrayFloat, true));
        onException(exception UngültigeDatenException)
            SysLib.writeStdout(exception.messageID + " " +
                                    exception.message + exception.fehlerInfo);
        onException(exception AnyException)
            throw exception;
        end
    end           
end

Abbildung 3: Das Programm ZahlungsAuswertung

Die folgenden Bemerkungen sollen noch einmal auf markante Punkte hinweisen:

  • Das Programm ZahlungsAuswertung wird im Projekt EGLKunden entwickelt, die beiden anderen EGL Parts im Projekt EGLAllgemein. Damit Letztere zur Entwicklungszeit gefunden werden können, muss das Projekt EGLAllgemein in den EGL-Erstellungspfad und in den Java-Erstellungspfad des Projekts EGLKunden aufgenommen werden.
  • Beachten Sie, dass der Exception-Record nur das Feld fehlerInfo explizit deklariert und dass die beiden Felder messageID und message durch den Stereotyp Exception implizit deklariert sind. Sie können genauso wie fehlerInfo benutzt werden (Abbildungen 1 und 2).
  • In der erweiterten Funktion mittelWert() (Abbildung 2) wird in if eine komplexe logische Bedingung mit „logischem und" benutzt, die als ersten Operanden eine Variable vom Typ boolean und als zweiten Operanden einen Gleitkommavergleich enthält.
  • Im Anweisungsblock des if werden die Felder des Exception-Record gefüllt. Am Ende wird die Ausnahme UngültigeDatenException geworfen, so dass beim Durchlaufen dieses Blocks mit dem throw die Funktion mittelWert() sofort beendet wird. Aus diesem Grund hat die Rückgabevariable einen undefinierten Wert und wird nicht zurückgegeben. Das sollte jedoch nicht stören, denn mit dieser Tatsache wird durch die geworfene Ausnahme in der aufrufenden Funktion bei deren Fangen sowieso bewusst umgegangen. Oder es wird die Ausnahme weiter propagiert – in diesem Fall spielt der Rückgabewert ohnehin keine Rolle mehr.
  • Die erweiterte Funktion standardAbweichung() beginnt mit der erweiterten Funktion mittelWert(), in der die Kontrolle auf positive Werte durchgeführt wird. Dadurch kommt bei einem negativen Wert eine Ausnahme aus mittelWert(); diese wird, da nicht gefangen, propagiert.
  • Die beiden ursprünglichen Funktionen mittelWert() und standardAbweichung() können zur Vermeidung redundanter Codes durch Aufruf der entsprechenden erweiterten Funktionen mit dem zweiten Argument false realisiert werden.
  • Beim Aufruf der Funktionen in ZahlungsAuswertung (Abbildung 3) werden zuerst die beiden ursprünglichen Funktionen aufgerufen, die auch bei einem negativen Wert im übergebenen Array ihren Dienst versehen. Dies tun auch die danach aufgerufenen erweiterten Funktionen durch das zweite Argument false. Dieses bestimmt dann, dass gemäß Signatur der Parameterliste die gleichnamige erweiterte Funktion aufgerufen wird.
  • Mit dem zweiten Argument true wird jeweils die erweiterte Funktion mit Kontrolle auf positive Werte aufgerufen. Beide Aufrufe finden in extra try-Konstrukten statt, da ansonsten der zweite Aufruf durch ein Suspendieren der Befehlsfolge im try nicht mehr aufgerufen würde. Im Beispiel ist nach Ende jedes der beiden try-Konstrukte alles in Ordnung, da die Ausnahmen als behandelt gelten (es wird ja keine Ausnahme mehr geworfen oder propagiert). Die Anwendung wird daher wie üblich beendet (Abbildung 4).

Abbildung 4: Konsolausgabe des obigen Tests

EGL Services

Services sind EGL Parts, die wie Libraries aus Funktionen und globalen Variablen bestehen und deren Funktionen sehr ähnlich aufgerufen werden. Während beim Aufruf der Funktionen einer Library das Aufrufziel schon zur Programmierzeit "fest verdrahtet" wird, wird für den Aufruf eines Service ein Objekt mit gleicher Aufrufschnittstelle definiert, das den eigentlichen Service kapselt. Der Funktionsaufruf wird dann von diesem Objekt, auch als Proxy oder Stellvertreter bezeichnet, an den Service weitergeleitet.

Dieser Proxy wird im Deployment-Deskriptor, der dem Client zugeordnet ist, über eine Namenszuordnung konfiguriert (Abbildung 5). Je nach Konfiguration können auf diese Weise funktionsgleiche Services wahlweise aus unterschiedlichen Technologien aufgerufen werden. So kann auch ein Web Service aufgerufen werden, was die Tür zu modernen Technologien und Anwendungsarchitekturen öffnet, insbesondere zur Service-orientierten Architektur (SOA).

Abbildung 5: Konfiguration eines Service-Client-Binding im Deployment-Deskriptor

Abbildung 6: Aufruf einer Funktion aus einer Library vs. aus einem Service

Für die Flexibilität eines solchen Aufrufs sollte man jedoch den Proxy nicht wie in Abbildung 6 vom Typ des Service selbst, sondern vom Typ eines entsprechenden Interface definieren, das vom Service implementiert wird. Dies wird u. a. Gegenstand des nächsten Artikels sein.

Je nach Konfiguration des Aufrufs des Service kann der Service im einfachsten Falle lokal (Abbildung 6) vorhanden sein, wobei ein Java-internes Aufrufprotokoll zum Tragen kommt, das im Prinzip genauso schnell arbeitet wie der Funktionsaufruf aus der Library.

Service oder Library?

Libraries und Services sind einander sehr ähnlich und daher in gewissen Grenzen austauschbar. Jedoch der Einsatz und die enthaltenen Funktionen gebieten aus inhaltlichen oder auch technologischen Gründen, sich für das eine oder für das andere zu entscheiden. Libraries beispielsweise sind etwas einfacher zu handhaben, so dass für Quick-and-Dirty-Lösungen Libraries den Vorrang haben. Auch eine enge inhaltliche Verflechtung mit dem aufrufenden Code spricht für Libraries. Dies ist z. B. für Inhalte, die bei entsprechenden ILE-Modulen in ein Programmobjekt eingebunden würden, und auch für Laufzeitbibliotheken der Fall. Und auch die in den genannten Fällen häufig höheren Anforderungen an die Performance sprechen für die Libraries.

Services werden dagegen dort am sinnvollsten eingesetzt, wo Teile von Software lose miteinander gekoppelt werden, d. h., wo eine Anwendung entflochten werden soll oder gewisse allgemein verwendbare, aber nicht "überall" verwendete Software-Bausteine eingebunden werden sollen. Dabei werden die Funktionen des Aufruf-Frontends in Services zusammengefasst, während eventuell weitere, von diesen aufgerufene mehr im "Hinterland" eines größeren Bausteins arbeitende Funktionen wieder mehr für eine Library geeignet sind.

Bei einer Service-orientierten Anwendungsarchitektur mit Web Services ist die Präferenz eindeutig. Sowohl auf Server- als auch auf Client-Seite ist der Einsatz von Services zwingend, wenn man die enormen Vorteile der Sprache und der Technologie EGL nutzen will. Und natürlich auch die lose Kopplung als eine der Grundeigenschaften einer SOA spricht für die Services, sogar in ihrer "losesten" Form, den Web Services.

Funktionalität des Projekts EGLAllgemein jetzt in Services

Nach den o. g. Kriterien sind die "unabhängigen", allgemeinen und gelegentlich einzusetzenden Anwendungsbausteine Verteilung und Zufall des Projekts EGLAllgemein in der Gestalt von Libraries Kandidaten zur Migration in Services.

Obwohl wir mit den beiden Projekten EGLAllgemein und EGLKunden weiterarbeiten könnten, empfehle ich, das mit Kopien dieser beiden Projekte unter den Namen EGLAllgemeinServices und EGLKundenServices zu machen.

Für die Anfertigung der Kopien gehen Sie folgendermaßen vor:

  • Anfertigung der Kopien der Projekte jeweils über das Kontextmenü der Projekte, dabei Benennung in EGLAllgemeinServices und EGLKundenServices
  • Manuelle Korrektur des EGL und des Java-Erstellungspfads in den Eigenschaften des Projekts EGLKundenServices (Projekt EGLAllgemein ist einziges Projekt der beiden Erstellungspfade)
  • Umbenennung oder Neuerstellung (z. B. durch Kopieren) der Erstellungs-Deskriptoren *.eglbld und der Deployment-Deskriptoren *.egldd (beide sind, abgesehen von der Extension, namensgleich mit dem jeweiligen Projekt)
  • Korrektur der Eigenschaften deploymentDescriptor und genProject in *.eglbld (Verweise in demselben Projekt)
  • Definition der Build-Deskriptoren als Standard-Build-Deskriptor in den Eigenschaften der neuen Projekte und dort jeweils für das Zielsystem und die Debug-Umgebung (Kontextmenü auf dem Projekt à Eigenschaften à EGL-Standard-Build-Deskriptor à …)
  • Schließen der beiden alten Projekte, um diese nicht versehentlich zu ändern
  • Löschen aller Laufzeitkonfigurationen für Ausführung und Debugging (über Ausführen als à Ausführen …)

Die Handarbeit ist deswegen notwendig, weil das Refactoring beim Kopieren die Namen nicht mit übernimmt bzw. korrigiert. Wenn Sie die bisherigen Programme wieder zum Laufen bringen, ist alles gut. Ansonsten löschen Sie die neuen Projekte und arbeiten mit den alten weiter. Sie haben auf diese Weise trotzdem einige wichtige Zusammenhänge kennengelernt, die beim Verstehen der folgenden Inhalte helfen werden.

Übung zum Artikel

Die praktische Durchführung der Migration der Services wird nun im Rahmen einer Übung durchgeführt, deren Ergebniscode im nächsten Artikel veröffentlicht wird.

  1. Wir stellen für den neuen Service VerteilungService eine Kopie der Library Verteilung her und ändern die Library-Definition um in die Service-Definition
    service VerteilungService.
    Alternativ könnte auch der Service per Wizzard neu erstellt und der Code für die Funktionen kopiert werden.
  2. Der Service wird jetzt in EGLKundenServices.egldd als Service für Clients eingebunden:
    • Öffnen des Deployment-Deskriptors mit seinem spezifischen Editor
    • Registrierkarte Service-Client-Bindings nach oben bringen
    • Hinzufügen
    • EGL Binding auswählen à Weiter
    • EGL-Binding-Name über Durchsuchen… à den Service VerteilungService auswählen: den generierten Namen VerteilungService so lassen
    • Protokolltyp Lokal
    • Fertigstellen à Sichern à Schließen
    • Projekt EGLKundenServices generieren (am besten vorsorglich beide Projekte)
    • Am Ende sollte das Service-Client-Binding etwa so aussehen wie in Abbildung 6, wo sich ggf. auch noch abweichende Angaben korrigieren lassen.
  3. Als Nächstes muss ein Proxy für den Service im Client Part, in dem Funktionen des Service aufgerufen werden sollen (das ist das Programm ZahlungsAuswertung), definiert werden. Diese Variable vom Typ des Service wird gleich mitsamt den Metadaten für ein spätes Binden zur Ausführungszeit definiert. Als globale Variable auf Programmebene wird definiert
                proxyVerteilungService VerteilungService
                            {@BindService {bindingKey = "VerteilungService"}};
    Dabei wird mit dem Property-Operator "@" die komplexe Property BindService mit der Sub-Property bindingKey belegt.
    Jetzt bräuchten wir nur noch statt der Library-Funktionen die Funktionen des Proxy-Objekts aufzurufen. Möglicherweise werden infolge eines noch bestehenden Bugs die gleichen Namen der Funktionen durch das Überladen moniert, obwohl die Inhaltshilfe damit keine Probleme hat. Deshalb benennen wir einfach die beiden Funktionen mit den zwei Parametern um in mittelWert2() und standardAbweichung2().
  4. Im Ergebnis der vorherigen Schritte ist jetzt alles codiert und konfiguriert. Es müssen noch erledigt werden:
    • Sichern aller geänderten Quellen
    • Generieren des Projekts EGLKundenServices
  5. Abschließend testen wir das Programm ZahlungsAuswertung durch Ausführen der zugeordneten generierten Java-Klasse als Java-Anwendung. 
  6. Ersetzen Sie auch im Projekt EGLAllgemein die Library Zufall durch einen lokalen Service ZufallService!
    • Rufen Sie im Programm ZufallTest die Funktionen aus dem Service auf!
    • Überlegen Sie, in welchem Deskriptor Sie das Service-Client-Binding konfigurieren müssen!
  7. Testen Sie mit der Anwendung ZufallTest!

Ausblick auf den nächsten Artikel

Im nächsten Artikel wird der Aufruf des Service durch Arbeit mit EGL Interfaces flexibler gestaltet. Mit einem aus einer WDSL-Datei generierten Interface wird ganz einfach ein Web Service aufgerufen. Mithilfe einer Implementierung des generierten Interface kann man dann mit nur einem "Federstrich" im Deployment-Deskriptor zwischen dem Aufruf des Web Service und dem entsprechenden lokalen Service umschalten.