App-Kommunikation ohne Installationsabhängigkeit

1              Themenübersicht

Apps werden häufig unabhängig voneinander entwickelt und gewartet. Systemseitig werden sie als separate Softwarebestandteile behandelt. Apps, die nicht voneinander abhängig sind, können auch unabhängig installiert und aktualisiert werden.

Fachlich kann es jedoch sinnvoll sein, dass eine Kommunikation zwischen Apps stattfindet: beispielsweise durch den Zugriff auf die Daten einer anderen App oder die Benachrichtigung anderer Apps bei bestimmten Ereignissen in der Programmlogik einer App. Diese Kommunikation wird meist durch Hook Contracts, Objektsichten und Stable-Klassen realisiert und führt zu einer Installationsabhängigkeit zwischen den beteiligten Apps.

In diesem Dokument werden Schnittstellentechnologien beschrieben, durch die Apps miteinander kommunizieren können, ohne dass dadurch eine Installationsabhängigkeit entsteht. Die Anwendungsfälle für solche Schnittstellen werden ebenfalls beschrieben.

2              Zielgruppe

App-Programmierer

3              Begriffsbestimmung

Client Interface

Ein Client Interfaces ist eine Schnittstelle, die von einer App bereitgestellt wird und von anderen Apps aufgerufen werden kann. Ein Anwendungsfall ist die Abfrage persistenter Daten.

Provider-App

Eine App, die eine Schnittstelle bereitstellt, wird als „Provider-App“ bezeichnet.

User-App

Eine App, die eine Schnittstelle nutzt, wird als „User-App“ bezeichnet.

4              Beschreibung

4.1        Hinweise zur Verwendung

Für die Kommunikation zwischen Apps sollten nur dann die in diesem Dokument beschriebenen Schnittstellentechnologien verwendet werden, wenn tatsächlich keine Installationsabhängigkeit gewünscht ist. Wenn anderweitig bereits eine Installationsabhängigkeit besteht, sollten stattdessen Stable-Klassen, Hook Contracts (mit Abhängigkeit) und Objektsichten genutzt werden.

Hinweis:

Aufgrund der fehlenden Installationsabhängigkeit wird das einseitige Aktualisieren fachlich verbundener Apps nicht verhindert. Eine App A, die für eine funktionierende Kommunikation auch eine Aktualisierung der App B bräuchte, kann also aktualisiert werden, während App B aus anderen Gründen nicht aktualisiert werden kann oder soll. Dies ist ein Problem, wenn die Kommunikation zwischen A und B vor der Aktualisierung verwendet wurde. Dieses Problem muss für den konkreten Fall beurteilt werden.

Die mit den beschriebenen Technologien bereitgestellten Schnittstellen müssen kompatibel gehalten werden, nachdem die Schnittstellen einmal ausgeliefert wurden. Da keine Abhängigkeit geprüft wird, können Verwender im System vorhanden sein, die gegen eine beliebige bisher ausgelieferte Version der Schnittstelle entwickelt wurden.

Mithilfe der in diesem Dokument beschriebenen Schnittstellentechnologien ist eine Kommunikation zwischen Apps auch dann möglich, wenn diese nicht in demselben App-Entwicklungssystem entwickelt werden. Die Kommunikation kann in diesem Fall jedoch auf einem App-Entwicklungssystem nicht getestet werden.

4.2        Client Interfaces ohne Installationsabhängigkeit

Ein Client Interface ist eine Schnittstelle der Provider-App, an der die User-App Methoden aufrufen kann. Die Installationsabhängigkeit wird vermieden, indem Methodenaufrufe über Java-Proxys stattfinden.

Die folgende Grafik zeigt die Bestandteile eines Client Interface ohne Installationsabhängigkeit. Die Provider-App stellt die Schnittstelle (Interface) zur Verfügung, in der die verfügbaren Methoden definiert werden, und sie enthält die Implementierung dieser Schnittstelle (Implementation). Die User-App enthält eine Kopie dieser Schnittstelle in ihrem eigenen Namensraum. Im Code der User-App (Klasse „Caller“ in der Grafik) werden die Methoden über einen Java-Proxy im „Implementation“-Objekt aufgerufen.

Client-Interface ohne Installationsabhängigkeit

Die durch den Java-Proxy realisierte Koppelung von kopierter Schnittstelle in der User-App und der Implementierung in der Provider-App wird im Folgenden „Schnittstellenbindung“ genannt.

Das Interface in der Provider-App dient dem Programmierer der User-App zur Dokumentation und als Kopiervorlage.

Die Annotation @InvocationTarget am Interface gibt die Implementierungsklasse an, damit das Ziel der Methodenaufrufe bekannt ist. Die Angabe erfolgt als String, damit die Annotation in der kopierten Schnittstelle unverändert übernommen werden kann. Nur diese Kopie ist für den Methodenaufruf erforderlich.

Beispiel:

Provider-App B, Interface:

package com.provider.ext.app.b.template;

import com.cisag.pgm.appserver.InvocationTarget;

 

@InvocationTarget(“com.provider.ext.app.b.log.Implementation”)

interface Interface {

String method(…);

}

Provider-App B, Implementierung:

package com.provider.ext.app.b.log;

 

class Implementation implements com.provider.ext.app.b.template.Interface {

String method(…) { … }

}

User-App A, Interface:

Die Schnittstelle wird in die User-App kopiert, wobei lediglich das Package ausgetauscht werden muss:

package com.user.ext.app.a.log;

import com.cisag.pgm.appserver.InvocationTarget;

 

@InvocationTarget(“com.provider.ext.app.b.log.Implementation”)

interface Interface {

String method(…);

}

Zwischen dem Interface in der User-App und der Implementierung in der Provider-App besteht eine Schnittstellenbindung. Um Methoden aufrufen zu können, erzeugt die User-App über eine Hilfsmethode in PGM ein Implementierungsobjekt und ein Java-Proxy-Objekt, das die Schnittstelle „Interface“ repräsentiert. Auf diesem Java-Proxy-Objekt kann die User-App anschließend Methoden aufrufen, wobei die Aufrufe durch den Java-Proxy an das Implementierungsobjekt weitergeleitet werden.

Wenn die Provider-App nicht installiert ist, dann wird „null“ statt des Proxy-Objekts zurückgegeben.

Beispiel eines Methodenaufrufs:

import com.user.ext.app.a.log.Interface;

:

CisProxyManager xm= CisEnvironment.getInstance().getProxyManager();

Interface intf = xm.createImplementation(Interface.class);

If (intf != null) {

String s = intf.method();

//…

}

Beim Aufruf einer Methode wird die Message-Queue nicht isoliert. Dies entspricht dem Verhalten beim Aufruf von Stable-Methoden, ist aber anders als bei Hook Contracts.

Die Schnittstelle sollte direkt in der Provider-App mithilfe von JavaDoc dokumentiert werden. Dadurch wird die Dokumentation beim Kopieren der Schnittstelle übernommen und steht im Code der User-App zur Verfügung.

4.2.1    Anforderungen an die Schnittstelle

In der Provider-App sollte die Schnittstelle in einem Unternamensraum „template“ neben einem „log“-Namensraum erfasst werden. Durch die die Annotation @InvocationTarget kann die Schnittstelle als Client-Interface ohne Installationsabhängigkeit genutzt werden. In der User-App kann die Schnittstelle in einem beliebigen Namensraum erfasst werden, auch als Inner-Interface.

Die Implementierungsklasse darf keine Inner-Class sein. Objekte dieser Klasse werden durch den CisProxyManager mit CisFactory.createInstance() erzeugt.

Nur die Methoden, die im Java-Interface vorhanden sind, können über den Proxy aufgerufen werden. Die Verwendung evtl. geerbter Methoden führt zu einer RuntimeException. Eine Ausnahme bilden die geerbten Methoden hashCode() und toString(). Sie können aufgerufen werden, wenn sie nicht explizit im Java-Interface überschrieben werden.

4.2.2    Parameter und Rückgabewerte

Die Methoden in der Schnittstelle können mit Parametern aufgerufen werden und Werte zurückliefern. Während primitive Typen (short, int, char usw.) dabei uneingeschränkt genutzt werden können, bestehen für einige Objekttypen die im Folgenden beschriebenen Einschränkungen.

4.2.2.1      Objektsichten

Objektsichten sind Interfaces, die nicht 1:1 kopiert werden können. Sie können daher nicht als Parameter für die Schnittstellenbindung verwendet werden.

4.2.2.2      Schnittstellenbindung

Interfaces, für die eine Schnittstellenbindung vorgesehen ist, können nicht nur als Client-Interface, sondern auch als Methodenparameter verwendet werden. Die Methodenparameter müssen dafür in der User-App mit createImplementation(.) erzeugt werden. Zur Laufzeit werden die übergebenen Proxy-Objekte durch die entsprechenden Implementierungsinstanzen ersetzt. Die Provider-App bekommt keinen Proxy übergeben, sondern die Implementierung aus ihrem Modul, die direkt das Interface aus der Provider-App implementiert.

Analog können Interfaces mit Schnittstellenbindung als Rückgabe geliefert werden. In diesem Fall liefert die Methode der Provider-App eine Implementierungsinstanz. Diese wird für die User-App mit einer Proxy-Instanz versehen, die das Interface der User-App realisiert.

Interfaces, deren Schnittstellenbindung zur Laufzeit aufgelöst werden soll, müssen von der User-App beim Anlegen eines Implementierungs-Objekts explizit durch createImplementation(.) registriert werden:

CisProxyManager.createImplementation( Class<?> intf, Class<?>… additionalInterfaces)

Nicht registrierte Interfaces mit Schnittstellenbindung werden wie reguläre Objekte behandelt und direkt übergeben, siehe Abschnitt „Reguläre Objekte“.

Interfaces können durch den Aufruf der folgenden Methode auch nachträglich für ein Proxy-Objekt registriert werden:

CisProxyManager.registerAdditionalInterfaces(Proxy, Class<?>… additionalInterfaces)

Dies ist i. d. R. nur dann erforderlich, wenn ein Implementierungsobjekt als Rückgabewert eines Methodenaufrufs automatisch mit einer Proxy-Instanz versehen wurde, statt durch createImplementation(.) erzeugt zu werden.

4.2.2.3      Schnittstellenbindung in Collections

Sind Collections Parameter oder Rückgabewert eines Methodenaufrufs, dann werden die in den Collections enthaltenen Interfaces mit Schnittstellenbindung zur Laufzeit aufgelöst. Voraussetzung hierfür ist, dass der Parameter bzw. die Rückgabe in der Methode als einer der folgenden Interfaces deklariert ist:

  • lang.Iterable,
  • util.Collection,
  • util.List,
  • util.Set,
  • util.Map,
  • cisag.pgm.util.CisMap

Bei Deklaration eines anderen Collection-Interface oder einer anderen Collection-Klasse findet keine Schnittstellenbindung statt.

Beim Methodenaufruf werden die Collections durch Proxy-Objekte gewrapped, so dass es nicht möglich ist, die Collection auf eine bestimmte Collection-Klasse zu casten.

Die als Element enthaltenen Interfaces, deren Schnittstellenbindung zur Laufzeit aufgelöst werden soll, müssen explizit von der User-App registriert werden, siehe Abschnitt „Schnittstellenbindung“.

Die Collections können beliebig geschachtelt werden.

4.2.2.4      Reguläre Objekte

Alle Objekte, für die keine der in den vorangegangenen Abschnitten beschriebenen Vorgehensweisen zutrifft, werden direkt übernommen. Dies gilt insbesondere auch für Arrays.

In der Provider-App können bei Bedarf komplexere Datenstrukturen als eigenes Interface mit Schnittstellenbindung abgebildet werden, siehe Abschnitt „Schnittstellenbindung“.

4.2.2.5      Beispiel: Objekt-Parameter

Ein „UserData“-Containerobjekt soll als Parameter und Rückgabewert in den Methoden einer Schnittstelle verwendet werden. Sowohl die Schnittstelle als auch das Containerobjekt werden als Interface mit Schnittstellenbindung bereitgestellt.

Containerobjekt

Provider-App B, Interface:

package com.provider.ext.app.b.template;

:

@InvocationTarget(“com.provider.ext.app.b.log.UserDataImplementation”)

interface UserDataInterface {

void setData(…);

Object getData();

}

Provider-App B, Implementierung:

package com.provider.ext.app.b.log;

import com.provider.ext.app.b.template.UserDataInterface;

 

class UserDataImplementation implements UserDataInterface {

void setData(…) { … }

Object getData() { … }

}

User-App A, Interface:

package com.user.ext.app.a.obj;

:

@InvocationTarget(“com.provider.ext.app.b.log.UserDataImplementation”)

interface UserDataInterface {

void setData(…);

Object getData();

}

Schnittstelle „Interface“ mit ihrer Implementierung

Provider-App B, Interface:

import com.provider.ext.app.b.template.UserDataInterface;

:

interface Interface {

void initUserData (UserDataInterface user);

UserDataInterface getUserData();

Boolean printUserDatas(java.util.list<UserDataInterface> datas);

}

Provider-App B, Implementierung:

import com.provider.ext.app.b.template.UserDataInterface;

:

class Implementation implements Interface {

void initUserData (UserDataInterface user) { … }

UserDataInterface getUserData() { … };

boolean printUserDatas(java.util.list<UserDataInterface> datas)
{ … }

}

User-App A, Interface:

Beim Übernehmen des Datencontainers in die User-App A brauchen nur das Package ausgetauscht und ggf. Importe angepasst werden.

import com.user.ext.app.a.log.UserDataInterface;

:

interface Interface {

String method(…);

 

void initUserData (UserDataInterface user);

UserDataInterface getUserData();

boolean printUserDatas(java.util.list<UserDataInterface> datas);

}

Methodenaufrufe

Verschiedene Methodenaufrufe durch die User-App A mit „UserData“ als Parameter bzw. Rückgabe:

import com.user.ext.app.a.log.Interface;

import com.user.ext.app.a.log.UserDataInterface;

:

CisProxyManager xm= CisEnvironment.getInstance().getProxyManager();

 

UserDataInterface userData = xm.createImplementation(UserDataInterface.class);

 

Interface intf = xm.createImplementation(Interface.class, UserDataInterface.class);

If (intf != null) {

 

intf.initUserData(userData);

UserDataInterface userData2 = intf.getUserData();

 

java.util.ArrayList datas = new ArrayList();

datas.add(userData);

datas.add(userData2);

Boolean ok = printUserDatas(datas);

}

4.3        Hook Contracts ohne Installationsabhängigkeit

Hook Contracts sind eine Schnittstellentechnologie, bei der eine Provider-App eine Schnittstelle (Hook) zur Verfügung stellt, und User-Apps sich registrieren können, um im Rahmen dieser Schnittstelle aufgerufen zu werden.

Die folgende Abbildung zeigt die Bestandteile von Hook Contracts bei Nutzung ohne Installationsabhängigkeit. In der Provider-App wird ein Hook Contract mit einem Hook bereitgestellt. In der User-App werden Hook Contract und Hook implementiert. Wie bei Hook Contracts üblich, wird die Hook-Implementierung durch die Provider-App aufgerufen.

Hook Contract ohne Installationsabhängigkeit

In der Provider-App ist zur Nutzung ohne Installationsabhängigkeit weder am Hook  noch am Hook Contract eine besondere Kennzeichnung notwendig. Die Hooks  müssen die im nachfolgenden Abschnitt „Parameter und Rückgabewerte“ genannten Bedingungen erfüllen, damit sie ohne Installationsabhängigkeit genutzt werden können.

In der User-App muss die Hook-Contract-Implementierung zur Nutzung ohne Installationsabhängigkeit gekennzeichnet werden. Dazu wird das Attribut „dependency“ des „contract“-Elements in der XML-Definition auf „false“ gesetzt (Standardwert ist „true“). Mit dieser Kennzeichnung kann die Hook-Contract-Implementierung als Entwicklungsobjekt auch ohne Hook-Contract-Definition oder Interface-Java-Klasse aktiviert werden und ist von der Hook-Contract-Definition somit nicht installationsabhängig.

Bei einer Hook-Implementierung besteht die Wahl,

  • entweder die Hook-Schnittstelle in die User-App zu kopieren und die Implementierung davon ableiten zu lassen,
  • oder die Methoden des Hooks mit unveränderter Signatur in die Implementierung zu übernehmen und aus technischen Gründen direkt das leere Interface cisag.pgm.base.Hook zu implementieren.

Die Schnittstellenbindung kommt in beiden Fällen zustande. Es wird empfohlen, die Hook-Schnittstelle in die User-App zu kopieren, um Programmierfehler zu vermeiden.

Wenn die Provider-App zur Laufzeit nicht vorhanden ist, und damit die Hook-Contract-Definition fehlt, dann werden der Hook oder die Hook-Implementie­rung nicht aufgerufen.

Im umgekehrten Fall, wenn die User-App zur Laufzeit nicht vorhanden ist, dann hat der Hook-Aufruf durch die Provider-App – wie bei einem Hook Contract mit Installationsabhängigkeit – keine Auswirkung.

Wenn zur Laufzeit zwar die Hook-Contract-Definition vorhanden ist, diese aber nicht das Hook-Interface enthält, dann hat der Hook-Aufruf ebenfalls keine Auswirkung. In diesem Fall ist die Provider-App wahrscheinlich in einer veralteten Version installiert. Ob dies ein Problem ist, muss im Einzelfall beurteilt werden.

In allen anderen Punkten verhalten sich die Hook Contracts so, wie in der Dokumentation „Hook Contracts“ beschrieben.

4.3.1    Beispiel

Hook-Contract-Implementierung

:

<contract dependency=”false”>com…b…HookContractDef</contract>

<hook>

<interface>com.provider.ext.app.b.hook.HookInterface</interface>

<implementation>com.user.ext.app.a.log.HookImpl</implementation>

</hook>

:

User-App A, kopiertes Hook-Interface (optional)

package com.user.ext.app.a.log;

/* Kopiertes Interface com.provider.ext.app.b.hook.HookInterface */

public interface HookInterface extends com.cisag.pgm.base.Hook {

:

}

User-App A, Hook-Implementierung

class HookImpl implements com.user.ext.app.a.log.HookInterface {

:

}

oder

class HookImpl implements com.cisag.pgm.base.Hook {

:

}

4.3.2    Parameter und Rückgabewerte

Für Parameter und Rückgabewerte von Hook-Methoden gelten folgende Einschränkungen:

  • Ein Interface mit Schnittstellenbindung und Implementierung in der Provider-App kann in Hook-Methoden als Parameter und Rückgabe verwendet werden, es erfolgt eine automatische Typanpassung über Proxys. Dies gilt jedoch nicht für Elemente innerhalb von Collections: sie können übergeben werden, verwenden jedoch weiterhin das Interface der Provider-App.
  • Darüber hinaus gelten für Parameter und Rückgabewerte in Hook Contracts dieselben Einschränkungen wie in Client Interfaces.

Bei der Verwendung von Hook Contracts ohne Installationsabhängigkeit müssen die Interfaces, deren Schnittstellenbindung zur Laufzeit aufgelöst werden soll, explizit von der Provider-App registriert werden. Für einen Hook-Aufruf erzeugt die Provider-App zunächst die Hook-Implementierungen:

Collection<Hook> CisHookManager.getHookImplementations(Class contract, Class interf)

oder

Hook CisHookManager.getHookContainer(Class contract, Class interf).

Anschließend werden durch Aufruf der folgenden Methoden zusätzliche Interfaces zur Schnittstellenbindung von Parametern oder Rückgabewerten registriert:

CisProxyManager.registerAdditionalInterfaces(Collection<Hook>, Class<?>… additionalInterfaces)

oder

CisProxyManager.registerAdditionalInterfaces(Hook, Class<?>… additionalInterfaces)

Objekte mit Unterstützung der Schnittstellenbindung, die als Parameter übergeben werden sollen, werden von der Provider-App für den Hook-Aufruf direkt erzeugt, wie im folgenden Beispiel gezeigt wird. Demgegenüber wird für eine Rückgabe eines Objekts mit Schnittstellenbindung zunächst dieses Objekt in der Hook-Implementierung mittels CisProxyManager.createImplementation(.) erzeugt.

Beispiel: Parameter bei Hook-Aufrufen

Das Beispiel zeigt einen Hook-Aufruf in der Provider-App mit Unterstützung der Schnittstellenbindung. Die Provider-App übergibt eine Implementierung des Interface beim Hook-Aufruf.

 

import com.provider.ext.app.b.template.Interface;

import com.provider.ext.app.b.template.UserDataInterface;

:

CisProxyManager xm= CisEnvironment.getInstance().getProxyManager();

 

UserDataInterface userData = new UserData();

 

Interface hook =
hm.getHookContainer(com.provider.ext.app.b.ContextClass.class,
Interface.class);

xm.registerAdditionalInterfaces(hook, UserDataInterface.class);

hook.initUserData(userData);

Die Hook-Implementierung verwendet eine Kopie des Interface in ihrer Methodensignatur und kann darüber auf das übergebene Objekt zugreifen.

4.4        Befreundete Apps

Die oben beschriebenen Verfahren verwenden eine Schnittstellen-Kopplung zwischen den beteiligten Apps, d. h. App A muss eine Schnittstelle bereitstellen, über die App B zugreifen kann. Diese Schnittstellen sind für funktionale Aufrufe mit einfacher Parameter-Struktur sinnvoll. Ungeeignet sind sie z. B. für den Austausch von Massendaten in Verbindung mit Datenbankzugriffen. Wie oben beschrieben, müssen Methodenparameter jeweils einzeln per createImplementation(.) mit einem Proxy versehen werden. Sollen komplette Business Objects ausgetauscht werden, muss also jede einzelne Instanz separat aufbereitet werden. Vor allem sind die Methoden des Persistenzdienstes nicht direkt nutzbar, um auf die gewohnte Weise Business Objects zu lesen.

Sogenannte „befreundete Apps“ hingegen erlauben, dass App A auf bestimmte Entwicklungsobjekte von App B zugreifen kann, als hätte sie diese selbst angelegt. Dies ist insbesondere für Business Objects und Java-Klassen sinnvoll.

Im Folgenden sind, neben „befreundeten Apps“, die weiteren möglichen Arten der Abhängigkeit zweier Apps beschrieben. , die in der Anwendung „Systemcockpit“, in der Ansicht „Systemgruppe“ unter dem Karteireiter „Modul-Zuordnungen“ festgelegt werden können.

Keine Abhängigkeit

Der Standard-Zustand ist, dass App A keine Entwicklungsobjekte aus App B referenzieren darf. Versuche, entsprechende Elemente aus App A einzuchecken oder zu aktivieren, führen zu einer Fehlermeldung. Dadurch können keine Installationsabhängigkeiten entstehen. Wird in der Systemgruppe nicht explizit eine Abhängigkeit zwischen zwei Apps festgelegt, gilt dieser Zustand.

Benötigtes Modul

App B kann als „Benötigtes Modul“ für App A eingetragen werden. Dies hat folgende Auswirkungen:

  • Entwicklungsobjekte aus App B dürfen in App A referenziert werden. Dies ist zwar zulässig, wird aber nicht empfohlen, weshalb es zu einer Warnung führt.
  • Java-Klassen aus App A dürfen Klassen-Methoden aus App B aufrufen. Sofern diese Methoden allerdings nicht als stabil identifiziert sind, erfolgt eine Warnung.
  • Einfluss auf Installation: sämtliche von App A als „benötigt“ deklarierten Apps müssen auf dem Zielsystem bereits installiert sein, sonst bricht die Installation ab.
Befreundetes Modul

App B kann als „Befreundetes Modul“ von App A deklariert werden. Dies hat folgende Auswirkungen:

  • Entwicklungsobjekte aus App B dürfen in App A referenziert werden. Es erfolgt keine Warnung.
  • Java-Klassen aus App A dürfen Klassen-Methoden aus App B aufrufen. Es erfolgt keine Warnung für nicht-stabile Methoden.
  • Einfluss auf Installation: zum Zeitpunkt der Installation von App A ist es nicht erforderlich, dass App B bereits installiert ist.

Hinweis:

Wenn App B mit App A befreundet ist, heißt das nicht automatisch, dass auch App A mit App B befreundet ist. Dies muss explizit deklariert werden.

Hinweis:

Die Deklaration als „Befreundetes Modul“ ist abhängig von der Systemgruppe, da der Zugriff auf die befreundete App z. B. erst ab einem bestimmten Release implementiert ist.

Hinweis:

Unabhängig von der festgelegten Abhängigkeit entsteht durch das Referenzieren eines Entwicklungsobjekts aus App B durch App A immer eine Abhängigkeit, die insbesondere bei der Installation berücksichtigt werden muss.

4.4.1    Programmierung

Die Programmentwicklung findet in einer Umgebung statt, in der alle beteiligten Apps installiert sind. Hier kann der Entwickler zur Kompilierzeit mit sämtlichen üblichen Mitteln auf Methoden aus anderen Apps zugreifen, sofern sie als “public“ deklariert sind. Für das Fehlerhandling beim Einchecken gilt daher unverändert: nur wenn alle verwendeten Symbole und Verweise erfolgreich aufgelöst werden können und sich die neu entwickelte oder geänderte Klasse fehlerfrei kompilieren lässt, kann sie eingecheckt werden.

Die erzeugten Java-class-Dateien enthalten somit Verweise auf die aufgerufenen Methoden anderer Apps.

Zur Laufzeit versucht die Java Virtual Machine (JVM) beim ersten Zugriff auf eine andere Klasse, diese Klasse in den Speicher zu laden. Stellt sie hierbei fest, dass die entsprechende class-Datei nicht vorhanden ist, wirft sie eine NoClassDefFoundError-Exception, die standardmäßig einen Neustart des Application Servers auslösen würde.

Es liegt daher in der Zuständigkeit des Entwicklers, zu verhindern, dass die JVM versucht, eine nicht vorhandene Klasse zu laden. Dies kann auf zwei Arten erfolgen: mit Java-Mitteln oder per CEE-API.

Mit Java-Mitteln kann die Exception durch einen try-catch-Block abgefangen werden. Dies bietet jedoch am wenigsten Kontrolle über den Programmablauf, da die Exception erst beim eigentlichen Zugriffsversuch auftritt.

Mehr Kontrolle bieten die Methoden isAppInstalled, mit denen sich zur Laufzeit abfragen lässt, ob daie gewünschte App installiert ist. Abhängig vom Ergebnis kann dann der Programmablauf gezielt gesteuert werden. Der Zugriff auf die befreundete App gehört häufig zu einem Block von Anweisungen, z. B. mit vorbereitenden unkritischen Methodenaufrufen, die bei nicht installierter App komplett übersprungen werden sollen.

Die Klasse com.cisag.pgm.util.PluginUtility bietet 2 Methoden isAppInstalled(..) mit verschiedenen Signaturen an:

  • Static boolean isAppInstalled(String developmentPrefix, String appName)
  • Static boolean isAppInstalled(CisModuleId)

4.4.1.1      Beispiel

Die App App2 greift auf ein Business Object aus App1 zu.

Package com.ado.ext.app.app2;

import com.cisag.pgm.util.PluginUtility;

import com.ado.ext.app.app1.obj.App1TestBO;

:

App1TestBO b;

if (PluginUtility.isAppInstalled(“ado”, “app1”)) {

System.out.println(“Start reading…”);

b = App1TestBO.newTransientInstance();

CisObjectIterator<App1TestBO> it = b.retrieve_instances();

while (it.hasNext()) {

b = it.next();

System.out.println(”  value: ” + b.getValue());

}

} else {

System.out.println(“Plugin nicht installiert”);

}

:

Die Variablendeklaration App1TestBO b; erzeugt keinen ausführbaren Code und muss daher nicht eingeschachtelt werden.

Dieser Code lässt sich auf dem Entwicklungssystem u.a. wegen der Zeile b = App1TestBO.newTransientInstance(); nur übersetzen, wenn die Klassendatei com/ado/ext/app/app1/obj/App1TestBO.class vorhanden ist. Zur Laufzeit ist der Code innerhalb des if-Statements nur ausführbar, wenn auch die Mapper-Klassen von App1TestBO vorhanden sind.

4.4.1.2      Leitlinien zur Programmierung

Verfügbarkeit von Klassen

Die Möglichkeit zur Adaptierung bestehender Java-Klassen sollte mit möglichst wenigen Einschränkungen bestehen bleiben. Zur Anpassung ist jedoch erforderlich, dass sich die Klasse auf dem jeweiligen Entwicklungssystem komplett kompilieren lässt. Beim Zugriff auf befreundete Apps sind deshalb einige Punkte zu berücksichtigen, damit die Klassen adaptierbar bleiben.

Eine Klasse mit Zugriff auf befreundete Apps ist nur auf solchen Entwicklungssystemen bearbeitbar, auf denen die entsprechenden befreundeten Apps installiert sind. Andernfalls darf die Klasse nicht in eine Entwicklungsaufgabe aufgenommen werden, da sie aufgrund von Compiler-Fehlern nicht wieder eingecheckt werden kann. Dies gilt für sowohl für manuell als auch für automatisch zu einer Entwicklungsaufgabe hinzugefügte Klassen.

Adaptierung von Java-Klassen

Ein Problem kann sich immer dann ergeben, wenn in einem nachgelagerten System S2 eine Klasse A aus System S1 adaptiert werden soll, welche Zugriffe auf eine befreundete App B enthält. Wie oben vorausgesetzt, muss App B in S1 vorhanden sein. Dies darf jedoch in S2 keine Voraussetzung sein, insbesondere wenn in S2 nur Adaptierungen von Klasse A gemacht werden sollen, die keine Referenzen zu App B haben, sondern sich nur auf Standard-App-Code beziehen.

Beachten Sie daher Folgendes, um den Code möglichst adaptierbar zu halten („Best Practice“): Klasse A sollte möglichst keine Zugriffe auf Klasse B aus einer befreundeten App mit umfangreicher eigener Logik vermischen. Dies kann z. B. dadurch erreicht werden, dass Sie eine separate Klasse A‘ anlegen, die Wrapper-Methoden für die eigentlichen Zugriffe auf B enthält, während Ihre eigene Logik in A verbleibt und die Wrapper-Methoden aus A‘ aufruft. Auf diese Weise lässt sich A weiterhin auf S2 anpassen und kompilieren, während A‘ auf S2 nur als class-Datei vorhanden sein muss.

Die Klasse A‘ sollten Sie zusätzlich explizit mit der Annotation @UsesFriendApps als nur bedingt adaptierbar kennzeichnen.

Nicht adaptierbare Klassen

Mit der Annotation @UsesFriendApps können Sie direkt an der Klassendeklaration hinterlegen, dass diese Klasse auf befreundete Apps zugreift. Dies wirkt sich vor allem auf die Entscheidung aus, ob diese Klasse adaptiert werden darf. Konkret bestimmt die Annotation, ob die Java-Klasse eventuell nicht in eine Entwicklungsaufgabe aufgenommen werden darf.

Die annotierte Klasse darf immer in eine Entwicklungsaufgabe aufgenommen werden, wenn sie auf dem aktuellen System erzeugt wurde. Stammt sie aus einem anderen System, ist das Aufnehmen von den Parametern der Annotation abhängig. Verwenden Sie die Annotation ohne weitere Parameter (@UsesFriendApps class …), dann darf die Klasse generell nicht in eine Entwicklungsaufgabe übernommen werden. Durch die explizite Angabe der verwendeten befreundeten Apps können Sie festlegen, welche Apps auf dem Entwicklungssystem S2 installiert sein müssen, damit die Klasse trotzdem in eine Entwicklungsaufgabe aufgenommen werden darf. Geben Sie dazu die Apps als Liste von Strings im Format „<Entwicklungspräfix>/<App-Name>“ an, z. B. @UsesFriendApps=({„ado/app1“, „ado/app3“}) class …

Hinweis:

Dies ist unabhängig von der Deklaration befreundeter Apps innerhalb der aktuellen Systemgruppe.

Adaptierung von Business Objects

Wird ein Business Object in einer Entwicklungsaufgabe verändert, werden durch den Aufruf von „crtbo“ evtl. bereits vorhandene Update-Klassen zu der Entwicklungsaufgabe hinzugefügt. Die Verwendung des Persistenzdienstes in den Update-Klassen ist nicht zulässig. Dies gilt damit auch für die Abfrage über isAppInstalled(..): eine entsprechende Konstruktion mit eingeschachteltem Zugriff auf eine andere App darf hier nicht verwendet werden.

Einschränkungen bei Parametern

Künftig werden separate Apps getrennte Bibliotheken nutzen können, insbesondere auch unterschiedliche Versionen derselben externen Bibliothek. Dazu wird jede App einen eigenen Speicherbereich verwalten, in dem seine Bibliotheken lokal geladen sind. Dies führt allerdings dazu, dass Objekte aus zwei lokalen Bibliotheken nicht zueinander kompatibel sind. (Technischer Grund: Java verwendet für jede App einen eigenen Classloader, und zwei Klassen können nur aufeinander abgebildet werden, wenn sie vom selben Classloader geladen wurden.)

Vermeiden Sie daher, Objekte aus einer Fremdbibliothek als Parameter oder Rückgabewert einer befreundeten Methode zu verwenden.

Hinweis:

Eine entsprechende Validierung findet nicht statt.

4.5        Nutzung anderer Schnittstellen

Die Klasse com.cisag.pgm.appserver.CisModuleId enthält eine Methode, mit der festgestellt werden kann, ob eine App installiert ist. Diese Prüfung kann vor der Verwendung bestimmter APIs (OQL-Suchen, Resultsets) genutzt werden. Damit können diese APIs ebenfalls ohne Installationsabhängigkeit genutzt werden.

Ist „id“ die CisModuleId der zu prüfenden App, so liefert die Instanz-Methode boolean isInstalled() die Information, ob die App verwendet werden kann. Die Methode berücksichtigt dabei auch den Lizenzschlüssel der App, jedoch nicht die Customizing-Einstellungen.

 

Czy ten artykuł był pomocny?