Le applicazioni da integrare
Gli
esempi che verranno proposti sono volutamente molto
semplici da un punto di vista implementativo per permettere
al lettore di concentrarsi prettamente sulle operazioni
d'integrazione.
Si vuole valutare gli impatti architetturali (sia applicativi
che tecnologici) per l'integrazione/interoperabilità
delle seguenti tre applicazioni:
-
Applicazione A: è un'applicazione J2EE dove
la parte di presentation è installata su Tomcat
versione 4.1 (vedi[TOMCAT]), mentre la parte di business
su JBoss 2.4.4(vedi[JBOSS]).
L'applicazione A permette di effettuare la concatenazione
tra due stringhe inserite dall'utente.
-
Applicazione B: anch'essa è un'applicazione
J2EE che però è interamente installata
(sia la parte di presentation che la parte di business)
su Weblogic versione 7.0(vedi[WLS]).
Il suo scopo è di calcolare la lunghezza di
una stringa inserita dall'utente.
-
Applicazione C: è l'analogo dell'applicazione
B sviluppata su Framework Microsoft .NET (vedi[.NET])
ed installata su Internet Information Server.
Figura 1 : Le applicazioni da integrare
Grazie
a queste tre semplici applicazioni sarà possibile
mostrare in modo pratico alcune possibili modalità
d'integrazione.
A che livello integrare
Definite le Applicazioni da integrare bisogna decidere
a che livello di "profondità" integrarle
(vedi [JW1]). Ogni decisione ha i suoi pro e i suoi
contro sia da un punto di vista di progetto, implementativo,
di risorse e di tempo.
User interface integration
L'integrazione a livello di User Interface non è
di fatto un'integrazione nel termine stretto del termine.
Si opera affinchè le due applicazioni (indipendenti
tra di loro) presentino la medesima interfaccia utente
condividendo quindi il medesimo "Look & Feel".
Questo tipo di integrazione (a "grana grossa")
è consigliabile laddove non si necessiti di distinguere
da quale applicazione (integrante o integrata) provengano
i dati.
Questo approccio ha il vantaggio di non essere oneroso
sia in termini tecnici che di tempo permettendo l'interoperabilità
tra applicazioni eterogenee.
Object/RPC
integration
L'integrazione Object/RPC prevede di affrontare l'integrazione
al livello di oggetti distributi (EJB,CORBA,DCOM,.NET
Remoting).
I dati vengono passati come parametri dei metodi di
business con opportuni componenti di disaccoppiamento
(Proxy e Adapter) per incapsulare/delimitare le "zone
integranti".
Un grosso vantaggio di questo approccio è di
essere "type safety" e di facile gestione
per quanto riguarda la propagazione dei dati, delle
eccezioni e dei codici d'errore.
Message Integration
Per integrare le applicazioni mediante la Message Integration,
i dati vengono sotto forma di messaggi.
Le due applicazioni devono essere logicamente in grado
di scambiarsi i messaggi (JMS, SOAP,
): supponendo
di dover integrare piattaforme diverse tra loro, come
ad esempio J2EE e .NET, bisogna trovare un "fattore
comune" di interoperabilità basato ad esempio
su Web Services o piu'semplicemente su SOAP.
Al contrario delle architetture basate su ORB, l'integrazione
Message-oriented permette un forte disaccoppiamento
tra i sistemi integranti e integrati per la natura intrinsecamente
asincrona dei sistemi di messaging.
Integrazione di dati
Nel caso di Data Integration i dati sono passati tra
i vari sistemi in modo data/record-oriented e adattati
da un formato di un sistema all'altro.
La Java Connector Architecture di SUN (vedi [MOKA_JCA])
ricade proprio in questa categoria.
Inbound e Outbound integration
Quando si parla di integrazione bisogna tenere conto
del "verso" dell'integrazione, cioè
chi è che richiede dati e chi invece li fornisce.
In una integrazione di tipo Inbound sono i sistemi esterni
che richiedono dati/servizi al proprio sistema. Nell'
Outbound integration è il proprio sistema che
richiede dati/servizi agli altri sistemi.
Tale "verso" di integrazione ha impatti ben
precisi sia a livello di progetto che di implementazione.
Se è il nostro sistema il richiedente saremo
chiamati a sviluppare gli appositi Adapter con il preciso
compito di invocare il servizio ed eventualemente di
convertire (semanticamente e/o sintatticamente) il formato
dei dati.
Nel caso contrario saremo noi gli interpellati da fonti
esterne e quindi a dovere esportare opportunamente le
funzionalità da integrare.
Nell'esempio citato l'applicazione A è il sistema
integrante (Outbound integration) mentre le Applicazioni
B e C sono quelle integrate (Inbound integration).
L'Applicazione A
Passiamo ora a descrivere in dettaglio le tre applicazioni
che saranno oggetto delle procedure d'integrazione.
L'Applicazione A permette di effettuare la concatenazione
di due stringhe inserite dall'utente. Al fine di semplificare
il piu' possibile il codice, l'applicazione A si basa
su una implementazione minimale della architettura Model
View Control. Il controller è costituito dalla
pagina JSP ConcatDispatcher.jsp che provvede ad invocare
l'opportuna vista in seguito all'azione richiesta dall'utente.
Si è effettuata tale scelta per cercare di rendere
il piu' semplice possibile l'esempio(per una trattazione
esaustiva sul MVC (vedi [MOKASHOP_1], [MOKA_MVC]).
<%
String nextPage = null;
String action = request.getParameter("ACTION");
if(action.equals("CONFIRM")){
nextPage = "/jsp/concatConfirm.jsp";
}
else if(action.equals("EXECUTE")){
nextPage = "/jsp/concatExecute.jsp";
}
RequestDispatcher rd = application.getRequestDispatcher(nextPage);
rd.forward(request,response);
%>
La
View è costituita dalle seguenti pagine JSP:
- concat.jsp
(richiede l'inseriento delle due stringhe da concatenare)
- concatConfirm.jsp
(chiede la conferma dei valori da concatenare prima
di avviare il servizio)
La
pagina concatExceute.jsp invoca il servizio di business
mediante il metodo MyBusinessDelegate.getConcat e ne
visualizza il risultato:
<%
String result = null;
try {
MyBusinessDelegate proxy = new MyBusinessDelegate();
result = proxy.getConcat( (String)request.getParameter("FIRST_STRING"),
(String)request.getParameter("SECOND_STRING"));
}catch(Exception e){
e.printStackTrace();
result = e.getMessage();
}
%>
Il
risultato ottenuto, contenuto nella variabile result,
viene visualizzato dalla pagina stessa:
<html>
<body>
. . .
<%= result %>
. . . .
</body>
</html>
Il
servizio di business è costituito da un semplice
EJB Session Stateless ConcatBean che espone il seguente
motodo di business:
public
interface Concat extends EJBObject {
public String concat(String a, String b) throws RemoteException;
}
con
le seguente semplice implementazione
public
class ConcatBean implements SessionBean {
public String concat(String a, String b) {
return a + b;
}
. . .
L'EJB
viene invocato dalla classe Business Delegate MyBusinessDelegate
(vedi [MOKA_BD]) che di fatto agisce da proxy verso
l'EJB.La classe MyBusinessDelegate demanda le problematiche
di localizzazione dell'EJB alla classe MyServiceLocator
(vedi [MOKA_SL]).
La
classe MyBusinessDelegate
public
class MyBusinessDelegate {
private
Concat concat = null; // Remote reference del Concat
EJB
public
String getConcat(String param1, String param2)
throws BusinessDelegateException {
try {
// richiede
la localizzazione della Home
// interface ConcatHome alla classe
MyServiceLocator
ConcatHome homeConcat = (ConcatHome)MyServiceLocator.
getHome(Defines.CONCAT_SERVICE);
//
per poi creare l'EJB
this.concat=
homeConcat.create();
// ed
invocarne il metodo di business concat()
return
this.concat.concat(param1,param2);
}
catch(Exception e){
throw new BusinessDelegateException(e);
}
Figura 2 : Sequence diagram dell' utilizzo del
Concat EJB
La classe MyserviceLocator (vedi [MOKA_SL]) localizza
la Home interface dell'EJB avvalendosi dell'array di
configurazione di nome businessServices di classe ServiceConfig:
public
class MyServiceLocator {
// Configration file
private static final String CONFIG_FILE_NAME = "/mokabyte/moka_eai/config/locator.xml";
// Service configurations array
private static ServiceConfig businessServices[] = null;
// Localizzazione type checked di una EJB Home
// dato un SERVICE ID logico
public static EJBHome getHome(int serviceID)
throws ServiceLocationException {
home = null;
try {
ServiceConfig config = MyServiceLocator.businessServices[serviceID];
Context ctx = getInitialContext(config.getInitialContextFactory(),
config.getProviderUrl());
Object objref = ctx.lookup(config.getJndiName());
Object obj = PortableRemoteObject.narrow(objref,config.getHomeClass());
Return (EJBHome)obj;
}
catch( Exception e ) {
throw new ServiceLocationException(e);
}
La
classe ServiceConfig è un Java Bean il cui compito
è di incapsulare i dati di configurazione del
servizio quali la classe Home, la classe ContextFactory,
il Provider URL ed il nome JNDI del servizio da localizzare:
public
class ServiceConfig {
private String description = null;
private Class homeClass = null;
private String jndiName = null;
private String initialContextFactory = null;
private String providerUrl = null;
private String username = null;
private String password = null;
<< relativi metodi get e set >>
Ogni
servizio ha quindi un proprio ID logico che deve corrispondere
allo slot fisico dell'array businessServices contenente
le relative configurazioni. Tali ID sono dichiarati
come costanti nella classe Defines.
public
class Defines {
public static final int CONCAT_SERVICE = 0;
. . .
Il
ServiceLocator configurare l'array businessServices
all'atto della sua creazione mediante la lettura del
file XML /mokabyte/moka_eai/config/locator.xml:
private
MyServiceLocator(){
try {
java.beans.XMLDecoder d = new java.beans.XMLDecoder(
new BufferedInputStream( new FileInputStream(CONFIG_FILE_NAME)));
Object result = d.readObject();
Object o[] = (Object[])result;
businessServices = new ServiceConfig[o.length];
for(int i=0; i<o.length; ++i){
if(o[i] instanceof ServiceConfig){
businessServices[i] = (ServiceConfig)o[i];
}
}
d.close();
}catch(Exception e){
}
Figura 3 : Struttura dell'Applicazione A
L'Applicazione
A è l'applicazione integrante (il sistema outbound)
dei nostri esempi e quindi man mano che verranno integrati
i servizi delle applicazioni B e C verranno "potenziate"
le classi MyBusinessDelegate, MyServiceLocator ed il
relativo file locator.xml di configurazione.
L'Applicazione B
L'Applicazione B permette di effettuare il calcolo della
lunghezza di una stringa inserita dall'utente.
Il controller è costituito dalla pagina JSP ConcatDispatcher.jsp
che provvede ad invocare l'opportuna vista a secondo
della richiesta dell'utente:
<%
String nextPage = null;
String action = request.getParameter("ACTION");
if(action.equals("CONFIRM")){
nextPage = "/jsp/wordlengthConfirm.jsp";
}
else if(action.equals("EXECUTE")){
nextPage = "/jsp/wordlengthExecute.jsp";
}
RequestDispatcher rd = application.getRequestDispatcher(nextPage);
rd.forward(request,response);
%>
La
View è costituita dalle seguenti pasgine worlength.jsp
(richiede l'inseriento della stringa di cui calcolare
la lunghezza),wordlengthConfirm.jsp (chiede la conferma
della stringa di cui calcolare la lunghezza) e wordlengthExecute.jsp
(invoca direttamente l'EJB e visualizza il risultato).
Il servizio di business modellato è cosituito
dell'EJB CalculatorBean che, in questo caso, viene invocato
direttamente da scriptlet all'interno della pagina JSP
wordlengthExecute.jsp:
<%
String result = null;
try {
Properties properties = null;
properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
properties.put(Context.PROVIDER_URL, "t3://localhost:7001");
Context ctx = new InitialContext(properties);
Object ref = ctx.lookup("MokaCalculatorWLS");
CalculatorHome calculatorHome = (CalculatorHome) PortableRemoteObject.narrow(ref,
CalculatorHome.class);
Calculator calculator = calculatorHome.create();
result = "" + calculator.getLength( (String)request.getParameter("STRING_USER"));
. . .
%>
<html>
<head></head>
<body>
<table>
<tr>
<td>
<%= result %>
</td>
. . .
L'EJB
che mette a disposizione il servizio di business si
chiama CalculatoreBean (Session Stateless EJB) e pubblica
nell'interfaccia remota il seguente metodo di business
:
public
interface Calculator extends EJBObject {
public int getStringLength(String a) throws RemoteException;
}
con
la seguente semplice implementazione
public
class CalculatorBean implements SessionBean {
public int getLength(String a) {
int retval = -1;
if(a!=null) {
retval = a.length();
}
return retval;
}
. . . . .
L'Applicazione
C
L'Applicazione
C, in modo analogo all'applicazione B, permette di effettuare
il calcolo della lunghezza di una stringa inserita dall'utente;
di fatto l'applicazione C è il porting dell'applicazione
B sul Framework Microsoft .NET.
Il
controller è costituito dalla pagina ASP ConcatDispatcher.asp:
<%
dim nextPage
fname=Request.QueryString("ACTION")
If fname = "CONFIRM" Then
nextPage="/myiis/asp/wordlengthConfirm.asp"
ElseIf fname = "EXECUTE" THEN
nextPage="/myiis/aspx/wordlengthExecuteRemoting.aspx"
End if
Response.Redirect ("http://localhost" + nextPage
+ "?" + Request.QueryString())
%>
La
View è costituita dalle pagine ASP concat.asp
(richiede l'inserimento delle due stringhe da concatenare),
concatConfirm.asp (chiede la conferma dei valori da
concatenare prima di avviare il servizio) e dalla pagina
ASP .NET wordlengthExecuteRemoting.aspx (invoca l'oggetto
.NET remoto e visualizza il risultato).
Il
servizio di business è costituito dal seguente
oggetto remoto .NET che estende dalla classe MarshalByRefObject
public
class MokaServant : MarshalByRefObject {
public MokaServant(){ }
e
mette a disposizione il metodo di business getLength()
public int getLength(String param) {
return param.Length;
}
}
L'oggetto
remoto è messo a disposizione dalla seguente
applicazione server :
public
class Server {
public static int Main(string [] args) {
System.Console.WriteLine("Starting ...");
in
ascolto sulla porta TCP 9999
TcpChannel
chan = new TcpChannel(9999);
ChannelServices.RegisterChannel(chan);
che
provvede a registrare l'oggetto remoto di classe RemotingSamples.MokaServant
con un endpoint (nome logico) MokaRemotingCalculator
in modalità SingleCall (viene creata una nuova
istanza dell'oggetto per ogni cliente).
RemotingConfiguration.RegisterWellKnownServiceType
(Type.GetType("RemotingSamples.MokaServant,object"),
"MokaRemotingCalculator",
WellKnownObjectMode.SingleCall);
System.Console.WriteLine("Hit <enter> to
exit...");
System.Console.ReadLine();
return 0;
}
}
La
pagina wordlengthExecuteRemoting.aspx invoca il metodo
RemotingSamples.MokaServant.getLength() dell'oggetto
remoto esplicitando l'url dell'endpoint (tcp://localhost:9999/MokaRemotingCalculator")
e visualizzando il risultato contenuto nella variabile
result:
<script
language="C#" runat="server">
string result = "";
void invokeWS(System.Object sender, System.EventArgs
e) {
TcpChannel chan = new TcpChannel();
MokaServant obj = (MokaServant)Activator.GetObject(
typeof(RemotingSamples.MokaServant),
"tcp://localhost:9999/MokaRemotingCalculator");
if (obj == null)
result=" # ERRORE ! Remote Server NON
localizzato!";
else
result="" + obj.getLength("CIAO!");
}
</script>
<html>
<head></head>
<body>
<table>
<tr>
<td>
<%= result %>
</td>
Conclusioni
In questo articolo si sono descritte tre semplici applicazioni
che saranno oggetto di integrazione nei prossimi articoli.
Nel prossimo articolo si inizierà a trattare
l'integrazione a livello d'interfaccia e a livello di
ORB. Successivamente si affronterà la possibilità
di integrare mediante messaggi, sia con JMS che con
SOAP.
Bibliografia
[JW1]
Dirk Reinshagen: Connect the enterprise with the JCA
(I),JavaWorld Gennaio 2002
[TOMCAT] http://jakarta.apache.org/tomcat/index.html
[JBOSS] http://www.jboss.org
[WLS] http://www.beasys.com
[.NET] http://msdn.microsoft.com/library/default.asp?url=/nhp/default.asp?contentid=28000519
[MOKASHOP_1] G.Puliti: "MokaShop: I parte",
MokaByte 60, Febbraio 2002
[MOKA_FAC] S.Rossini, L. Dozio:"SessionFacade",Mokabyte
N.64-Giugno 2002
[MOKA_BD] S.Rossini, L. Dozio:"Business Delegate",Mokabyte
N.65-Luglio 2002
[MOKA_SL] S.Rossini, L. Dozio:"Service Locator",Mokabyte
N.67-Ottobre 2002
[MOKA_FC] S.Rossini, L. Dozio:"Front Controller",Mokabyte
N.70-Gennaio 2003
[MOKA_MVC] S.Rossini, L. Dozio:"Model-View-Control",Mokabyte
N.70-Gennaio 2003
|