Direktbank: Implementierung mit dem JBuilder X

Die X-Bank

Daniel Reiberg und Torsten Langner

Ausgangsbasis für diesen Artikel bilden die im Java Magazin 12.03 erschienenen Beiträge zum Thema .NET vs. J2EE, in denen jeweils ein und dieselbe Aufgabenstellung mit Hilfe von .NET bzw. Java gelöst wurde [1]. Nach dem für Java schon fast ernüchternden Vergleich zwischen beiden Entwicklungstechnologien, bei dem klar wurde, dass die Überlegenheit von .NET lediglich auf den Fähigkeiten des Visual Studios beruhte, das als ein All-In-One Wunder daherkommt, blieb die Frage offen, wie sich die restlichen Tools auf dem J2EE-Schlachtfeld schlagen würden. Kann die Microsoft-Bastion eingenommen werden? Dieses Mal ist der erst vor kurzem veröffentlichte JBuilder X im Rennen.

Die Architektur

Wie auch schon bei den beiden vorherigen Implementierungen mit Eclipse und dem Visual Studio soll auch hier wieder die Devise gelten: wenig Aufwand betreiben, und trotzdem eine hochleistungsfähige Applikation auf die Beine stellen. Bereits nach einer kurzen Selbstfindungsphase war klar, wie die Architektur dieser Implementierung auszusehen hat.

Abb. 1: Die Basisarchitektur für die Lösung mit dem JBuilder

Wie in Abpictureung 1 dargestellt, wird nun eine 4-Tier-Architektur nach Lehrbuch aufgesetzt. Das bedeutet, dass entweder ein Browser die Daten visualisiert, die ihm das Web Gateway zusendet, oder dass ein stand-alone Client die Geschäftslogik über ein Web Service Gateway (in)direkt anspricht. Die Geschäftslogik an sich ist auf Ebene III der 4-Tier-Architektur angesiedelt. Die Realisierung erfolgt mit Hilfe von Enterprise JavaBeans. Da die Aufgabenstellung sehr datenlastig ist, sprach alles für die Container Managed Persitence (CMP), was zur Folge hatte, dass sämtliche SQL-Anweisungen und Datenbankverwaltungen aus der Eigenleistung gestrichen werden konnten. Die Geschäftslogik an sich wurde von einer zustandsbehafteten Session Bean einem Client der Ebene II zur Verfügung gestellt. Auf Ebene II wurden JSPs und Servlets verwendet, um vornehmlich XML-Dokumente zusammenzustellen, die auf Ebene I via XSLT zu HTML-Dokumenten transformiert werden (wie auch schon in den beiden anderen Implementierungen zuvor). Da der JBuilder nur Axis als Web Service Engine unterstützt, blieb keine andere Wahl, als Axis für die Realisierung des Web Service Gateways zu benutzen.

Die Realisierung der Ebene III

Doch beginnen wir nun mit der Realisierung der Geschäftslogik auf Ebene III. Der JBuilder verfügt über einen EJB-Designer, mit dem Entity, Session und Message Driven Beans grafisch entworfen werden können. Diese Funktionalität war bereits in Version 9 integriert und wurde für die Version 10 nur minimal modifiziert. Wie in Abpictureung 2 zu erkennen ist, können innerhalb des Programms Datenquellen verwaltet werden. Wird – wie in diesem Fall – eine Datenbank registriert, liest der JBuilder ihre Struktur aus. Im Gegensatz zur Version 9, die noch erkannte, ob es sich bei einem Feld einer Tabelle um einen Primärschlüssel handelt, muss bei der Version 10 jeder Primärschlüssel manuell kenntlich gemacht werden.

Abb. 2: Der EJB-Designer des JBuilder

Ebenfalls wünschenswert wäre es, die Relationen, die bereits in der Datenbank enthalten sind, durch den Einsatz eines Assistenten zu automatisieren. So müssen sämtliche Relationen zwischen den als CMP 2.0 entworfenen Entity Beans manuell nachgezeichnet werden. Solch eine Relation ist ebenfalls in Abpictureung 2 zu erkennen. Wird z.B. dem Kurs eine neue Relation zu einem Handelsplatz hinzugefügt, dann muss diese Relation nachbearbeitet werden. Standardmäßig geht der JBuilder davon aus, dass es sich um eine 1:N Relation handelt. Dies muss im Fall der zugrunde liegenden Applikation jeweils in eine 1:1 Relation abgeändert werden. Ebenfalls muss mittels eines Assistenten festgelegt werden, welches Attribut welcher Tabelle ein Fremdschlüssel eines anderen Attributs ist (Abb. 3).

Abb. 3: Das manuelle Feintuning der Relationen

Doch wer glaubt mit Hilfe eines solchen Assistenten eine einwandfrei lauffähige Applikation auf die Beine stellen zu können, der wird spätestens beim Deployen des Archivs eines Besseren belehrt werden: Auch wenn der JBuilder eine JBoss-Unterstützung (erst seit Version 10) anbietet, so erkennt man recht schnell, dass diese Unterstützung nicht 100%ig funktioniert. Das liegt vor allem daran, dass der in unserem Fall eingesetzte JBoss 3.2.1 sich nicht mit dem vom JBuilder generierten Deployment Descriptor jbosscmp-jdbc.xml anfreunden kann. Erst durch massive Eingriffe in diese Datei (die übrigens bei jeder Assistenten-Benutzung wieder vom JBuilder überschrieben wird), können die EJBs im JBoss fehlerfrei deployt werden. Wie diese Entity Beans genau aussehen, muss an dieser Stelle nicht beschrieben werden. Es reicht vielmehr zu erwähnen, dass alle der Performance wegen nur lokal erreichbar sind.

Der Zugang zu den Entitäten wurde mit Hilfe des DTO-Patterns realisiert. Hierdurch wird für jede Entity Bean eine separate (serialisierbare) JavaBean, die im Folgenden als Data Transfer Object bezeichnet wird, geschrieben. Gibt man diese über eine Session Bean an den jeweiligen Client zurück, reduziert sich der Kommunikationsaufwand zwischen Client und Server enorm. Für eine Entität mit 6 Attributen bedeutet das Schreiben eines passenden DTO jedoch auch einen Schreibaufwand von 6 Attributen in dem entsprechenden DTO. Ändert sich die Datenbankstruktur ab, muss nicht nur die Entity Bean, sondern auch die DTO abgeändert werden. Der JBuilder bietet seit der Version 9 bereits einen Assistenten an, der das Schreiben eines DTOs für eine Entity Bean übernimmt. Auch für die mit anderen Entitäten durch Relationen verbundene Entity Beans werden geeignete DTOs geschrieben.

Abb. 5: Der DTO-Wizard

Wie in Abpictureung 4 dargestellt, genügt ein rechter Mausklick auf eine Entity Bean und der Entwickler erhält Zugriff auf den DTO Wizard. Letzterer, dessen Eingangsfenster in Abpictureung 5 dargestellt ist, bietet jedoch nur indirekt an, ein DTO zu generieren. Es kann nur generiert werden, wenn auch die Session Facace generiert wird. Dies ist eine zustandslose Session Bean, die diverse Setter und Getter enthält und die als eine Art Durchreiche zu der betreffenden Entity Bean fungiert. Diese sind jedoch für die hier vorgestellte Applikation unnötig und müssen daher nach deren Erstellung wieder aus dem Projekt manuell gelöscht werden. Zu jedem DTO wird auch ein sog. Assembler generiert, der ein Remote-Objekt in ein DTO verwandelt. Um einen Eindruck zu bekommen, wie die mit dem Assistenten generierten DTOs aussehen, sei an dieser Stelle auf den Codeauszug in Listing 1 hingewiesen.

Listing 1

package dtos;

public class HandelsplatzDto implements Serializable
{
private String bezeichnung;
private String kuerzel;
public String getBezeichnung() {
return bezeichnung;
}
public void setBezeichnung(String bezeichnung) {
this.bezeichnung = bezeichnung;
}
public String getKuerzel() {
return kuerzel;
}
...
}

Der Zugang zur Außenwelt wird über die zustandsbehaftete Session Bean KundenserviceBean realisiert, die das Interface Kundenservice implementiert. Wie das (Listing 2) Interface Kundenservice bzw. die KundenserviceHome-Schnittstelle (Listing 3) erkennen lassen, wird auf Server-Seite nur dann ein entsprechendes Session-Objekt angelegt, wenn sich ein Benutzer erfolgreich legitimieren kann. Wurde ein Session-Objekt erst einmal erfolgreich angelegt, dann kann der Client die Geschäftslogik durch den Aufruf diverser Funktionen ansprechen, deren Rückgabewerte jedoch keine Remote-Interfaces der betreffenden Entity Beans sind, sondern den zuvor per Assistenten generierten DTOs entsprechen. Wie u.a. in [3] vorgeschlagen wird, sollten bestimmte Routinen statisch (und damit einmalig) angesiedelt werden, um ein wenig Performance gewinnen zu können. Daher wurde eine projektweit globale Klasse Environment (Listing 4) gemäß dem Singleton-Pattern geschaffen, auf die die diversen Klassen zugreifen. Der Code-Ausschnitt in Listing 5 zeigt, wie Environment eingesetzt wurde und wie simpel aus einer lokalen Entity Bean ein DTO gemacht werden kann.

Listing 2

package environment;

public class Environment {

//-------> STATIC CONTENT:

public static Environment getInstance () {  }

//--------> INSTANCE CONTENT:

private Environment() throws Exception {
Properties p = new Properties ();
p.setProperty( Context.PROVIDER_URL, APPSERVER );
initialContext = new InitialContext ( p );
}

public Context getInitialContext () {  }

public String convertToXml ( Object bean )
{
ByteArrayOutputStream out = new ByteArrayOutputStream ();
XMLEncoder encoder = new XMLEncoder ( out );
encoder.writeObject( bean );
encoder.close();
String xml = out.toString();
return xml.substring( xml.indexOf( "?>") + 2);
}

Listing 5

Abb. 6: Die via XSLT transformierten XML-Dokumente der Ebene II

Listing 6

Wesentlich interessanter als das Web Gateway war die Realisierung des Web Service Gateways. Letzteres war schließlich mit einer der Gründe bei der Realisierung im Java Magazin 12.03, weshalb das Visual Studio .NET seinen Kampf gewonnen hatte. Genau wie in Version 9, so setzt auch der JBuilder X auf die Axis-Engine. Die einzige Änderung gegenüber Version 9 scheint optische Gründe zu haben.

Abb. 7: Der Web Service Designer

Um die Geschäftslogik in Form der Session Bean Kundenservice als Web Service zu exportieren, hätte man – wenn man es sich sehr einfach machen will – lediglich dem Web Service-Designer (Abb. 7) mitteilen müssen, man wolle das Interface Kundenservice (mit der implementierenden Klasse KundenserviceBean) exportieren. Jedoch muss man hierzu wissen, dass Axis für jede (Session- bzw. Request-)Anfrage eine neue Instanz der Klasse KundenserviceBean innerhalb des Web-Containers auf Ebene II erzeugt, und nicht den Aufruf des Clients an eine EJB-Instanz auf Ebene III weiterleitet. Auf Grund der Skalierbarkeit der Gesamtapplikation und auf Grund der Tatsache, dass auch ein stand-alone Client dieselbe Geschäftslogik nutzen sollte wie ein Web-Client auf Ebene II, wurde ein anderer Weg gewählt.

Listing 7

package webServices;

public interface IKundendienst {
// Session-basierte Methoden:
String login ( String user, String password ) throws RemoteException;
void logout ( String sessionID ) throws RemoteException;
// Modifizierte EJB-Methoden:
HandelsplatzDto[] getHandelsplaetze(String sessionID) throws RemoteException;
KursDto[] getKurse(String sessionID, String wkn, String handelsplatzKuerzel) throws RemoteException;
WertpapierDto[] getWertpapiere(String sessionID) throws RemoteException;
KundeDto getKunde(String sessionID) throws RemoteException;

}

Wie bereits in [4] und auch in [5] ausgiebig erläutert wurde, gilt für Web Services das Kriterium der Interoperabilität in besonderem Maße. Daher wurde ein eigenes Interface geschrieben, das zum einen die Funktionalitäten der Session Bean enthält, und das zum anderen den Forderungen in [4] gerecht wird. Das Resultat ist in Listing 7 in Form des Interfaces IKundenservice abgedruckt. Die Realisierung der dazugehörigen Implementierungsklasse KundenserviceWso wurde in Listing 8 Ausschnittsweise angedeutet. Mit Hilfe des Assistenten ist der Rest ein Kinderspiel.Listing 8

package webServices;

public class KundendienstWso implements IKundendienst
{
private static final String KUNDENDIENST = "ejbs.Kundendienst";

// Session-basierte Methoden:
public String login(String user, String password) throws RemoteException
{
try
{
Environment env = Environment.getInstance();
Context c = env.getInitialContext();
KundendienstHome kHome =
(KundendienstHome) c.lookup( env.KUNDENDIENSTHOME );
Kundendienst kundendienst = kHome.create ( user, password );
// Wenn's geklappt hat, dann geht's hier weiter:
SessionManager sManager = SessionManager.getInstance();
Session session = sManager.createNewSession();
session.putValue( KUNDENDIENST, kundendienst );
return session.getSessionID();
}
catch ( Exception e )
{
throw new RemoteException( "login", e );
}
}

// Modifizierte EJB-Methoden:
public HandelsplatzDto[] getHandelsplaetze(String sessionID) throws RemoteException
{
return this.getKundendienst( sessionID ).getHandelsplaetze();
}

public KursDto[] getKurse(String sessionID, String wkn, String handelsplatzKuerzel) throws RemoteException
{
return this.getKundendienst( sessionID ).getKurse( wkn, handelsplatzKuerzel );
}

}

Um die Interoperabilität des Web Service Gateways zu demonstrieren, wurde innerhalb des Visual Studios der Web Service referenziert. Der Code in Listing 9 zeigt, dass der C#-Client ohne weiteres nun mit der J2EE-Applikation interagieren kann.

Listing 9

using System;
using ConsoleApplication2.direktbank;

namespace ConsoleApplication2
{
class Class1
{
static void Main(string[] args)
{
IKundendienstService service = new IKundendienstService();
String sessionID = service.login ("ali", "baba");
KursDto[] kurse = service.getKurse( sessionID, "923835", "FFM" );
service.logout ( sessionID );
}
}

Wie die vorherigen Ausführungen gezeigt haben, lässt die Implementierung der Aufgabenstellung mit Hilfe des JBuilder kaum mehr Wünsche offen. Lediglich die Schwierigkeiten mit JBoss sind gravierend. Was den Zeitaufwand der Lösung angeht, kann behauptet werden, dass dieser in etwa dem der .NET-Variante entspricht. Da dies jedoch eine echte 4-Tier-Architektur ist, lag der Aufwand zwar vielleicht etwas darüber, doch darf spekuliert werden, dass dies eine weitaus skalierbarere Architektur ist.

Links und Literatur

  • [1] D. Reiberg, T. Langner: J2EE vs. .NET Titelthema im Java Magazin 12.2003
  • [2] Download des JBuilder X nach Registrierung unter: www.borland.com
  • [3] W. Crawford, J. Kaplan: J2EE Design Patterns. S. 421-442
  • [4] Torsten Langner: Web Services in heterogenen Systemumgebungen – Interoperable Session-Verwaltung, XML & Web Services Magazin, 4.2003
  • [5] Torsten Langner: Web Services mit Java – Neuentwicklung und Refactoring in der Praxis Markt und Technik 2003
Geschrieben von
Daniel Reiberg und Torsten Langner
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.