MokaByte 73- Aprile 2003 
Integrazione di applicazioni Enterprise
I parte
di
Stefano Rossini
Assicurare la possibilità di far comunicare tra loro applicazioni eterogenee (cioè sviluppate su framework, piattaforme e/o sistemi operativi diversi) sta diventando ormai un elemento chiave per l'evoluzione dei nuovi sistemi informatici. Le esigenze possono essere limitate al semplice scambio di dati o evolversi fino alla possibilità di utilizzare metodi offerti da sistemi remoti per integrare servizi di terzi nella propria applicazione. Le soluzioni di integrazione riguardano sia l'implementazione di applicazioni o di sistemi informatici creati ex novo, sia la gestione delle tecnologie aziendale preesistenti, in modo da non intaccare gli investimenti anteriori. In questa serie di articoli verranno presentati dei semplici esempi di integrazione di applicazioni Enterprise al fine di tracciare delle linee guida d'integrazione basilari.

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

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it