Realizzazione di un motore d`esecuzione per un linguaggio di

Transcript

Realizzazione di un motore d`esecuzione per un linguaggio di
Realizzazione di un motore d’esecuzione
per un linguaggio di orchestrazione di
servizi
Blite ovvero BPEL in versione lite
Paolo Panconi
15 aprile 2009
“A mio padre, a Valeria, presto mia moglie
e soprattutto a mia madre”
Indice
1
Introduzione
11
2 Linguaggi per l’orchestrazione
2.1 BPEL: uno standard per l’orchestrazione di Servizi Web
2.2 Blite, un approccio formale a BPEL . . . . . . . . . . .
2.3 Una grammatica per un compilatore . . . . . . . . . . .
2.4 Alcune osservazioni sulla semantica della correlazione .
3 Blite-se
3.1 Progetto di un motore per l’orchestrazione
3.2 Specifica dell’Engine . . . . . . . . . . .
3.3 Un modello per le attività . . . . . . . . .
3.4 Esecuzione e parallelismo . . . . . . . . .
3.5 Comunicazione ed eventi . . . . . . . . .
3.6 Contesto, FaultHandler e Compensazione
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
19
19
27
40
50
.
.
.
.
.
.
55
55
60
62
67
72
81
4 Blide
91
4.1 Un IDE per Blite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
4.2 Un esempio d’uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
5 Conclusioni e sviluppi
119
5.1 Osservazioni conclusive . . . . . . . . . . . . . . . . . . . . . . . . . . 119
5.2 Sviluppi futuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
6
INDICE
Elenco delle figure
2.1
Comunicazione asincrona con Blite . . . . . . . . . . . . . . . . . . .
30
2.2
Un parser con JJTree e JavaCC . . . . . . . . . . . . . . . . . . . . . .
45
2.3
Codice Blite, esempio delimitatori di blocco . . . . . . . . . . . . . . .
48
2.4
Codice Blite, esempio ready-to-run instance . . . . . . . . . . . . . . .
50
3.1
I moduli di Blite-se . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56
3.2
Blite-se: Engine, ProcessManager e ProcessInstace . . . . . . . . . . .
60
3.3
Blite-se: modello statico e modello dinamico . . . . . . . . . . . . . .
63
3.4
Blite-se: Gerarchia delle ActivityComponent . . . . . . . . . . . . . .
66
3.5
Diagramma di classe FlowExecutor, FlowOwner e ThreadPool . . . . .
73
3.6
Comunicazione One-Way . . . . . . . . . . . . . . . . . . . . . . . . .
79
3.7
Propagazione di un’eccezione . . . . . . . . . . . . . . . . . . . . . .
83
3.8
ExecutionContext Class Diagram . . . . . . . . . . . . . . . . . . . . .
84
4.1
Blide Schermata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
92
4.2
Il pannello “Favorites” . . . . . . . . . . . . . . . . . . . . . . . . . .
94
4.3
Menu Contestuale su di un File Blite . . . . . . . . . . . . . . . . . . .
96
4.4
Blide, l’editor per Blite . . . . . . . . . . . . . . . . . . . . . . . . . .
99
4.5
Blide, feedback alla compilazione . . . . . . . . . . . . . . . . . . . .
99
4.6
Blide, feedback alla compilazione . . . . . . . . . . . . . . . . . . . . 100
4.7
Blide: rappresentazione grafica di istanze . . . . . . . . . . . . . . . . 103
4.8
Blide: programma Blite . . . . . . . . . . . . . . . . . . . . . . . . . . 104
4.9
Codice Blite, il Servizio Spedizioni . . . . . . . . . . . . . . . . . . . . 108
4.10 Codice Blite, Clienti del Servizio Spedizioni . . . . . . . . . . . . . . . 111
4.11 Codice Blite, interfaccia Store Service . . . . . . . . . . . . . . . . . . 114
4.12 Codice Blite, interfaccia Billing Service . . . . . . . . . . . . . . . . . 114
4.13 Blide, simulazione 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
8
ELENCO DELLE FIGURE
4.14 Blide, simulazione 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
4.15 Blide, simulazione 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
4.16 Blide, simulazione 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
Elenco delle tabelle
2.1
2.2
2.3
2.4
2.5
2.6
La sintassi di Blite . . . . . . . . . . . . . . . . . .
Congruenza Strutturale per le attività e i deployment
Semantica operazionale per le attività . . . . . . . .
Regole di riduzione per deployment . . . . . . . . .
Definizione predicato match di ricezione . . . . . . .
Delimitatori di blocco per le attività strutturate . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
29
33
34
38
39
47
3.1
3.2
3.3
Metodi principali dell’interfaccia ActivityComponent . . . . . . . . . .
Factory per creare le opportune sottoclassi che implementano le attività
Interfaccia ExecutionContext . . . . . . . . . . . . . . . . . . . . . . .
64
67
90
4.1
4.2
4.3
Icone associate ai file Blite con estensione .blt . . . . . . . . . . . . . 93
Azioni che possono essere eseguite su un file Blite . . . . . . . . . . . 95
I vari tipi di nodi nell’albero Local Engines . . . . . . . . . . . . . . . 101
10
ELENCO DELLE TABELLE
Capitolo 1
Introduzione
Nell’era di Internet e della globalizzazione dell’informazione le necessità di coloro che
utilizzano il software per svolgere le loro attività primarie sono cambiate drasticamente.
Per le grandi organizzazioni (siano queste aziende private, pubbliche amministrazioni
o altro) risulta indispensabile avere un modello di attività dinamico e aperto che possa
permettere loro di modificare rapidamente le politiche e le strategie, e di dialogare in
maniera sicura ed efficiente con il maggior numero di partner e clienti. È quindi ovvio
che la risposta del mondo del software e della tecnologia non potesse che muoversi in
tal direzione.
Il primo passo è stato quello di creare, tramite processi condivisi, gli standard pubblici che permettessero di ideare una “lingua franca” tramite cui scambiare l’informazione. La creazione dello standard XML [8] ha permesso di descrivere i dati tramite
una rappresentazione testuale che autoesplicita lo schema di encoding utilizzato per
rappresentare i caratteri in sequenze di byte. In questo modo si è realizzata la completa
indipendenza dei dati dalle specifiche architetture hardware/software e l’informazione
è diventata finalmente portabile attraverso le diverse piattaforme. Insieme alla necessità di rappresentare i dati a basso livello vi è sempre quella di poterli strutturare in tipi
e forme sempre più complessi. DTD [9] prima, e XML Schema [10] poi, in maniera
definitiva, hanno permesso di risolvere brillantemente tale problematica nell’ambito di
XML stesso.
A questo punto avendo modo di rappresentare l’informazione in maniera totalmente
portabile e tipata ci si è rivolti ad individuare un modello di comunicazione che permettesse lo scambio di tale informazione. È stata individuata l’astrazione di “servizio”,
inteso semplicemente come un’interfaccia formalmente specificata ad una risorsa puramente stateless. Il concetto di “risorsa” è da intendersi in senso generale, come un
12
Introduzione
documento statico, ma anche come unità elaborativa capace di restituire dinamicamente
dei dati in base al valore di parametri ricevuti in input.
La definizione della specifica formale dei servizi non poteva avvenire che nell’ambito di XML e XML Schema e l’introduzione di WSDL [11], linguaggio direttamente
basato su questi standard, ha permesso la realizzazione di interfacce in cui si definiscono operazioni i cui parametri di input ed output sono formalmente tipati e la loro
descrizione astrae completamente dalle tecnologie utilizzate per implementarle. Mentre
WSDL rappresenta le interfacce, SOAP [13] e WSDL Binding [11] definiscono il modo
in cui l’informazione che transita per tali interfacce viene strutturata e “imbustata” in
messaggi. A questo punto non rimane altro che determinare il mezzo con cui inviare
tali messaggi. E la scelta più ovvia non poteva che essere quella di utilizzare i mezzi più
diffusi e collaudati, ovvero HTTP [14] [15] e i protocolli di Internet.
Servizi basati sull’utilizzo combinato di XML, XML Schema, WSDL, SOAP e
HTTP sono in generale individuati col termine “Servizi Web”, anche se spesso con tale
termine si tende a definire una classe più ampia di Servizi non necessariamente basati
su tutte le precedenti tecnologie.
Avere delle specifiche con un cosı̀ alto livello di descrizione formale, fa sı̀ che sia
possibile realizzare strumenti per la creazione automatica di ampie parti di codice in vari
linguaggi di programmazione. In pratica, semplicemente dalla definizione di un dato
servizio è possibile creare parte dell’implementazione di esso o parte dei client. Questa
è stata una della caratteristiche che hanno contribuito maggiormente alla diffusione dei
Servizi Web ed a determinarne il successo.
Disporre di un sistema di comunicazione cosı̀ promettente ha fatto sı̀ che si cominciasse ad utilizzarlo per definire un nuovo paradigma di programmazione e di sviluppo
di applicazioni, in cui al concetto di servizio si dovesse inevitabilmente affiancare il
concetto di stato (tutti sanno che per realizzare applicazioni poco più che banali è indispensabile mantenere uno stato). Tale paradigma è noto con il nome di SOC (Service
Oriented Computing) [17] [16]. In pratica, SOC racchiude tutte quelle attività tese alla
definizione di un modello di sviluppo software basato sull’assemblaggio di componenti
che possono essere specificati tramite i formalismi dei servizi ma che possono eventualmente mantenere anche uno stato. WS-Addressing [18], per esempio, è una tecnologia
che contribuisce alla definizione del concetto di stato all’interno degli standard dei Web
Service.
In SOC, oltre al concetto fondamentale di servizio, confluiscono diverse metodologie per la realizzazione delle funzionalità più complesse tramite la composizione di
13
funzionalità più semplici offerte dai servizi componenti. Per esempio, un approccio che
sta avendo un discreto successo è quello identificato con il termine Business Process
Oriented Approach, secondo cui ogni attività aziendale1 deve avere un preciso obiettivo (per esempio, mettere a disposizione un prodotto o un servizio eventualmente anche
solo per altre attività) e un ciclo di vita formale, secondo cui, ad un evento di inizio attività (istanziazione del processo), deve corrispondere un’evoluzione e un esito sempre
individuabile come positivo o negativo.
Ogni attività è quindi un processo composto da una serie di passi, visti esternamente come unità atomiche, ma che in realtà possono racchiudere a loro volta sottoprocessi
(composizionalità), che vengono svolti secondo regole di evoluzione precise (workflow)
che ne stabiliscono le dipendenze logiche e temporali. Le sottoattività utilizzate da un
processo possono spaziare nell’intero dominio aziendale ed essere svolte sotto la responsabilità di diverse figure dell’organigramma societario. I tempi di evoluzione delle
attività possono essere in generale fortemente variabili e dipendenti da innumerevoli
fattori. In tal senso i processi, pur trovandosi spesso a dover garantire stringenti requisiti temporali, devono fare i conti con sottoattività particolarmente riluttanti a rispettare
specifici vincoli temporali.
Nell’ambito di SOC, tale metodologia ha trovato attuazione utilizzando i Servizi
Web come tecnologia, sia per la realizzazione delle interfacce delle attività, sia per la
rappresentazione e la comunicazione dei dati, e sviluppando un linguaggio come BPEL
[3] per la codifica dei workflow dei processi. Tale linguaggio permette di utilizzare i
Servizi Web per accedere alle sottoattività e, a sua volta, espone il processo stesso come
un Servizio Web, permettendo di realizzare la composizionalità e uniformando il modo
in cui ogni risorsa è vista esternamente. Come si può intuire, un linguaggio come BPEL
risulta uno strumento molto complesso, ricco di costrutti sia per la gestione dei flussi
interni che per la realizzazione della comunicazione.
In pratica riguardo a BPEL risulta difficile sia realizzarne implementazioni a partire
dalle specifiche del linguaggio, sia utilizzare queste ultime per sviluppare applicazioni.
Attualmente BPEL è specificato da documenti, scritti in linguaggio naturale, prodotto di un processo di standardizzazione supervisionato da Oasis [5]. L’assenza di una
semantica formale può far sı̀ che si creino dei fraintendimenti e che ci possano essere
interpretazioni diverse del medesimo concetto, facendo sı̀ che le implementazioni infine risultino incompatibili. Per esempio, possono risultare di difficile interpretazione la
1
Il termine aziendale deve essere inteso in senso generale di organizzazione o di entità in cui più
soggetti collaborano per il raggiungimento di un fine comune secondo regole e strategie condivise.
14
Introduzione
relazione fra le Multiple Start Activity e il meccanismo di gestione dei conflitti nell’attribuzione dei messaggi alle diverse istanze concorrenti di un processo, o alcuni aspetti
legati alle problematiche di terminazione delle attività e di compensazione.
Il processo di realizzazione di applicazioni BPEL risulta difficile e fortemente soggetto ad errori anche per la presenza di caratteristiche complesse quali: parallelismo,
concorrenza, terminazione forzata di attività, correlazione e compensazione, quest’ultima utilizzata per la realizzazione di “Long-Running Transaction”, e forse ancora non
compresa in tutti i suoi aspetti e potenzialità dagli utilizzatori. In tal senso un processo
di analisi e di formalizzazione di tali costrutti è certamente utile, sia per chi si trova ad
utilizzarli, sia per coloro che ne realizzano l’implementazione a partire da specifiche in
linguaggio naturale, in modo tale da renderne più chiaro il significato ed eventualmente
semplificarne gli aspetti più delicati.
Un approccio, per cercare di affrontare queste problematiche, è stato quello di utilizzare i metodi formali e la teoria delle “algebre di processo” per la definizione di una
semantica formale e per la realizzazione successiva di una piattaforma per la verifica
e la dimostrazione di proprietà di applicazioni basate su BPEL. Con tale scopo è stato
ideato Blite [1], una variante semplificata di BPEL, che riproduce alcune delle sue funzionalità più caratteristiche, cercando d’altra parte di semplificare alcuni aspetti ritenuti
marginali nella realizzazione del modello Process Oriented. In definitiva in Blite è stato
selezionato un core di BPEL, per cui fosse abbastanza agevole definire una semantica
formale e con cui, tramite “encoding”, fosse possibile sostanzialmente riproporre tutte
le funzionalità del linguaggio.
Il lavoro svolto in questa tesi è da considerarsi a supporto di tale attività, come
un’ulteriore verifica e confronto del processo di astrazione teorica e formale con gli
aspetti più concreti e tecnologici, legati all’implementazione di un linguaggio per l’orchestrazione distribuita di servizi. Infatti l’obiettivo primario è stato quello di realizzare
un’implementazione di Blite che potesse essere il più vicino possibile, per caratteristiche ed applicabilità, ai motori di esecuzione di BPEL attualmente disponibili. Se da un
lato Blite vuole essere un’analisi di BPEL, fatta tramite un processo di astrazione e formalizzazione, il lavoro qui esposto vuole essere a sua volta un’analisi di tale processo,
nel senso che ne vuole verificare la compatibilità e l’attinenza con le problematiche più
concrete e tecnologiche presenti nell’ambito dell’orchestrazione di servizi. Di fatto il
lavoro svolto rappresenta un test critico di come i costrutti formali, utilizzati per fornire
la semantica del linguaggio, possano essere realmente implementati in scenari di software di produzione e che quindi continuino a mantenere un legame a doppio filo con il
15
mondo delle tecnologie, da cui hanno tratto iniziale ispirazione.
Oltre che nella direzione sopra esposta, la nostra attività di verifica di Blite si è
mossa verso la creazione di strumenti che possano permettere direttamente di scrivere e
simulare in maniera rapida processi definiti con esso. Ci è sembrato che un modo molto
utile per verificare la funzionalità del linguaggio creato, fosse quello di poter scrivere ed eseguire programmi e di avere una rappresentazione, il più espressiva possibile,
dell’avvenuta esecuzione e di come si sia attuata la comunicazione tra i processi.
In pratica sono stati realizzati due progetti software basati su tecnologia Java [21]:
Blite-se e Blide.
Blite-se (Blite Service Engine) realizza l’insieme delle funzionalità necessarie alla
esecuzione di processi definiti tramite Blite, o meglio, tramite una sintassi leggermente
modificata. In particolare fornisce un compilatore (realizzato con la tecnologia JavaCC e
JJTree [22]) e un modello statico per la rappresentazione dei programmi. Il compilatore,
come risultato dell’analisi sintattica, produce un modello ad oggetti rappresentante il
programma stesso. Tale modello, di seguito riferito come Modello Statico, è in pratica
l’albero sintattico i cui nodi sono oggetti che espongono funzionalità specifiche, in base
alla categoria sintattica rappresentata.
In Blite-se è realizzato anche il motore (Engine) per l’esecuzione dei processi secondo la semantica formale definita in [1]. Tale componente software è stata progettata
in riferimento a specifiche che tenessero il più possibile conto delle problematiche reali
degli ambiti produttivi (efficienza, scalabilità, requisiti di memoria, ecc). È stato ideato
inoltre, un modello di esecuzione basato sul pattern Composite [29], secondo cui ogni
attività è rappresentabile come un componente che apporta il proprio contributo all’esecuzione dell’istanza di processo. A partire dal modello statico, a runtime viene costruito
passo passo un Modello Dinamico, l’albero dei componenti/attività, e l’esecuzione procede tramite l’invocazione ricorsiva del metodo doActivity() su tali camponenti. In sintesi, il nostro modello di esecuzione può essere definito Activity Centric, nel senso che
le varie attività saranno responsabili nel realizzare la loro esecuzione, ma anche nel collaborare, per far sı̀ che i flussi globali dei processi evolvano secondo quanto specificato
dalla semantica.
L’Engine è stato realizzato astraendo dagli aspetti più concreti e tecnologici, quali la
comunicazione e il deployment. L’Engine, per comunicare con le porte dei servizi esterni, utilizza un’interfaccia (EngineChannel), che definisce un modello di comunicazione totalmente generico, utilizzabile per realizzare molteplici protocolli per lo scambio
di messaggi, dal più semplice “fire and forget” ai più complessi, che necessitano un
16
Introduzione
mantenimento dello stato. Le implementazioni di tale interfaccia verranno fornite dal
componente software Environment, che realizza un contenitore per l’Engine nell’ambito
di una precisa tecnologia di comunicazione.
Attualmente è stato creato un Local Environment capace di eseguire localmente
più Engine e simulare la rete e la comunicazione remota, ma sarebbe interessante
realizzare Environment capaci di supportare direttamente gli standard tipici della
tecnologia dei Servizi Web, come WSDL, SOAP e HTTP, e poter quindi far dialogare
i nostri programmi Blite con ogni servizio disponibile in Internet. Altre possibilità
potrebbero essere quelle di avere Environment capaci di supportare lo standard JBI
[24] e poter integrare il nostro Engine con ESB (Enterprise service bus) che supportano
tale tecnologia; o Environment integrabili con framework per la comunicazione remota
come IMC [30].
Blide (Blite Integrated Development Environment) costituisce il secondo progetto
software realizzato. Esso implementa un vero e proprio IDE che permette di scrivere rapidamente e testare, tramite l’esecuzione di simulazioni, i programmi Blite. Oltre
alle consuete funzionalità di gestione ed editing dei file, compilazione ed esecuzione
integrata dei programmi, Blide fornisce un formalismo e una tecnologia per la rappresentazione grafica dell’esecuzione delle istanze e la comunicazione fra esse. In pratica
le varie istanze vengono monitorate durante la loro esecuzione e vengono memorizzate
le informazioni necessarie a darne una rappresentazione grafica. Alcune parti dell’interfaccia grafica mostrano all’utente le varie istanze eseguite e in esecuzione, cosicché
l’utente può scegliere fra le istanze andando a comporre rappresentazioni della loro
interazione.
Blide è stato realizzato tramite il progetto NetBeans Platform [27], un framework
studiato per facilitare lo sviluppo di applicazioni Java con interfaccia grafica. È stato
scelto tale progetto per la sua completezza e per il modello architetturale offerto.
NetBeans Platform permette allo sviluppatore di realizzare le proprie applicazioni
componendo diversi moduli, ciascuno dei quali offre una funzionalità specifica. La
modularizzazione molto fine e la gestione formale delle dipendenze permettono di
costruire applicazioni riuscendo a selezionare in modo molto mirato solamente i moduli
realmente necessari e quindi a mantenere limitate le dimensioni complessive dell’applicazione. In questo modo è stato possibile realizzare un modello di distribuzione
dell’applicazione basato su Java WebStart [26], tramite il quale gli utenti possono eseguire, istallare e aggiornare in maniera trasparente l’applicazione da una pagina Web,
17
semplicemente selezionando un link. Dalla pagina http://code.google.com/p/blite-se/ è
possibile istallare e mettere in esecuzione la versione più recente di Blite sulla propria
macchina, per poi effettuare eventualmente anche esecuzioni offline.
Tutto il progetto software realizzato per questa tesi è disponibile online alla pagina
http://code.google.com/p/blite-se/, da cui è possibile ottenere le distribuzioni binarie, la
documentazione e i sorgenti. Questi ultimi sono gestiti con la tecnologia SVN [35],
messa a disposizione da Google tramite il progetto Google Code [36] e sono rilasciati
sotto la licenza GNU General Public License v3 [37].
Dopo aver presentato una panoramica delle motivazioni e del lavoro da noi svolto
andiamo a illustrare i contenuti di questa tesi. Nel Capitolo 2 viene introdotto il linguaggio BPEL per l’orchestrazione di servizi, partendo da una presentazione ad alto livello
dei suoi costrutti caratteristici; successivamente viene presentato Blite, fornendone sintassi e semantica operazionale. L’implementazione del compilatore per Blite necessita
di una grammatica formale, e nella Sezione 2.3 vengono presentati i procedimenti che ci
hanno portato ad ottenerla, a partire dalla sintassi astratta definita in Sezione 2.2. Il capitolo si chiude con una sezione in cui vengono commentati alcuni aspetti della semantica
rispetto alle problematiche d’implementazione.
Nel Capitolo 3 viene presentato il progetto software Blite-se. Dopo aver fornito
una panoramica del progetto e dei principali moduli che lo compongono si presenta nel
dettaglio l’architettura del motore di esecuzione, mettendo in evidenza il modello per
le attività, la gestione del parallelismo e della comunicazione, l’implementazione dei
contesti, con la gestione delle eccezioni e della compensazione.
Il Capitolo 4 fornisce una panoramica delle funzionalità offerte da Blide e dalla sua
interfaccia utente e si conclude con la presentazione di un semplice caso d’uso.
Il Capitolo 5 presenta un riepilogo critico del lavoro svolto e si delinea i possibili
sviluppi ulteriori.
18
Introduzione
Capitolo 2
Linguaggi per l’orchestrazione
Questo capitolo offre una panoramica ad alto livello di BPEL, il linguaggio che è diventato lo standard per l’orchestrazione dei Servizi Web. Successivamente viene introdotto
Blite, una variante semplificata di BPEL, che ne riproduce alcune delle funzionalità più
caratteristiche al fine di definire una semantica formale. Tale semantica è presentata e
commentata in dettaglio nella Sezione 2.2. A partire dalla sintassi astratta di Blite si
ricava una grammatica in notazione EBNF utilizzata successivamente per lo sviluppo
di un compilatore; tale procedimento è presentato nella Sezione 2.3. Il capitolo si chiude con una disamina di alcuni aspetti della semantica rispetto a problematiche legate
all’implementazione.
2.1
BPEL: uno standard per l’orchestrazione di Servizi
Web
Abbiamo detto come SOA e i Servizi Web siano una delle risposte più recenti alla
necessità di integrare funzionalità applicative eterogenee e di fornire un modello di sviluppo software che sia il più efficace possibile rispetto alla natura dinamica dei domini
applicativi tipici delle grandi realtà aziendali.
Alla base della metodologia SOA vi è l’approccio “bottom-up”, secondo cui è possibile comporre le funzionalità di base offerte dalle diverse applicazioni aziendali per
crearne di più complesse e articolate. Gli standard e i linguaggi dei Servizi Web forniscono il supporto tecnologico e formale per realizzare tale integrazione che nella maggior parte degli scenari reali prevede di dover far comunicare tecnologie e formalismi del
tutto eterogenei e incompatibili. Una delle possibilità più interessanti è quella di poter
20
Linguaggi per l’orchestrazione
utilizzare il patrimonio storico (“legacy”) del software aziendale tramite standard moderni e aperti, e poter fare dialogare le applicazioni di ultima generazione con quelle più
“mature”, che spesso mantengono un alto valore aziendale ma si basano su tecnologie e
metodologie proprietarie ormai difficilmente manutenibili.
Predisporre i componenti e renderli disponibili secondo il paradigma dell’ Architettura Orientata ai Servizi certo non realizza tutte le necessità di un complesso sistema
aziendale, il passo successivo non può essere che quello di comporre le singole funzionalità di base per realizzare i flussi operativi che implementano le reali politiche e
strategie di business. Le attività aziendali quindi possono essere rappresentate come
processi (“Business Process”) che raggruppano le singole funzionalità secondo precise regole aziendali (“Business Rules”) e tramite primitive di aggregazione che attuano
dipendenze temporali e logiche fra i diversi componenti pubblicati come servizi (“Service Orchestration”). Se si può pensare che le singole funzionalità siano i mattoni del
business e che abbiano una certa robustezza temporale, in termini di specifica e implementazione, i processi al contrario possono essere fortemente dinamici e mutevoli, per
poter facilmente adeguarsi alle necessità sempre nuove che si presentano nelle attività
di un’azienda.
Si capisce quindi come nasca la necessità di un formalismo specifico per la definizione e realizzazione di tali processi. In generale si vorrebbe poter disporre di un
linguaggio semplice e flessibile che possa essere utilizzato a diversi livelli aziendali,
compreso e adoperato dalle diverse figure professionali che partecipano alla definizione e attuazione dei processi stessi. Si vorrebbe disporre non solo di un linguaggio di
programmazione utile ai tecnici del software ma anche di un formalismo utilizzabile
dai manager e dagli esperti dei domini applicativi, che possa realizzare una piattaforma
comune per la collaborazione fra le diverse aree disciplinari.
È proprio come risposta a tale necessità che si propone BPEL, un linguaggio
basato su XML per la definizione di processi aziendali realizzati come composizione
di funzionalità esposte da Servizi Web. BPEL storicamente nasce dalla fusione di
due tecnologie sviluppate indipendentemente all’inizio degli anni 2000: WSFL (Web
Service Flow Language) di IBM [6] e XLANG di Microsoft [7]. La prima versione
del linguaggio (BPEL4WS “Business Process Execution Language for Web Services Version 1.0” [2]) risale al 31 Luglio 2002 e fu prodotta dal lavoro congiunto di grandi
aziende come IBM, BEA, SAP, Siebel e Microsoft. Dall’Aprile del 2003 il lavoro di
stesura della versione successiva (1.1 [3]) è stato affidato alla supervisione di Oasis
[5], società nata con il compito di realizzare standard aperti e condivisi dalla comunità
2.1 BPEL: uno standard per l’orchestrazione di Servizi Web
21
internazionale; al 5 Maggio 2003 è datato il rilascio di questa versione nella stesura
ufficiale. Oasis è anche la curatrice della versione 2.0 dello standard (WS-BPEL “Web
Services Business Process Execution Language Version 2.0” [4]) che ha visto il primo
rilascio ufficiale in data 11 Aprile 2007.
BPEL, come molti linguaggi legati alla tecnologia dei servizi web, ha una sintassi
basata su XML. Tramite tale sintassi è possibile descrivere un business process definendo una logica di orchestrazione che determina le elaborazioni interne e le interazioni
con i partner del processo, le quali sono, in entrambi i casi prodotte dall’esecuzione di
attività definite sintatticamente in forma di tag XML.
In BPEL possono essere date definizioni di processi eseguibili, le quali comprendono tutti i dettagli relativi ai meccanismi interni del processo e descrivono in maniera
completa l’orchestrazione dei servizi web partner; processi di questo tipo possono essere eseguiti da un motore di esecuzione BPEL. BPEL permette anche di definire processi
astratti, che costituiscono una descrizione del flusso di informazioni tra i partner ed il
processo e non specificano i dettagli delle elaborazioni interne al processo; un processo
di questo tipo non può essere eseguito, poiché mancano i dettagli relativi all’orchestrazione e descrive piuttosto una coreografia di messaggi. Noi siamo interessati alla descrizione completa dell’orchestrazione di un insieme di servizi web, pertanto prenderemo
in considerazione solo i processi eseguibili.
La definizione di un processo BPEL include la definizione delle relazioni tra il processo ed i suoi partner, rappresentate dai cosiddetti partner link. Tra le definizioni
WSDL utilizzate dal processo devono essere presenti le definizioni di uno o più partner
link type, espresse tramite uno o più elementi della forma:
<partnerLinkType name="ncname">
<role name="ncname">
<portType name="qname"/>
</role>
<role name="ncname">?
<portType name="qname"/>
</role>
</partnerLinkType>
Ad ogni partner link type viene associato un nome e due ruoli, ognuno dei quali
rappresenta una delle due estremità del link ed indica la specifica interfaccia (port type)
22
Linguaggi per l’orchestrazione
WSDL che deve essere implementata dall’entità posta a tale estremità (il processo o
uno dei suoi partner). Nel caso in cui uno dei due ruoli usufruisce delle operazioni
dell’altro senza che ad esso sia richiesto di fornire specifici servizi, viene indicato un
solo elemento <role> (quello relativo all’estremità a cui sono richieste funzionalità
specifiche). Sottolineiamo che la definizione dei partner link type deve essere contenuta
in un documento WSDL. Di fatto i partner link type a due ruoli introducono la novità
rispetto a WSDL e servono ad estendere il concetto di contratto ad una comunicazione
asincrona in cui il richiedente del servizio può essere non noto in fase di sviluppo del
provider.
La definizione del processo BPEL invece contiene le definizioni dei partner link
veri e propri, espresse da uno o più elementi <partnerLink> annidati nell’elemento
<partnerLinks>:
<partnerLinks>
<partnerLink name="ncname" partnerLinkType="qname"
myRole="ncname"? partnerRole="ncname"?>+
</partnerLink>
</partnerLinks>
Ciascun partner link viene definito indicando il nome del partner link type e indicando quali sono il ruolo del processo (tramite l’attributo myRole) ed il ruolo del partner
(tramite l’attributo partnerRole).
Una caratteristica importante di BPEL è la possibilità di utilizzare delle strutture dati
particolari, dette endpoint reference, che rappresentano l’indirizzo fisico di un partner
del processo. Gli endpoint reference possono essere copiati da una variabile ad un’altra
oppure possono essere inviati all’interno dei messaggi trasmessi tra processo e partner;
ciò permette di determinare gli indirizzi fisici degli stessi partner in modo dinamico,
durante il corso dell’esecuzione del processo.
In questo modo un processo BPEL non deve per forza conoscere fin dall’inizio i
suoi partner effettivi e come precedentemente fatto notare, la combinazione endpoint
reference con i partner link type bidirezionali permette di realizzare l’indipendenza dell’implementazione e della locazione di una parte della comunicazione asincrono pur
lasciando la possibilità di un controllo statico dei tipi.
La possibilità di modificare le connessioni tra il processo ed i propri partner in modo
dinamico caratterizza in modo importante il linguaggio BPEL e rimanda al passaggio
dei nomi (name-passing) attuato nelle algebre di processo come il π-calculus.
2.1 BPEL: uno standard per l’orchestrazione di Servizi Web
23
Un processo BPEL può essere eseguito su un dato motore di esecuzione, che ha
naturalmente anche la funzione di server, cioè la funzione di accogliere le richieste che
provengono dai client che invocano le operazioni dell’interfaccia WSDL del processo.
Uno stesso processo può essere istanziato più volte, in modo che ogni istanza provveda a
soddisfare una specifica richiesta pervenuta e che più richieste possano essere processate
contemporaneamente; solo certe attività interne del processo possono determinare la
creazione di una nuova istanza e tipicamente queste sono attività per la ricezione di un
messaggio.
Affinché l’esecuzione di ogni istanza avvenga in modo corretto può essere necessario inserire nei messaggi scambiati tra processo e partner delle informazioni di correlazione, che permettono di associare ciascun messaggio all’istanza corretta a cui è
destinato o da cui proviene. Tali informazioni di correlazione sono definite attraverso
il concetto di proprietà e vengono associate ai dati contenuti nei messaggi mediante il
meccanismo dell’aliasing delle proprietà.
<property name="ncname" type="qname"/>
<propertyAlias propertyName="qname" messageType="qname"
part="ncname" query="queryString"/>
Una proprietà è definita tramite un elemento <property> associando un nome ad
un tipo di dati. Ad esempio la proprietà CodiceFiscale definisce un’informazione di tipo
stringa (che corrisponde al codice fiscale di un certo utente):
<property name="CodiceFiscale" type="xsd:string"/>
Tale proprietà deve essere individuata all’interno dei messaggi scambiati tra processo e partner. Ciò avviene in virtù della funzione di corrispondenza definita attraverso gli elementi propertyAlias. Con il codice di esempio che segue, si dichiara che la proprietà CodiceFiscale sarà contenuta nei messaggi con formato
richiestaConteggioFiscale ed esattamente nella parte cfUtente:
<propertyAlias propertyName="CodiceFiscale"
messageType="richiestaConteggioFiscale"
part="cfUtente"/>
</bpws:propertyAlias>
24
Linguaggi per l’orchestrazione
Tipiche informazioni di correlazione possono essere il numero di un ordine, il codice
di un cliente, l’identificativo di una transazione, etc. Quando le informazioni contenute
nei messaggi inviati o ricevuti dal processo devono essere confrontate con le informazioni di correlazione possedute dal processo, le attività tramite cui tali messaggi sono
inviati o ricevuti devono indicare uno o più correlation set, che sono insiemi di proprietà
tra di loro attinenti. I correlation set che verranno usati dal processo devono essere
dichiarati in un elemento <correlationSets> con la seguente sintassi:
<correlationSets>?
<correlationSet name="ncname" properties="qname-list"/>+
</correlationSets>
dove qname-list è una lista di nomi di proprietà separati da uno spazio.
Di seguito è mostrata la struttura XML di una definizione di processo BPEL
<process name="pName" ...>
<partnerLinks>?
...
</partnerLinks>
<partners>?
...
</partners>
<variables>?
...
</variables>
<correlationSets>?
...
</correlationSets>
<faultHandlers>?
...
</faultHandlers>
<compensationHandler>?
...
</compensationHandler>
<eventHandlers>?
...
</eventHandlers>
2.1 BPEL: uno standard per l’orchestrazione di Servizi Web
25
activity <!-- attivita’ principale del processo -->
</process>
L’attività principale del processo indicata activity è una delle attività elementari o
strutturate di BPEL e definisce il comportamento del processo.
Le attività BPEL sono i blocchi con i quali si definisce la logica di orchestrazione del
processo e si dividono in elementari e strutturate. Le attività elementari sono elencate
di seguito, accompagnate da una breve descrizione degli effetti della loro esecuzione:
<receive> il processo rimane in attesa della ricezione di un messaggio corrispondente all’invocazione, da parte di un partner, di una delle operazioni contenute in una
delle interfacce WSDL del processo stesso;
<reply> il processo invia un messaggio ad un partner in risposta all’invocazione
-da parte dello stesso partner- di un’operazione di tipo request-response del processo;
<invoke> il processo invia un messaggio per invocare un’operazione di tipo
request-response oppure one-way contenuta nell’interfaccia WSDL di un partner (nel
caso di operazione request-response, dopo avere inviato il messaggio di richiesta, il processo attende la ricezione di una risposta orrelata, che può anche essere un messaggio
di errore);
<assign> il processo aggiorna il contenuto di una o più variabili, attraverso uno
o più assegnamenti elementari che vengono eseguiti in modo atomico (cioè tutti o
nessuno);
<wait> il processo attende senza fare nulla fino allo scadere di un timeout o fino a
che non si raggiunge un certo istante di tempo (detto deadline);
<empty> l’esecuzione di questa attività non ha alcun effetto (spesso viene usata per
sincronizzare attività concorrenti o per ignorare un errore);
<throw> l’esecuzione di questa attività provoca il sollevamento di un’eccezione,
cioè notifica al resto del processo una situazione anormale o di errore;
<compensate> vengono annullati gli effetti di un’attività precedentemente
completata con successo, cioè senza che essa abbia sollevato eccezioni;
<terminate> l’esecuzione del processo viene interrotta in maniera immediata.
Sottolineiamo che un processo BPEL implementa un’operazione WSDL di tipo
request-response tramite una coppia di attività receive e reply, mentre a un’operazione di
tipo one-way viene implementata attraverso una singola attività receive. Un’operazione
contenuta nell’interfaccia di un partner viene invocata dal processo mediante l’attività
invoke.
Di seguito descriviamo le attività strutturate:
26
Linguaggi per l’orchestrazione
<sequence> le attività annidate tra i tag <sequence> e </sequence> vengono
eseguite una dopo l’altra nell’ordine in cui compaiono tra i due tag;
<switch> l’attività <switch> definisce un insieme di attività alternative, associate
a condizioni in base alle quali viene selezionata ed eseguita una sola di tali attività;
<while> l’attività annidata nel costrutto while viene eseguita ripetutamente fino a
che una data condizione di controllo è verificata;
<pick> si attende fino a che non viene ricevuto uno specifico messaggio,
corrispondente all’invocazione di una delle operazioni WSDL del processo;
<flow> le attività annidate tra i tag <flow> e </flow> vengono eseguite in modo
concorrente;
<scope> un’attività <scope> corrisponde ad un’unità logica di elaborazione e definisce un contesto (in inglese scope) per l’attività principale annidata nel tag <scope>,
cioè indica quali sono le variabili locali, i gestori e delle eccezioni, l’attività di
compensazione ed i gestori degli eventi dell’attività principale.
BPEL utilizza un meccanismo di gestione degli errori chiamato Long-Running (Business) Transaction (LRTs), che permette di controllare in modo flessibile l’esecuzione
di transazioni long-running. Quando si ha a che fare con questo tipo di transazioni è necessario poter definire delle procedure di compensazione, cioè delle attività che tentano
di annullare gli effetti delle transazioni completate con successo. Un’attività di compensazione può essere invocata solo dopo che la transazione -per la o quale è stata definitaha completato con successo la sua esecuzione e se -in seguito- si verifica qualche situazione eccezionale che richieda l’annullamento degli effetti della suddetta transazione.
Nel caso delle transazioni sul web non è solitamente possibile eseguire delle operazioni di rollback totale come nel caso delle transazioni tradizionali su una base di dati (in
quel caso il rollback riporta la base di dati esattamente allo stato precedente l’esecuzione
della transazione); per le transazioni su web invece si programmano delle procedure di
rollback ad hoc, in base alla specifica business logic, che cercano per quanto possibile
di annullare gli effetti della transazione per la quale sono state definite.
L’elemento <faultHandlers> annidato in un’attività scope (o nello stesso elemento radice <process>) definisce un insieme di gestori delle eccezioni o fault handler, che sono attività che vengono eseguite se l’attività principale dello scope (o del
processo) genera un’eccezione. Infatti, se viene sollevata un’eccezione, l’esecuzione
dell’attività principale si interrompe e viene selezionato uno dei fault handler in base al
tipo dell’eccezione.
L’elemento <compensationHandler> annidato in un’attività scope (o nello stesso
2.2 Blite, un approccio formale a BPEL
27
elemento radice <process>) definisce invece l’attività di compensazione dello scope
(o del processo stesso). Tale attività può essere invocata dopo il completamento dello
scope attraverso l’esecuzione di un’attività compensate, che può essere contenuta in
un fault handler o nel compensation handler dello scope in cui è annidato lo scope da
compensare.
Nonostante BPEL sia ampiamente documentato da specifiche e standard ufficiali
e sia disponibile in diverse implementazioni, le problematiche relative al suo uso non
mancano e alcune di esse possono essere attribuite alla mancanza di una semantica formale. Il processo di realizzazione di applicazioni BPEL risulta difficile e fortemente
soggetto ad errori anche per la presenza di costrutti complessi come: il parallelismo,
la concorrenza, la terminazione forzata di attività, la correlazione e la compensazione,
quest’ultima utilizzata per la realizzazione di “Long-Running Transaction” e forse ancora non compresa in tutti i suoi aspetti e potenzialità dagli utilizzatori. In tal senso si
è pensato che un processo di analisi e di formalizzazione di tali costrutti potesse essere
utile sia per chi si trova ad utilizzarli sia per coloro che ne definiscono le specifiche in
linguaggio naturale, in modo tale da renderne più chiaro ed eventualmente semplificarne
gli aspetti più delicati.
Un approccio, per cercare di affrontare queste problematiche, è stato quello di
utilizzare la teoria dei metodi formali e delle algebre di processo per la definizione
di una semantica formale e per la realizzazione successiva di una piattaforma per la
verifica e la dimostrazione di proprietà di applicazioni basate su BPEL. Con tale scopo
è stato creato Blite, una variante semplificata di BPEL, che riproduce alcune delle sue
funzionalità più caratteristiche cercando d’altra parte di semplificarne alcuni aspetti
ritenuti marginali nella realizzazione del modello Process Oriented.
Di seguito andremo a descrivere la sintassi e la semantica originali di Blite e le
versioni leggermente modificate di cui è stata realizzata l’implementazione.
2.2
Blite, un approccio formale a BPEL
Come già detto Blite è un linguaggio che può essere visto come una semplificazione
di BPEL, nel senso che ne ripropone solo alcuni aspetti essenziali come: partner link,
process e activity termination, message correlation, fault handler e compesation handler,
e ne tralascia altri come timeout, eventi, termination handler e flow graph.
28
Linguaggi per l’orchestrazione
Anche il modello di invocazione dei servizi è stato semplificato. In BPEL difatti
sono possibili sia invocazioni one-way che request-response. Le prime sono invocazioni asincrone, in cui il client dopo aver invocato può continuare la sua elaborazione
senza dover attendere alcuna risposta, le seconde invece realizzano invocazioni sincrone, in cui l’operazione di richiesta blocca il client fino al sopraggiungere del risultato.
In Blite è stato di fatto scelto di supportare solamente la comunicazione asincrona, in
quanto il comportamento sincrono può essere sempre riprodotto tramite l’opportuna
sequenzializzazione di operazioni asincrone.
In generale in BPEL il meccanismo di instradamento dei messaggi alle opportune istanze di processo è realizzato tramite la Correlazione (Message Correlation) che
discrimina rispetto ai valori applicativi contenuti in determinate parti del corpo stesso dei messaggi. In Blite viene rappresentata tale metodologia e si tralasciano tecniche alternative, come il WS-Addressing, che guida l’indirizzamento in base al valore di
metainfomazioni contenute negli header1 .
In definitiva è stato selezionato un core di BPEL per cui fosse abbastanza agevole
definire una semantica formale e con cui, tramite “encoding”, fosse possibile riproporre
tutte le funzionalità del linguaggio. In [1] vengono presentate in dettaglio le regole
per la realizzazione dell’encoding, tramite Blite, dei costrutti BPEL che non sono stati
inclusi in tale core.
La sintassi di Blite è data in Tabella 2.1. La categoria sintattica Servizio (Service) rappresenta sia la definizione di processo [r • a f ], che le istanze nella loro esecuzione a runtime con uno specifico stato della memoria, µ ⊢ a; quest’ultima forma non è utilizzabile direttamente dal programmatore, ma serve per poter introdurre
una rappresentazione della fase di esecuzione indispensabile per definire la semantica
operazionale.
Una definizione di servizio è semplicemente uno Scope (o Contesto) in cui è definita
una Start Activity r e un Fault Handler a f . Difatti s’impone che le attività iniziali di un
processo siano un sotto-insieme di tutte quelle possibili e che in pratica la prima attività
operativa (o Basic Activity) sia una ricezione. Le attività sono divise in due categorie
principali: le Basic Activity e le Structured activities. Le prime sono le attività primitive,
cioè individuano le operazioni di base compiute da un’istanza di processo, le seconde
sono una composizione strutturale di quest’ultime. Di fatto le attività di base sono costituite dall’invocazione asincrona inv ℓ i o x̄ di un’operazione remota o su un partner link
1
WS-Addressing, sebbene non faccia direttamente parte delle specifiche del linguaggio BPEL, viene
utilizzato in numerose implementazioni come tecnica aggiuntiva alla Message Correlation.
2.2 Blite, un approccio formale a BPEL
Basic activities
b ::= inv ℓ i o x̄ | rcv ℓ r o x̄ | x := e
| empty | throw | exit
Structured activities a ::= b | if(e){a1 }{a2 } | while(e) {a}
P
r
| a1 ; a2 |
j∈J rcv ℓ j o j x̄ j ; a j
| a1 | a2 | [a • a f ⋆ ac ]
P
r
Start activities
r ::= rcv ℓ r o x̄ |
j∈J rcv ℓ j o j x̄ j ; a j
| r ; a | r1 | r2 | [r • a f ⋆ ac ]
29
invoke, receive, assign
empty, throw, exit
basic, conditional, iteration
sequence, pick (with | J | > 1)
parallel, scope
receive, pick
sequence, parallel, scope
Services
s ::= [r • a f ] | µ ⊢ a | µ ⊢ a , s
definition, instance, multiset
Deployments
d ::= {s}c | d1 k d2
deployment, composition
Tabella 2.1: La sintassi di Blite
ℓ i con parametri attuali x̄, dall’attesa dell’invocazione rcv ℓ r o x̄ dell’operazione locale
o tramite il partner link ℓ r con parametri formali x̄, dall’assegnazione della valutazione
dell’espressione e alla variabile x, dalla attività vuota empty, dalla sollevazione di una
eccezione throw e dall’operazione di terminazione d’istanza exit.
Le attività strutturate invece sono costituite dalla scelta condizionale if(·){·}{·}, dalla iterazione while(e) {a}, dalla composizione sequenziale di sottoattività a1 ; a2 , dalla
P
scelta esterna su un set non vuoto di possibili porte j∈J rcv ℓ jr o j x̄ j ; a j 2 , dalla composizione parallela di attività a1 | a2 e per finire dal costrutto di scope o contesto [a • a f ⋆ ac ],
dove ad un’attività pricipale detta Contest Activity a viene associato un Fault Handler
a f e un Compensation Handler ac .
La sintassi afferma che le operazioni di comunicazione sono definite sui partner link
i
ℓ per l’invocazione e ℓ r per la ricezione. Un’ulteriore imposizione viene fatta sulla
struttura sintattica di tali oggetti, richiedendo che essi siano tuple di uno o al massimo
due elementi, con la seguente ulteriore restrizione:






h
u
,
p
i
 hp, ui

r
con p staticamente noto e u evetualemente variabile
ℓ
=
ℓi = 



 hpi
 hui
Di fatto i partner link possono essere monodirezionali h·i o bidirezionali hs1, s2i. In
quest’ultimo caso sottintendono una comunicazione asincrona richiesta-risposta secon2
La scelta può essere anche espressa tramite l’operatore binario · + ·.
30
Linguaggi per l’orchestrazione
x := "s1"
rcv <"s1", z> op1 x
...
inv <x, "s2"> op1 value
...
inv <z> op2 k
...
rcv <"s2"> op2 y
Figura 2.1: Realizzazione della comunicazione asincrona richiesta-risposta con i construtti
della sintassi di Blite
do cui ad un’invocazione sul servizio s1 quest’ultimo risponderà in maniera asincrona
con una risposta sul servizio s2. Di fatto s’impone anche che i nomi dei servizi su cui si
eseguono le ricezioni siano staticamente noti3 . Un esempio di comunicazione asincrona
con i costrutti definiti da Blite è rappresentato in Figura 2.1, dove è esplicitato il fatto
che i nomi dei servizi s1 e s2 oggetto delle invocazioni sono determinati a runtime.
La composizione distribuita di diversi processi viene definita dalla categoria sintattica Deployments. Un termine d1 k d2 rappresenta la contemporanea esecuzione di tutte
le istanze ottenute dalle definizioni presenti in d1 e in d2 . Un Deployment {s}c è formato dalla definizione di processo s con tutte le istanze locali a cui è stato associato il
Correlation Set c. Tale insieme individua fra tutte le variabili presenti nella definizione
del processo quelle che dovranno essere considerate per valutare la correlazione di un
messaggio ad una particolare istanza. Vedremo come la semantica di Blite definisca tale
processo di attribuzione di un messaggio a una istanza e come tale semantica sia stata
implementata del nostro engine di esecuzione.
In generale si impone la restrizione che un insieme di deployments sia ben formato,
nel senso che i nomi dei partner link utilizzati per le ricezioni non siano condivisi fra
diversi deployment. In questo modo ogni definizione di processo avrà i propri nomi
di servizio univoci e dato un nome sarà sempre possibile individuare un deployment
specifico.
In seguito, si farà uso della notazione ¯·, per denotare tuple di valori o variabili, x̄
3
Quest’ultima imposizione rispecchia il fatto che staticamente sono noti i contratti e fissate le locazioni
su cui essi sono disponibili; a runtime le varie istanze si possono scambiare quest’ultima informazione,
ma il modello non prevede che possano nascere ne nuovi contratti ne nuove locazioni dove questi siano
implementati.
2.2 Blite, un approccio formale a BPEL
31
può essere considerata l’abbreviazione di hx1 , . . . , xh i (con h ≥ 0). L’ulteriore notazione
˜· indicherà tuple speciali di uno o al massimo due elementi (p̃ starà per hp1 , p2 i o
hp1 i). Inoltre l’operatore · : · permetterà di ottenere la tupla hp, u, x1 , . . . , xh i come
concatenazione di hp, ui e hx1 , . . . , xh i scrivendo hp, ui : hx1 , . . . , xh i.
Per concludere si osservi come la sintassi esposta debba ancora essere considerata
“astratta”, in quanto non definisce tutti gli aspetti necessari all’imlementazione. In
particolar modo non definisce i tipi dei valori attribuibili alle variabili, né la sintassi
delle espressioni supportate e nemmeno esplicita la precedenza degli operatori di
sequenzializzazione, parallelismo e scelta esterna4 . Di fatto l’implementazione da noi
realizzata fa riferimento ad una sintassi concreta che definisce in maniera formale
anche questi aspetti. In particolare vedremo come i semplici operatori binari siano
trasformati in costrutti con delimitatori di blocco. In questo modo risulta più semplice
l’implementazione del parser e anche la precedenza di un attività rispetto l’altra risulta
esplicitata nella codifica stessa dei blocchi. Cosı̀ il codice risulta più leggibile e
più simile nella struttura sia ai tradizionali linguaggi di programmazione che ad un
linguaggio come BPEL basato su XML .
Presentiamo ora la semantica formale in termini operazionali. Le relazioni semantiche sono definite su termini che includono ulteriori simboli e costrutti, rispetto a quelli
definiti dalla sintassi di Tabella 2.1, allo scopo di rappresentare alcuni aspetti dinamici
dell’esecuzione. In particolare vengono introdotti:
• Protected activity, LaM, utilizzato per sostituire contesti falliti con i rispettivi
compesation handler da eseguire in maniera protetta. La semantica chiarirà il
significato di esecuzione protetta.
• Unsuccessful termination, stop, utilizzato per uniformare il comportamento delle
attività exit e throw.
• Message ≪ p̃ : o : v̄ ≫, utilizzato per rappresentare i messaggi prodotti dalle attività
invoke.
• Scope dalla forma [a′ • a f ⋆ ac △ ad ], utilizzato per rappresentare l’evoluzione del
4
In merito alla precedenza degli operatori, il documento originale in cui è descritto Blite, afferma
che l’operatore di sequenza ·; · ha precedenza sull’operatore di parallelismo ·|·, e che quest’ultimo ha
precedenza sull’operatore di scelta · + ·
32
Linguaggi per l’orchestrazione
contesto [a • a f ⋆ ac ], in cui il completamento di sottocontesti definiti in a hanno
prodotto l’istallazione di compensation handler rappresentati con ad .
Inoltre si introduce il concetto di attività short-lived con l’intento d’individuare un
sott’insieme fra tutti i tipi di attività presenti nel linguaggio. Vedremo che la semantica attribuirà a tali attività la proprietà di essere immuni alla terminazione (in un certo senso queste attività dovranno essere considerate atomiche rispetto al processo di
terminazione). Tale insieme è costituito dalla seguenti attività
Attività short-lived : {empty, exit, throw, stop, ≪ p̃ : o : v̄ ≫}
e di seguito una generica attività short-lived sarà indicata con il simbolo sh.
La semantica dei termini Blite è definita tramite una congruenza strutturale, e tramite
due relazioni di transizione, una che descrive l’evoluzione delle istanze in termini di
operazioni interne e una che descrive l’evoluzione del sistema di deployment attraverso
le comunicazioni.
La Congruenza Strutturale identifica come equivalenti termini sintatticamente diversi ma che intuitivamente rappresentano il medesimo comportamento. Essa è definita
come la più grande congruenza indotta dalle equazioni rappresentate in Tabella 2.2, dove peraltro non sono riportate le leggi che esplicitano le ovvie proprietà di commutatività
e associatività per gli operatori binari.
Dalla congruenza strutturale deduciamo che: l’attività empty agisce come l’elemento identità sia per l’operatore di sequenza che per l’operatore di parallelismo. La
composizione parallela di più attività stop è equivalente ad un’unica stop, mentre
la stessa stop premessa nella sequenzializzazione disabilita le attività successive.
L’operatore di protezione L·M risulta idempotente e le attività short-lived sono da
considerarsi implicitamente protette, in questo modo è espressa la loro immunità alla
terminazione. I messaggi possono essere estratti dall’operatore di protezione. All’inizio
dell’esecuzione di uno scope il compensation handler istallato è da considerarsi uguale
all’attività empty. Importante è notare come la produzione di messaggi non blocchi le
attività successive, né il completamento di scope a meno che nello scope stesso non sia
attivato un throw (quest’ultima possibilità è verificata tramite il predicato · ⇓throw che
verrà formalizzato più avanti). Quest’ultime equazioni concorrono alla definizione di
un modello di comunicazione puramente asincrono per i processi Blite. Le equazioni
rimanenti risultano particolarmente ovvie, in quanto estendono la congruenza strutturale all’applicazione dei costrutti di scope, deployment e composizione di deployment.
2.2 Blite, un approccio formale a BPEL
a | empty ≡ a
33
empty ; a ≡ a ; empty ≡ a
LLaMM ≡ LaM
LshM ≡ sh
stop | stop ≡ stop
L≪ p̃ : o : v̄ ≫ | aM ≡ ≪ p̃ : o : v̄ ≫ | LaM
[a • a f ⋆ ac ] ≡ [a • a f ⋆ ac △ empty]
(≪ p̃ : o : v̄ ≫ | a1 ) ; a2 ≡ ≪ p̃ : o : v̄ ≫ | (a1 ; a2 )
[≪ p̃ : o : v̄ ≫ | a • a f ⋆ ac △ ad ] ≡ ≪ p̃ : o : v̄ ≫ | [a • a f ⋆ ac △ ad ]
′
a≡a
af ≡
stop ; a ≡ stop
a′f
ac ≡
a′c
ad ≡
if ¬a ⇓throw
a′d
[a • a f ⋆ ac △ ad ] ≡ [a′ • a′f ⋆ a′c △ a′d ]
r ≡ r′
a f ≡ a′f
{[r • a f ] , s}c ≡ {s , [r′ • a′f ]}c
d1 k d2 ≡ d2 k d1
a ≡ a′
{µ ⊢ a , s}c ≡ {s , µ ⊢ a′ }c
(d1 k d2 ) k d3 ≡ d1 k (d2 k d3 )
{µ ⊢ stop , s}c ≡ {s}c
{µ ⊢ empty , s}c ≡ {s}c
{µ ⊢ empty}c k d ≡ d
{µ ⊢ stop}c k d ≡ d
Tabella 2.2: Congruenza Strutturale per le attività e i deployment
Per concludere si osservi che istanze del tipo µ ⊢ empty e µ ⊢ stop risultano terminate e
possono essere eliminate. Equivalentemente deployment contenenti solamente istanze
terminate sono da considerarsi terminate e possono a loro volta essere eliminate.
L’evoluzione delle attività di un’istanza di processo è descritta dalla relazione di
α
transizione etichettata −−_, le cui regole sono presentate in Tabella 2.3, dove le etichette
o azioni α sono generate dalla seguente grammatica:
α ::= τ | x ← v | ! p̃ : o : v̄ | ? ℓ r : o : x̄ |
i
|
| (a)
in cui i nuovi simboli introdotti devono essere interpretati come le seguenti azioni:
• τ indica la produzione di un messaggio o operazioni interne come la valutazione di test nella scelta condizionata e nell’iterazione o l’istallazione/attivazione di
compensation handler.
• x ← v indica l’assegnamento del valore v alla variabile x.
• ! p̃ : o : v̄ e ? ℓ r : o : x̄ indicano rispettivamente l’esecuzione di un’invocazione
34
Linguaggi per l’orchestrazione
? ℓ r :o:x̄
τ
µ ⊢ inv ℓ i o x̄ −_ ≪ µ(ℓ i ) : o : µ(x̄) ≫ (inv)
rcv ℓ r o x̄ −−−−−−_ empty (rec)
x←µ(e)
µ ⊢ x := e −−−−−−_ empty (asg)
throw −_ stop (thr)
α
exit −−_ stop (term)
≪ p̃ : o : v̄ ≫ −−−−−_ empty (msg)
α
µ ⊢ a1 −−_ a′1
α
µ ⊢ a1 ; a2 −−_ a′1 ; a2
(
a1 if µ(e) = tt
a=
a2 if µ(e) = ff
τ
µ ⊢ if(e){a1 }{a2 } −_ a
α
µ ⊢ a1 −−_ a′1
µ ⊢ a −−_ a′
! p̃:o:v̄
i
P
(seq)
α
µ ⊢ LaM −−_ La′ M
(prot)
? ℓhr :oh :x̄h
j∈J
rcv ℓ jr o j x̄ j ; a j −−−−−−−−_ ah (h ∈ J) (pick)
a′ =
(if)
(
a ; while(e) {a} if µ(e) = tt
empty
if µ(e) = ff
(while)
τ
µ ⊢ while(e) {a} −_ a′
α < {i, } ¬(a2 ⇓throw ∨ a2 ⇓exit )
α
µ ⊢ a1 | a2 −−_ a′1 | a2
(a c )
[empty • a f ⋆ ac △ ad ] −−−_ empty (done1 )
α
µ ⊢ a −−_ a′
α
(par1 )
a1 −−_ a′1
α ∈ {i, }
α
a1 | a2 −−_ a′1 | end(a2 )
(par2 )
τ
[stop • a f ⋆ ac △ ad ] −_ Lad ; a f M (done2 )
α < {, (a′′ )}
α
µ ⊢ [a • a f ⋆ ac △ ad ] −−_ [a′ • a f ⋆ ac △ ad ]
(exec)
(a′′ )
a −−−−_ a′
τ
[a • a f ⋆ ac △ ad ] −_ [a′ • a f ⋆ ac △ a′′ ; ad ]
(done3 )
a −_ a′
τ
[a • a f ⋆ ac △ ad ] −_ [a′ • a f ⋆ ac △ ad ]
(fault)
Tabella 2.3: Semantica operazionale per le attività.
sull’operazione o, con il partner link valorizzato a p̃ e parametri attuali v̄, e la
ricezione sull’operazione o, partner link ℓ r e parametri formali x̄.
• i indica la richiesta di una terminazione forzata di un’istanza di processo.
• indica la generazione di un’eccezione.
• (a) indica il completamento con successo di uno scope che potrà essere
eventualmente compensato tramite l’attività a.
α
La relazione −−_ è definita con l’ausilio della funzione di stato µ che associa un
2.2 Blite, un approccio formale a BPEL
35
valore ad ogni variabile dell’istanza a cui è associata (vi è uno stato distinto e unico per
ogni istanza di processo, mentre non vi è il concetto di stato locale associato ai contesti).
In particolare µ(x) restituisce il valore della variabile x nello stato µ e µ(e) restituisce
il risultato della valutazione dell’espressione e nello stato µ. La funzione stato µ può
essere aggiornata con µ′ , scrivendo µ ◦ µ′ , in modo da avere



 µ′ (x) se x ∈ dom(µ′ )
µ ◦ µ′ (x) = 

 µ(x) altrimenti
Bisogna osservare che in alcune regole di Tabella 2.3 tale funzione non è esplicitata,
α
α
per esempio per semplicità si è scritto a −−_ a′ invece di µ ⊢ a −−_ a′ .
Alcune funzioni e predicati ausiliari, come già precedentemente osservato, sono stati
introdotti a supporto della definizione semantica. In particolare i predicati a ⇓exit and
a ⇓throw valutano la capacità di a di eseguire exit o throw, rispettivamente. Essi sono
definiti per induzione sulla struttura sintattica delle attività e di fatto valgono sempre
falso tranne che per i casi seguenti in cui si ha:
exit ⇓exit
throw ⇓throw
a1 ⇓exit
a1 ⇓throw
a1 ; a2 ⇓exit
a1 ; a2 ⇓throw
a ⇓exit
a ⇓exit
a ⇓exit
[a • a f ⋆ ac △ ad ] ⇓exit
LaM ⇓exit
LaM ⇓throw
a1 ⇓exit ∨ a2 ⇓exit
a1 ⇓throw ∨ a2 ⇓throw
a1 | a2 ⇓exit
a1 | a2 ⇓throw
Per rappresentare la semantica della terminazione viene introdotta la funzione
end(·), che applicata ad un’attività, la restituisce in modo da lasciare solamente le attività short-lived e quelle protette. Anche tale funzione è definita sulla struttura sintattica
delle attività e si ha che ∀a end(a) = stop tranne i seguenti casi:
end(sh) = sh
end(LaM) = LaM
end([a • a f ⋆ ac △ ad ]) = [end(a) • a f ⋆ ac △ ad ]
end(a1 ; a2 ) = end(a1 )
end(a′ | a′′ ) = end(a′ ) | end(a′′ )
in cui a1 non può essere congruente a empty o a ≪ p̃ : o : v̄ ≫ o alla composizione
parallela di essi.
Commentiamo ora le regole di Tabella 2.3.
36
Linguaggi per l’orchestrazione
(inv) (asg) Affermano che le attività di invocazione e di assegnamento possono procede-
re solamente se i loro argomenti sono espressioni chiuse (cioè senza varibili non
inizializzate). Inoltre (inv) insieme alla successiva (msg) e alla congruenza strutturale definisce la semantica asincrona per la comunicazione. Si osservi infatti il
seguente comportamento:
τ
inv ℓ i o x̄ ; a −_ ≪ µ(ℓ i ) : o : µ(x̄) ≫ ; a ≡ (≪ µ(ℓ i ) : o : µ(x̄) ≫ | empty) ; a
≡ ≪ µ(ℓ i ) : o : µ(x̄) ≫ | (empty ; a) ≡ ≪ µ(ℓ i ) : o : µ(x̄) ≫ | a
(rec) Un’operazione di ricezione offre la possibilità di invocazione di un’operazione
locale su un fissato partner link. Da osservare che a differenza dell’invocazione,
la ricezione blocca le attività sequenzialmente successive.
(thr) (term) Rappresentano rispettivamente la produzione di un’eccezione e di una
richiesta di terminazione per l’istanza corrente.
(msg) Un messaggio può essere consumato indipendentemente dalla sua generazione.
(prot) Il costrutto LaM permette la normale esecuzione di a al suo interno.
Poiché
end(LaM) = LaM, con l’operatore L·M si ottiene la protezione di a dalle richieste
esterne di terminazione.
(seq) (pick) (if) (while) Esprimono una semantica standard rispetto ai tradizionali linguag-
gi di programmazione e alle più comuni algebre di processo che prevedono
l’operatore di scelta esterna.
(par1 ) (par2 ) Esprimono la semantica caratteristica di BPEL riguardo alla terminazione
nella composizione parallela con attività fallite. Di fatto esse affermano che l’esecuzione di attività parallele evolve come atteso se nessuna di esse ha sollevato
un’eccezione o richiesto una terminazione. Viceversa, se questo accade, tutti i
rami della composizione parallela vengono terminati. Si ricordi anche che per
definizione end(a′ | a′′ ) = end(a′ ) | end(a′′ ).
(done1 ) (done3 ) Esprimono la caratteristica di BPEL secondo cui contesti completati con
successo istallano nei rispettivi contesti padre i loro compensation handler per
un’eventuale successiva compensazione. Il contesto padre colleziona in sequenza
i compensation halder di contesti figli completati (done3 ). Questa semantica, differentemente dalle ultime specifiche informali di BPEL, prevede che i compensation
handler siano mantenuti attraverso un solo grado di parentela nella gerarchia dei
2.2 Blite, un approccio formale a BPEL
37
contesti5 . Difatti un contesto completato istalla nel suo padre solo il suo compensation handler e non quelli istallati dai suoi figli. Una semantica alternativa poteva
essere:
(ad ;ac )
[empty • a f ⋆ ac △ ad ] −−−−−_ empty
in cui si mantengono anche i compensation handler precedentemente istallati.
(exec) Se non si producono eccezioni l’esecuzione interna ad uno scope procede come
atteso. Da osservare che questa regola è da applicare anche nel caso in cui a
i
richieda una terminazione (a −−_ a′ −
_ . . . stop); qui a differenza di (fault) l’azione
i viene propagata anche al di fuori del contesto per produrre la terminazione di
tutta l’istanza e successivamente tramite l’applicazione di (done2 ) vengono eseguiti
i compensation handler istallati e il fault hadler del contesto corrente.
(done2 ) In uno scope in cui la contest activity giunge a stop a causa di un’eccezione o
di una richiesta di terminazione, attraverso una transizione interna si attiva l’esecuzione protetta dei compensation handler istallati e del fault handler. Importante
è osservare come l’esecuzione dell’ambiente protetto avvenga nello stato attuale
dell’istanza.
(fault) La sollevazione di un’eccezione in a non viene propagata al di fuori dello sco-
pe, ma gestita tramite una transizione interna. La successiva evoluzione di
a −_ a′ −
_ . . . stop in cui vengono terminati gli eventuali rami paralleli, porterà
all’applicazione di (done2 ).
Abbiamo definito il comportamento delle attività interne delle istanze di processo,
vediamo ora come avviene la creazione di tali istanze e la comunicazione in sistemi
composti da più deployment. Il comportamento di questi ultimi è definito dalla relazione
di riduzione ≻−
_, le cui regole sono presentate in Tabella 2.4.
La problematica fondamentale è quella di individuare, fra le varie istanze di
un deployment, quella predisposta alla ricezione di un messaggio, o capire se il
sopraggiungere di un messaggio debba causare la creazione di una nuova istanza.
5
Un punto abbastanza discusso, nelle prime specifiche di BPEL è stato il meccanismo di realizzazione della compensazione. Il modello proposto dalla semantica di Blite non vuole essere una semplificazione, ma riprodurre la linea di pensiero secondo cui la compensazione di un contesto esegue ogni
azione necessaria ad annulare tutti gli effetti del contesto stesso, comprese quindi le azioni svolte dai
sotto-contesti.
38
Linguaggi per l’orchestrazione
! t2
? t1
a2 −−−_ a′2
a1 −−−_ a′1
match(c1 , µ1 , t1 , t2 ) = µ′1
c1 ,|µ′1|
¬ ( µ1 ⊢ a1 , s1 ⇓t2
)
(com)
_ {µ1 ◦ µ′1 ⊢ a′1 , s1 }c1 k {µ2 ⊢ a′2 , s2 }c2
{µ1 ⊢ a1 , s1 }c1 k {µ2 ⊢ a2 , s2 }c2 ≻−
? t1
[r • a f ⋆ empty] −−−_ a1
! t2
a2 −−−_ a′2
c ,|µ1|
match(c1 , ∅, t1 , t2 ) = µ1 ¬ (s1 ⇓t21
)
(new)
{[r • a f ] , s1 }c1 k {µ2 ⊢ a2 , s2 }c2 ≻−
_ {µ1 ⊢ a1 , [r • a f ] , s1 }c1 k {µ2 ⊢ a′2 , s2 }c2
x←v
µ ⊢ a −−−−_ a′
{µ ⊢ a , s}c ≻−
_ {µ ◦ µ′ ⊢ a′ , s}c
α
µ ⊢ a −−_ a′
d1 ≻−
_ d′1
match(c, µ, x, v) = µ′
d1 k d2 ≻−
_ d′1 k d2
d ≡ d1
α < {? t1 , ! t2 , x ← v}
{µ ⊢ a , s}c ≻−
_ {µ ⊢ a′ , s}c
(var)
(enab)
d1 ≻−
_ d2
d ≻−
_ d′
(part)
d2 ≡ d′
(cong)
Tabella 2.4: Regole di riduzione per i deployment (si consideri t1 = ℓ r : o : x̄ e
t2 = p̃ : o : v̄).
Nella definizione originale di Blite è stato scelto un approccio puramente semantico, nel senso che né la sintassi né la struttura dei messaggi prevedono la presenza di
informazioni aggiuntive che possono essere utilizzate per guidare tali scelte.
Di fatto si è scelto di definire un ordinamento fra tutte le istanze di processo che concorrono alla ricezione di un determinato messaggio e di dare la possibilità di consumare
il messaggio solo alle prime istanze dell’ordinamento. Talvolta tale ordinamento può
individuare più di un’istanza destinataria del messaggio, in questo caso la semantica
non specifica ulteriormente la scelta e consente un comportamento non deterministico.
In maniera informale l’ordinamento si basa sul seguente criterio: “i messaggi che
giungono alle porte di una definizione di processo possono, tramite i parametri formali,
aggiungere più o meno informazione nello stato di una istanza che riceve. Un messaggio
costituito dalla tupla hv1 , . . . , vn i, tramite i parametri formali hx1 , . . . , xn i ha grado di
definizione k (k ≤ n), se il messaggio definisce o ridefinisce con nuovi valori esattamente
k variabili formali e lascia immutato lo stato delle restanti n−k. Un messaggio può essere
consumato solamente da una fra le istanze con grado di definizione minimo”. Insieme a
questa regola si impone il vincolo generale che l’assegnamento ad una variabile inclusa
nel correlation set può essere fatto una sola volta, cioè non è possibile assegnare un
valore diverso ad una variabile del correlation set già precedentemente inizializzata.
2.2 Blite, un approccio formale a BPEL
| match(c, µ, ℓ r : o : x̄, p̃ : o : v̄) |< n
µ ⊢ rcv ℓ r o x̄ ; a ⇓p̃c,n
:o:v̄
µ ⊢ a1 ⇓p̃c,n
:o:v̄
39
∃ h ∈ J . | match(c, µ, ℓhr : oh : x̄h , p̃ : o : v̄) |< n
P
µ ⊢ j∈J rcv ℓ jr o j x̄ j ; a j ⇓p̃c,n
:o:v̄
µ ⊢ a1 ⇓p̃c,n
∨ µ ⊢ a2 ⇓p̃c,n
:o:v̄
:o:v̄
µ ⊢ a1 ; a2 ⇓p̃c,n
:o:v̄
µ ⊢ a1 | a2 ⇓p̃c,n
:o:v̄
µ ⊢ a ⇓p̃c,n
:o:v̄
µ ⊢ a ⇓p̃c,n
:o:v̄
µ ⊢ a ⇓p̃c,n
∨ s ⇓p̃c,n
:o:v̄
:o:v̄
µ ⊢ LaM ⇓p̃c,n
:o:v̄
µ ⊢ [a • a f ⋆ ac △ ad ] ⇓p̃c,n
:o:v̄
µ ⊢ a , s ⇓p̃c,n
:o:v̄
Tabella 2.5: Definizione del predicato s ⇓p̃c,n:o:v̄ .
Se ne conclude che non è possibile leggere un messaggio che assegnerebbe un valore
diverso ad una variabile di correlazione già precedentemente inizializzata.
Questi ragionamenti vengono formalizzati nella seguente funzione
match(c, µ, x̄, v̄), che prende un correlation set c, una funzione di stato µ, una tupla
di variabili x e una tupla di valori v e restituisce una funzione di stato eventualmente
anche vuota su un sotto-insieme delle variabili di x̄:



 {x 7→ v} if x < c ∨ (x ∈ c ∧ x < dom(µ))
match(c, µ, x, v) = 

 ∅
if x ∈ c ∧ {x 7→ v} ∈ µ
match(c, µ, hi, hi) = ∅
match(c, µ, x1 , v1 ) = µ′
match(c, µ, x̄2 , v̄2 ) = µ′′
match(c, µ, (x1 , x̄2 ), (v1 , v̄2 )) = µ′ ◦ µ′′
Si osservi come match(c, µ, x̄, v̄) non sia definita nel caso in cui le tuple x̄ e v̄ abbiano diversa lunghezza e quando si abbia per un qualche i che xi ∈ c e {xi 7→ v′ } ∈ µ con
v′ , vi . Quest’ultimo fatto impone quanto precedentemente detto che un’istanza non
possa ricevere messaggi i cui valori assegnati alle variabili attuali non siano compatibili
con lo stato del correlation set.
Con la funzione match(·, ·, ·, ·) è possibile definire il predicato s ⇓p̃c,n:o:v̄ , che valuta se
un’istanza di processo s con correlation set c ha un’attività di ricezione attiva che possa
40
Linguaggi per l’orchestrazione
ricevere il messaggio p̃ : o : v̄ con un grado di definizione minore di n. In Tabella 2.5
tale predicato viene definito induttivamente sulla struttura sintattica di s
A questo punto il comportamento descritto dalle regole di Tabella 2.4 risulta
abbastanza intuitivo e può essere sintetizzato nel seguente modo:
(com) Afferma che la comunicazione fra due istanze di processo può avvenire solo se
si realizza il matching, in rispetto dello stato di correlazione, dei valori del messaggio con le variabili di ricezione. Ovviamente ci deve essere anche uguaglianza
sintattica fra i partner link e il nome dell’operazione. Tale regola afferma anche
che fra tutte le istanze con un’attività di ricezione attiva conforme con i partner
link e l’operazione del messaggio in arrivo, se ne debba scegliere una con grado
di definizione minimo.
(new) Descrive la creazione di una nuova istanza di processo a partire dalla definizione
[r • a f ] a seguito dell’arrivo di un messaggio t2 = p̃ : o : v̄ che risulti in matching
con una delle start activity della definizione stessa. La nuova istanza verrà creata
solo se non c’è già un’istanza capace di ricevere il messaggio con un grado di
definizione minore della start activity.
(var) Afferma quanto già precedentemente detto che l’assegnamento di un valore v ad
una variabile x del correlation set su uno stato µ può avvenire solo se x < dom(µ)
o se {x 7→ v} ∈ µ.
(part) (pass) (cong) Tali regole risultano particolarmente ovvie ed esprimono rispettiva-
mente l’evoluzione parallela di un sistema di deployment, l’esecuzione indipendente delle operazioni interne in ciascuna istanza, l’uguaglianza delle riduzioni di
deployment strutturalmente congruenti.
2.3
Una grammatica per un compilatore
Il primo passo per lo sviluppo di un motore di esecuzione per Blite è la realizzazione
di un parser per l’analisi di programmi ottenibili dalla sintassi presentata in Tabella 2.1.
Un parser è il software predisposto alla realizzazione dell’Analisi Sintattica, cioè del
procedimento che riconosce la struttura del programma sorgente e costruisce l’Albero
Sintattico (Abstract Sintatic Tree - AST) associato. Come già osservato la sintassi esposta, pur specificando in modo formale i termini del linguaggio, non si presta ad essere
2.3 Una grammatica per un compilatore
41
implementata direttamente, in quanto per esempio presenta regole del tipo
a ::= . . .
a;a | a|a
...
che la rendono ambigua, per cui ad una frase del linguaggio può corrispondere più di un
albero sintattico. Risulta chiaro quindi che il primo obiettivo è stato quello di ottenere
una grammatica direttamente implementabile dal cui linguaggio sia possibile ottenere
i programmi Blite. Di seguito chiameremo termini Blite gli elementi del linguaggio
derivato dalla sintassi di Tabella 2.1, mentre chiameremo programmi Blite le frasi del
linguaggio realmente implementato.
Cenni di teroria dei linguaggi
Dalla teoria sappiamo che le grammatiche per generare linguaggi analizzabili con parser
deterministici, cioè che non fanno uso di tecniche di backtraking, sono raggruppabili in
sotto classi specifiche fra tutte le possibili grammatiche context free. In particolar modo
risultano fondamentali per la realizzazione di analizzatori sintattici le seguenti classi di
grammatiche:
• LL(k): Per cui risulta possibile realizzare parser top-down deterministici che
utilizzano al più k simboli non ancora riconosciuti della frase di input.
• LR(k): Per cui risulta possibile realizzare parser bottom-up deterministici che
utilizzano al più k simboli non ancora riconosciuti della frase di input.
I parser top-down costruiscono l’albero sintattico dalla radice (simbolo iniziale della
grammatica) alle foglie (simboli terminali della frase di input) secondo la derivazione
canonica sinistra, mentre quelli bottom-up realizzano l’albero sintattico dalle foglie alla
radice seguendo la riduzione destra.
La derivazione è quel processo che a partire dal simbolo iniziale della grammatica
S arriva alla frase di input applicando ad ogni passo una sostituzione di un simbolo non
terminale X con una parte destra Y presente in una regola della grammatica del tipo
X ::= Y.
Una derivazione può essere scritta come
S ⇒ · · · ⇒ abcX . . . ⇒ abcY . . . · · · ⇒ abcdeZ . . . ⇒ · · · ⇒ abcde f . . . mn
in cui l’ultima stringa è formata solo da terminali. Con il simbolo ⇒∗ si intendono
zero o più passi successivi e con ⇒+ uno o più passi successivi. I singoli passi della
42
Linguaggi per l’orchestrazione
derivazione legano forme di frasi ammesse dalla grammatica, dove una forma di frase
non è altro che una stringa s ∈ V ∗ , con V insieme dei terminali e non terminali della
grammatica. In un’analisi top-down si realizza la derivazione canonica sinistra, cioè si
impone la sostituzione del simbolo non terminale che compare più a sinistra nella forma
di frase corrente. Quindi quest’ultima avrà sempre una forma del tipo aXw con a ∈ VT ∗
(VT insieme dei simboli terminali del linguaggio), X simbolo non terminale corrente e
w ∈ V ∗ . La stringa a costituirà un prefisso della frase di input e indicherà la parte di essa
già riconosciuta.
Analogamente alla derivazione canonica sinistra si definisce la derivazione canonica destra che sceglie di sostituire il simbolo non terminale che compare più a destra
nella forma di frase corrente. L’analisi bottom-up realizza la riduzione destra che è la
sequenza inversa della derivazione canonica destra.
In generale con le grammatiche LR si riesce a rappresentare un numero maggiore
di linguaggi rispetto alle grammatiche LL, cioè l’insieme dei linguaggi generati con
grammatiche LL è contenuto nell’insieme dei linguaggi generati con grammatiche LR;
con quest’ultime però risulta molto più complessa la realizzazione del parser, in quanto
è necessario implementare algoritmi e strutture dati più raffinati.
Inoltre le grammatiche LL hanno caratteristiche che le rendono facilmente individuabili e da una generica grammatica, come quella di Tabella 2.1, si può ottenere un
grammatica LL applicando i seguenti passi:6
• Eliminazione della ricorsione sinistra diretta o indiretta. Una grammatica presenta
ricorsione sinistra quando ammette derivazioni del tipo X ⇒+ Xu, dove X ∈ V N
e u ∈ V + (la ricorsione diretta si ha banalmente quando sono presenti regole del
tipo X ::= Xu), in tal caso l’analizzatore top-down può entrare in un ciclo infinito.
Esiste un algoritmo per eliminare tale problema (si veda [19]), ma in generale può
essere evitato semplicemente aggiungendo nuovi non terminali e riorganizzando
le regole. Per esempio la seguente grammatica:
E ::= E + T | E − T | T | − T
T ::= T ∗ F | F
F ::= (E) | i
6
Per una trattazione formale riguardo al riconoscimento di grammatiche LL(k) si rimanda ai testi
specifici della teoria del linguaggi formali come [19] e [20].
2.3 Una grammatica per un compilatore
43
che presenta ricorsione sinistra può essere trasformata nella grammatica:
E
E′
T
T′
F
::=
::=
::=
::=
::=
T E′ | − T E′
+T E ′ | − T E ′ | ǫ
FT ′
∗FT ′ | ǫ
(E) | i
che ne è priva.
• Fattorizzazione sinistra. Questo procedimento serve ad eliminare un eventuale
prefisso comune a due parti destre di regole associate allo stesso simbolo non
terminale, cosa che in generale porta non determinismo nel processo di derivazione canonica sinistra. Se, ad esempio, una regola della grammatica è del tipo:
A ::= yv | yw con y ∈ V + , v ∈ V ∗ e w ∈ V + e le stringhe v, w non hanno prefissi
comuni, l’analizzatore top-down, quando espande il simbolo non terminale A non
può, in generale, scegliere con certezza la parte destra di A da usare, guardando
un solo simbolo (o anche più di uno ma in numero determinato) in avanti nella
frase di input. Avrebbe conferma della scelta fatta, soltanto dopo aver riconosciuto una sottostringa derivabile da y e, in caso di scelta errata, sarebbe costretto ad
effettuare backtracking. Per eliminare quest’incertezza, è necessario fattorizzare a
sinistra tali parti destre, cioè sostituire la regola con le due A := yA′ e A′ := v | w.
Questa tecnica si può estendere al caso in cui vi siano anche più di due parti destre
associate allo stesso simbolo non terminale aventi un prefisso comune.
La grammatica per Blite
Nel realizzare la nostra implementazione si è scelto di produrre una grammatica LL(k)
per rappresentare i programmi Blite. Per la precisione una grammatica LL(1), cioè una
grammatica il cui parser è in grado di riconoscere le frasi del linguaggio senza fare
lookahead, ma utilizzando unicamente il primo token ancora non riconosciuto nella
frase di input.
Nel momento in cui ci si trovi a dover realizzare un parser le strade da poter percorrere sono fondamentalmente due: la prima è quella di munirsi di un buon background
teorico ed implementare, partendo da zero, tutto il software necessario; la seconda invece prevede l’utilizzo di un tool per la creazione automatica di compilatori che, a partire
da una definizione formale della grammatica, generi tutto o parte del codice necessario.
44
Linguaggi per l’orchestrazione
Grazie all’ampia disponibilità di tool (anche open source)7 , all’ottimo livello di affidabilità e all’alto numero di funzionalità offerto da questi, si è optato per la seconda
strada. In particolare si è scelto di utilizzare JavaCC (Java Compiler Compiler) [22].
Questo è uno dei più diffusi generatori di parser in linguaggio Java e permette di realizzare in maniera produttiva ed efficiente compilatori per un gran numero di tipologie di
linguaggi di programmazione.
JavaCC di fatto realizza parser top-down a discesa ricorsiva a partire da grammatiche
LL(k) definite in un formalismo specifico in cui una notazione simile a EBNF è arricchita con istruzioni semantiche espresse direttamente in linguaggio Java. In realtà JavaCC
non si limita alle grammatiche LL(k), ma in pratica gestisce qualsiasi grammatica che
non presenti ricorsione sinistra. Infatti dispone di un supporto estremamente potente per
eseguire lookahead anche sintattico e semantico8 . Questo fa sı̀ che in pratica il parco
dei linguaggi implementabili con JavaCC sia del tutto uguale a quello realizzabile con
tool come Cup o SableCC che supportano grammatiche LALR.
Un punto a sfavore, rispetto ad altri tool, può essere invece che JavaCC di per sè
non offre nessun supporto alla creazione degli alberi sintattici. Di fatto è generato un
semplice parser top-down a discesa ricorsiva in cui l’utente può inserire qualsiasi tipo
di azione tramite codice Java, e tramite questa possibilità si lascia all’utente la completa
responsabilità di creare un eventuale albero sintattico. Si capisce bene che questa scelta,
sebbene permetta di realizzare parser molto efficienti nel caso di linguaggi molto semplici, risulta penalizzante qualora si voglia realizzare alberi sintattici per linguaggi con
un certo grado di complessità strutturale.
Per superare questa limitazione, all’interno del medesimo progetto, è stato sviluppato JJTree, che realizza un preprocessore per i file letti da JavaCC. In pratica JJTree
supporta un’estensione del linguaggio di specifica di JavaCC, in cui è possibile inserire,
direttamente nella grammatica e in formalismo dichiarativo, informazioni per guidare la
7
Rimanendo solamente nell’abito della piattaforma Java, si contano numerosi software per la generazione automatica di compilatori, fra i più diffusi certamente troviamo: ANTLR, SableCC, CUP (Java
Yacc/Bision), JavaCC, Coco/R, JLex/JFlex. Per una lista più ampia si faccia riferimento alla pagina online
http://java-source.net/open-source/parser-generators.
8
Il lookahead sintattico prevede non di considerare in avanti un numero fisso di token, ma di fare in avanti una vera e propria sotto analisi sintattica. In base alla categoria sintattica riconosciuta
“in avanti” è possibile fare la scelta adeguata nell’espansione del non terminale corrente nell’analisi
principale. Il lookahead semantico prevede invece che il programmatore possa definire delle azioni
semantiche che accedendo arbitrariamente ai simboli in avanti possono determinare la scelta opportuna da fare nel processo di analisi. Per un approfondimento su tali tecniche si faccia riferimento a
https://javacc.dev.java.net/doc/lookahead.html
2.3 Una grammatica per un compilatore
JJTree
Grammar
Specification
(*.jjt)
JJTree
Compiler
JavaCC
Grammar
Specification
45
JavaCC
Compiler
(*.jj)
Java Code
for top−down
parser with
AST managing
actions
(*.java)
Figura 2.2: Generazione in due passi di un parser Java con JJTree e JavaCC.
creazione dell’albero sintattico. L’output prodotto da JJTree non sarà altro che la grammatica per JavaCC arricchita delle istruzioni Java per la gestione automatica dell’albero
sintattico. In Figura 2.2 è rappresentato l’uso congiunto di JJTree e JavaCC per la produzione del codice Java che realizza il parser con la gestione automatica dell’albero
sintattico.
A questo punto non ci rimane che mostrare come dalla sintassi di Tabella 2.1 sia
stata prodotta una grammatica EBNF che potesse essere direttamente codificata nel
linguaggio di specifica supportato da JJTree.
Abbiamo già detto come, nonostante l’elevato supporto al lookahead fornito da JavaCC, si sia scelto di mantenere la grammatica nella classe LL(1), questo non tanto per
motivi di efficienza, che per altro potrebbero essere sempre rilevanti, ma per mantenere
il più semplice possibile la definizione stessa della grammatica che, per la ricchezza del
linguaggio offerto da JJTree, tende a complicarsi notevolmente. Inoltre ci è sembrato
che i vincoli imposti dalla classe LL(1) non appesantissero la sintassi, ma che anzi contribuissero a chiarirne alcuni aspetti, come per esempio la distinzione fra la definizione
di processo e le istanze pronte per essere eseguite (Ready-to-Run Instance).
Come illustrato i passi da compiere sono quelli di eliminare la ricorsione sinistra e i
prefissi comuni nelle diverse parti destre associate al medesimo non terminale. Di fatto
tali criticità sono presenti nelle regole EBNF di tipo:
a ::= a ; a | a | a | b ( + b )∗
d ::= {s}c | d k d
In generale per risolvere il problema si può utilizzare l’approccio usato per le espres-
46
Linguaggi per l’orchestrazione
sioni aritmetiche e introdurre i simboli terminali delle parentesi e nuovi simboli non
terminali e tramite questi riscrivere le regole nel seguente modo:
a ::=
sq
sq ::= par
par ::=
pk
pk ::=
ba
ba ::=
b
d ::= dep
(
(
(
|
; par )+
| pk )+
+ ba )∗
(a)
(
k
dep )+
dep ::= {s}c
Si può banalmente vedere che tale grammatica è di classe LL(1) e quindi non ambigua9 , infatti essa esplicita strutturalmente la precedenza dell’operatore ; su | e la precedenza di quest’ultimo sull’operatore di +. Il costo che si è dovuto pagare, oltre ad un
aumento delle regole e dei simboli non terminali, è la perdita di chiarezza e simmetria
nella grammatica che si traduce in una complicazione nella gestione dell’albero sintattico in quanto dovranno essere implementate in maniera specifica le azioni associate a
ciascuna regola.
Tale approccio è stato utilizzato solo per la categoria sintattica dei deployment d ma
non per quella delle attività strutturate a. Per queste ultime si è preferito un approccio
che aggiungendo dei nuovi simboli terminali (usati come delimitatori di blocco) permettesse di ottenere un grammatica LL(1) mantenendo una certa simmetria nella definizione
e facilitando la gestione dell’albero sintattico.
Quello che abbiamo fatto è stato di introdurre per ogni operatore una coppia di simboli terminali che delimitasse la rispettiva attività strutturale, di fatto vengono introdotte
regole del seguente tipo:
Activity ::= BasicActivity | S equenceActivity | S cope |
FlowActivity | PickActivity |
ConditionalActivity | IterationActivity
...
S equenceActivity ::= "seq" Activity (";" (Activity)? ) ∗ "qes"
FlowActivity ::= "flw" Activity ("|" Activity ) + "wlf"
PickActivity ::= "pck" ReceiveActivity ("+" ReceiveActivity ) + "kcp"
9
Si può dimostrare che le grammatiche LL(1) non sono ambigue, cioè essere in LL(1) è condizione
sufficiente a garantire la non ambiguità di una grammatica.
2.3 Una grammatica per un compilatore
Sintassi Blite
Sequenza
a1 ; a2 . . . ; an
Parallelismo
a1 | a2 . . . | an
Scelta Esterna rcv 1 + rcv 2 . . . + rcv
47
n
Sintassi Implementata
seq a1 ; a2 . . . ; an qes
f lw a1 | a2 . . . | an wl f
pck rcv 1 + rcv 2 . . . + rcv n kcp
Tabella 2.6: Utilizzo dei delimitatori di blocco per le attività strutturate con operatori
binari.
In ogni modo la motivazione che ci ha spinto maggiormente a propendere per l’utilizzo dei delimitatori è stata quella di avere una sintassi il più chiara possibile e che
non lasciasse a regole implicite la determinazione delle precedenze fra gli operatori. In
questo modo il codice stesso espliciterà tutte le relazioni di sequenzialità fra le diverse
attività strutturate e l’utilizzatore non sarà obbligato a ricordare le regole di priorità fra
i vari costrutti.
In Figura 2.3 è rappresentato, nella sintassi da noi implementata, il programma
corrispondente al termine seguente:
{(rcv h′′ auction′′ i seller hpid, selleri | rcv h′′ auction′′ i buyer hpid, buyeri)
; inv hselleri ok hpid, buyeri ; inv hbuyeri ok hpid, selleri}(pid)
A scapito di una maggiore compattezza si è guadagnato in chiarezza. Inoltre le
tecnologie attuali offrono un alto grado di assistenza all’editing del codice, per cui sarà
facilmente implementabile un editor che possa supportare l’utente nella scrittura dei
costrutti previsti dalla nostra grammatica.
Un altro aspetto da considerare nella sintassi di Tabella 2.1 è quello legato alla
definizione dei termini Services, tramite la regola:
(Services) s ::= [r • a f ] | µ ⊢ a | µ ⊢ a , s definition, instance, multiset
La parte destra instance ha la funzionalità di rappresentare le istanze in fase di esecuzione con uno stato di memoria (µ ⊢ a), ma anche di dare la possibilità di definire, e
quindi di rendere immediatamente disponibili nei deployment, istanze di processo che
sono già pronte per andare in esecuzione, senza dover ricevere messaggi di attivazione.
Tali definizioni sono qui identificate con l’espressione Ready-to-Run Instance.
48
Linguaggi per l’orchestrazione
{
[
seq
flw
rcv <"auction"> seller(pid, seller)
|
rcv <"auction"> buyer(pid, buyer)
wlf;
inv <seller> ok(pid, buyer);
inv <buyer> ok(pid, seller)
qes
]
} (pid)
Figura 2.3: Esempio di programma Blite con la sintassi dei delimitatori di blocco per le
attività strutturate.
Nel linguaggio da noi implementato si è voluto mantenere quest’ultima possibilità
in quanto ci sembra molto utile, nell’ottica di voler fornire uno strumento per la prototipizzazione, avere un meccanismo integrato nel formalismo, che possa permettere di
avviare i processi10 .
I tal senso si vuole dare la possibilità all’utente di poter definire dei deployment del
tipo:
{a1 , . . . , an , [r • a f ]}c
in cui è presente la definizione di processo [r • a f ] e n ready-to-run instance. Per non
appesantire ulteriormente la sintassi, il linguaggio da noi implementato non prevede un
formalismo specifico per la definizione della funzione di stato µ nelle ready to run instance, anche perché lo stato iniziale di tali istanze può essere definito semplicemente
con attività di assegnamento. Di fatto quindi la distinzione fra le ready-to-run instance
e la definizione di processo sta semplicemente nel fatto che quest’ultima deve obbligatoriamente avere la struttura [r • a f ], mentre le prime possono essere generiche attività strutturate. Nel caso quindi queste siano dei contesti, devono sempre esplicitare il
compensation handler ([r • a f ⋆ ac ]) per essere distinguibili dalle definizioni.
10
Si fa osservare che in BPEL tale possibilità non è presente. Il formalismo di BPEL prevede solamente
la definizione di processi che interagiscono con il mondo esterno esclusivamente attraverso le porte dei
servizi web. L’unico modo di avviare le istanze di processo è quello di inviare un messaggio ad una
start activity tramite l’uso di una tecnologia alternativa. Per esempio un’applicazione Java può fare un
invocazione ad una porta di una start activity e istanziare il processo BPEL
2.3 Una grammatica per un compilatore
49
Si capisce come limitandosi a tale sintassi non sia possibile prescindere dall’eseguire
lookahead sintattico per distinguere le definizioni dalle ready-to-run instance, in quanto
in generale tali termini possono avere prefissi comuni del tipo ′′ [a • a′′f . Anche in questo
caso si è preferito introdurre un nuovo terminale che preposto alle ready-to-run instance
le rendesse immediatamente riconoscibili, ottenendo quindi una grammatica LL(1) e
aumentando la leggibilità dei programmi.
In pratica si è introdotto il simbolo :: e nella grammatica si sono definite le seguenti
regole per la definizione della categoria sintattica Service:
S ervice ::= S erviceDe f | (S erviceInstance ("," S ervice)? )
S erviceInstance ::= "::" Activity
S erviceDe f ::= "[" S tartActivity ("fh:" Activity)? "]"
Usando un prefisso per distinguere le ready-to-run instance dalle definizioni di processo, cade l’obbligatorietà di avere sempre un compensation handler nelle scope activity generiche, per cui queste ultime potranno essere definite tramite la seguente regola
in cui risultano opzionali sia la definizione del compensation che del fault handler:
S cope ::= "[" Activity ("fh:" Activity )? ("ch:" Activity )? "]"
I simboli tipografici • e ⋆ sono stati sostituiti rispettivamente dalle scritture fh: e
ch: e si assume che un contesto che non definisca gli handler, implicitamente preveda
l’attività throw come fault handler e l’attività empty come compensation handler. In
pratica si assume che:
[a] = [a • throw ⋆ empty]
In Figura 2.4 è riportato un frammento di codice Blite in cui è definito un deployment
con una definizione di processo e due istanze pronte per essere eseguite.
Con i simboli terminali introdotti tutte le attività strutturate risultano avere un
prefisso caratterizzante per cui, nelle regole di Tabella 2.1 che definiscono l’attività
condizionata e l’iterazione:
(Structured activities) a ::= . . . if(e){a1 }{a2 } | while(e) {a} . . .
possono essere eliminati i delimitatori di blocco {·} e definire le seguenti regole:
ConditionalActivity ::= "if" "(" Expression ")" Activity Activity
IterationActivity ::= "while" "(" Expression ")" Activity
50
Linguaggi per l’orchestrazione
{
:: seq
inv <"s1"> start("john");
inv <"s1"> corre("john");
qes,
:: seq
inv <"s1"> start("bill");
inv <"s1"> corre("bill");
qes,
[
seq
rcv <"s1"> start(y);
x := y;
rcv <"s1"> corre(x)
qes
]
} (x)
Figura 2.4: Esempio di programma Blite con un deployment in cui è presente una
definizione di processo e due istanze ready-to-run.
Si deve osservare che dopo la scelta interna devono essere sempre esplicitate
(eventualmente con empty) entrambe le attività che realizzano le alternative.
In appendice è riportato il lessico e in notazione EBNF la grammatica completa che
definisce il linguaggio da noi implementato. In essa sono specificati i tipi supportati (attualmente stringhe, numeri -interi e decimali- e booleani) e la sintassi delle espressioni
logiche e aritmetiche.
2.4
Alcune osservazioni sulla semantica della correlazione
La semantica presentata in Sezione 2.2 descrive il comportamento dei programmi Blite
in modo molto elegante e sintetico. L’obiettivo del lavoro di tesi è stato quello di creare
un motore capace di eseguire i programmi producendo un comportamento equivalente a
quello specificato, nel rispetto di specifiche tecniche che tenessero conto il più possibile
di problematiche di efficienza, scalabilità e requisiti di memoria.
2.4 Alcune osservazioni sulla semantica della correlazione
51
Come vedremo nel capitolo seguente alla base dell’architettura software realizzata,
ci sono due componenti fondamentali:
• Un oggetto software chiamato ProcessManager, che, data una precisa definizione di processo, ha la responsabilità di creare le istanze e di metterle in esecuzione.
Tale oggetto ha inoltre la responsabilità di gestire i messaggi indirizzati alle porte
della propria definizione, memorizzandoli in opportune strutture dati.
• Un insieme di thread, ThreadPool, che eseguono concorrentemente le varie
istanze di processo e i flussi paralleli che si generano in esse.
Gli aspetti più critici nella realizzazione del comportamento specificato sono quelli
legati all’attribuzione dei messaggi alle diverse istanze e alla decisione se l’arrivo di un
messaggio produca o meno la creazione di una nuova istanza (regole (com) e (new) di
Tabella 2.4).
In riferimento alla prima problematica, la semantica definisce un ordinamento fra
tutte le istanze che contemporaneamente attivano una ricezione su una determinata porta11 e, in base a tale ordinamento, stabilisce l’attribuzione del messaggio. Di fatto in
un’architettura che usa una tecnologia di multithreading per realizzare la non sequenzialità specificata dalla semantica di Blite, non ha molto senso parlare di attività di
ricezione contemporaneamente attive su una porta. In pratica, quando il flusso di esecuzione di un’istanza arriva ad eseguire una ricezione su una determinata porta, si deve
poter decidere se un messaggio è destinabile a tale istanza solamente in base allo stato
attuale dell’istanza stessa.
Semplificando, per valutare se un messaggio può essere attribuito ad un’istanza che
esegue l’operazione rcv hpi o x̄, si può attuare la seguente procedura:
m := null
for each v in { messages on <p,o>}
if corr(x, v)
m := v
break iteration
if m is null start waiting on <p,o>
else
11
Con il termine porta si intende la coppia hservice-name, operation-namei, essa identifica
univocamente una funzionalità nell’interfaccia associata ad una definizione di processo.
52
Linguaggi per l’orchestrazione
consume m
continue execution
in cui “messages on <p,o>” identifica tutti i messaggi ricevuti sulla porta <p,o> e
la funzione corr(x, v), che restituisce un valore booleano, valuta se il messaggio
v può essere correlato all’istanza in base ai parametri attuali x, allo stato di memoria
dell’istanza e al correlation set definito dal programmatore.
In pratica, tale funzione può essere ricavata, in riferimento alla funzione
match(c, µ, x̄, v̄) definita in Sezione 2.2, nel seguente modo:



 f alse se x ∈ c ∧ x ∈ dom(µ) ∈ c ∧ v , µ(x)
corr(c, µ, x, v) = 

 true altrimenti
corr(c, µ, hi, hi) = true
corr(c, µ, x1 , v1 ) = b′
corr(c, µ, x̄2 , v̄2 ) = b′′
corr(c, µ, (x1 , x̄2 ), (v1 , v̄2 )) = b′ ∧ b′′
dove µ rappresenta lo stato di memoria associato all’istanza e c il correlation set
associato alla definizione di processo da cui l’istanza è ricavata.
Questa strategia risolve il problema della correlazione dei messaggi con le istanze di
processo, producendo un comportamento equivalente a quello specificato dalla semantica formale di Blite. Si fa notare infatti che la semantica non risolve più conflitti rispetto
all’implementazione realizzata, in quanto la regola (pass) porta ad attivare in modo non
deterministico le attività di ricezione delle diverse istanze.
I concetti di grado di definizione e di ordinamento delle istanze rispetto alla possibilità di consumare messaggi in arrivo, introdotti dalla semantica di Blite, risultano invece
determinanti per discriminare quando si deve creare una nuova istanza, e in particolare
per realizzare le multiple start activity.
Le multiple start activity sono attività di inizio istanza del tipo:
rcv ℓ1r o1 x¯1 ; a1 | rcv ℓ2r o2 x¯2 ; a2
in cui la start activity è costituita dalla composizione parallela di più ricezioni.
La specifica BPEL afferma, anche se in maniera non molto esplicita12 , che nel caso delle multiple start activity, la correlazione di un messaggio ad un’istanza abbia
precedenza sulla creazione di nuove istanze.
12
Per esempio, nella specifica di BEPEL4WS 1.1 [3] il comportamento associato alle multiple start
activity era addirittura descritto semplicemente con un esempio (sezione 1.6).
2.4 Alcune osservazioni sulla semantica della correlazione
53
Questo comportamento è espresso alla perfezione dalla semantica di Blite, che
determina in maniera univoca il comportamento del seguente termine:
{[(rcv hp1 i o hxi | rcv hp2 i o hx, zi) ; a] }{x}
k {{y 7→ v} ⊢ inv hp1 i o hyi}
k {{y1 7→ v, y2 7→ v′ } ⊢ inv hp2 i o hy1 , y2 i}
_ ...
. . . ≻−
{[(rcv hp1 i o hxi | rcv hp2 i o hx, zi) ; a] , {x 7→ v} ⊢ [rcv hp2 i o hx, zi) ; a]}{x}
k {{y1 7→ v, y2 7→ v′ } ⊢ inv hp2 i o hy1 , y2 i}
. . . ≻−
_ ...
{[(rcv hp1 i o hxi | rcv hp2 i o hx, zi) ; a] , {x 7→ v, z 7→ v′ } ⊢ [a]}{x}
in cui dalla definizione viene creata un’unica istanza di processo.
Nella implementazione realizzata il ProcessManager, nel momento in cui arriva un
nuovo messaggio, deve poter decidere se creare una nuova istanza o meno in base alle
informazioni di cui dispone in quel momento.
Si potrebbe pensare di risolvere il problema a livello sintattico rendendo distinguibili
le porte di ricezione su cui vengono create le istanze (create port). In pratica si potrebbe
dire che un messaggio su una porta utilizzata in una start activity conduce sempre alla
creazione di una nuova istanza, lasciando al programmatore la responsabilità di non
riutilizzare tali porte per effettuare la correlazione (in quanto tale correlazione non si
realizzerebbe mai).
È ovvio che questa tecnica non permetta di realizzare le multiple start activity, in cui
una porta è contemporaneamente di creazione e di possibile correlazione, e in generale
produca comportamenti non conformi alla semantica specificata. Invece, per ottenere
tali comportamenti, compreso il caso delle multiple start activity, si deve fare in modo
che il ProcessManager, all’arrivo di un messaggio ≪ p : o : v̄ ≫, attui la seguente
procedura:
On a new message v:
put v in { messages on <p,o> }
for each ’rcv <p> o x’ waiting on <p,o>
if cor(x, v) goto continue
54
Linguaggi per l’orchestrazione
if <p,o> is a create port
create a new instance i
start i
wait until all start rcv of i are activated
continue:
manage next message
In pratica viene creata una nuova istanza solo se il messaggio è indirizzato a una
porta di creazione e non c’è alcuna attività di ricezione (fra tutte le varie istanze) in attesa
su tale porta in grado correlare col messaggio. Inoltre si fa notare come la procedura
di gestione dei nuovi messaggi, dopo aver creato una nuova istanza e averla messa in
esecuzione, attenda che tutte le sue start activity si siano attivate, cioè si siano registrate
in attesa sulle rispettive porte o abbiano consumato qualche messaggio. Solo dopo
questo la procedura tornerà a gestire altri messaggi in arrivo. Cosı̀ si realizza l’atomicità
descritta dalla regola (new), in cui in un solo passo si crea una nuova istanza e si attivano
le attività di ricezione.
In questo modo si implementa la semantica delle multiple start activity secondo cui
la correlazione ha priorità sulla creazione di istanze.
Queste osservazioni risulteranno più chiare dopo aver letto il Capitolo 4 in cui verrà
presentata l’architettura e il modello di esecuzione dell’Engine. In questa fase si è illustrato come il processo di interpretazione della semantica risulti particolarmente delicato e come, in presenza di un’architettura multithreading, siano necessari algoritmi
e strutture dati non banalmente deducibili dalla semantica stessa. Inoltre è chiaro come risultino indispensabili meccanismi di sincronizzazione fra i vari thread eseguiti
concorrentemente.
Capitolo 3
Blite-se
Come già precedentemente detto il software realizzato può essere distinto in due progetti
a se stanti: Blite-se e Blide. In questo capitolo si descrive il primo; sono presentati
ad alto livello i diversi moduli con le loro funzionalità e in particolare per l’Engine,
il modulo predisposto all’esecuzione dei programmi Blite, si descrive l’architettura in
maniera dettagliata. Di questa si cerca di mettere in luce il modello di esecuzione ideato,
basato sul pattern Composite [29], secondo cui ogni attività è rappresentabile come un
componente che apporta il proprio contributo all’esecuzione dell’istanza di processo.
3.1
Progetto di un motore per l’orchestrazione
Blite-se (Blite Service Engine) è il progetto principale e implementa il linguaggio presentato nel capitolo precedente. Realizzare un linguaggio per l’orchestrazione di servizi
è un’attività complessa, che spazia in molteplici aree dello sviluppo software. Per questo
motivo è fondamentale organizzare il progetto in moduli distinti che realizzino le diverse funzionalità in maniera indipendente e che interagiscano fra loro con interfacce ben
definite, cercando di ridurre il più possibile le dipendenze dalle varie implementazioni.
Abbiamo individuato tre moduli principali con cui realizzare le funzionalità
necessarie:
1. Blite-se Definition Model: questo modulo realizza tutti quegli aspetti legati alla
definizione statica dei programmi Blite. In pratica qui è definita la grammatica
EBNF, secondo il formalismo di JJTree, da cui è stato ricavato l’analizzatore sintattico. In questo modulo è realizzato anche il modello statico per i costrutti del
linguaggio. Di fatto sono state implementate in maniera opportuna le classi i cui
56
Blite-se
Blite-se Defnition Model
Blite Static
Def. Model
Blite Parser
Blite-se Engine
Bilte Dynamic
Model Factory
Blite Runtime
Dynamic Model
Blite Execution Model
Blite Execution Machine
Blite-se Enviroment
EngineChannel Implementations
...
Blite-se Jbi Env.
Blite-se IMC Env.
Blite-se Local Env.
Figura 3.1: I moduli software del progetto Blite-se e le loro dipendenze.
oggetti andranno a creare i nodi dell’albero sintattico. Tali classi sono tutte discendenti della classe BltDefBaseNode che fornisce un’interfaccia comune per
accedere ai rispettivi nodi padre e figli, inoltre fornisce il metodo:
/ ∗ ∗ Accept the v i s i t o r . ∗ ∗ /
public Object j jt A c c ep t ( B l i t e P a r s e r V i s i t o r v i s i t o r , Object data ) ;
che permette la visita dell’albero secondo il pattern Visitor [29]. Oltre alle varie classi che implementano i nodi dell’albero sintattico ovviamente è reso disponibile il parser tramite la classe BliteParser. Questa dispone di due metodi
statici1 :
1
In JavaCC è stata usata l’opzione STATIC = “true”, in questo modo il parser top-down a discesa
ricorsiva è generato esclusivamente tramite metodi statici. L’efficienza risulta aumentata ma ovviamente,
vi può essere un unico compilatore per virtual machine e successive compilazioni devono prevedere una
di fase di reinizializzazione.
3.1 Progetto di un motore per l’orchestrazione
57
s t a t i c public void i n i t ( j a v a . io . InputStream stream )
s t a t i c p u b l i c B l t D e f B a s e N o d e p a r s e ( ) throws P a r s e E x c e p t i o n
che hanno le funzionalità rispettivamente di inizializzare il parser su una risorsa di
input e di eseguire l’analisi sintattica su di questa. Il valore restituito dal metodo
parse() è ovviamente un oggetto conforme al tipo BltDefBaseNode e nel caso
specifico l’oggetto istanza della classe BLTDEFCompilationUnit che costituisce
la radice dell’albero rappresentante la struttura del programma Blite. Da osservare l’eccezione ParseException eventualmente sollevata dal metodo parse(); con
essa è realizzata la gestione degli errori lessicali e sintattici incontrati durante una
compilazione. Dai suoi metodi è possibile risalire a tutte le informazioni utili
eventualmente per correggere l’errore, come: il token corrente, i token attesi, la
riga e la colonna a cui si è arrestata la compilazione.
Questo modulo non dipende dagli altri, ma da questi sarà utilizzato.
2. Blite-se Environment: questo modulo ha lo scopo di realizzare l’ambiente contenitore per l’Engine, facendo sı̀ che nella realizzazione di quest’ultimo si possa
astrarre dagli aspetti più tecnologici come le problematiche di comunicazione o di
deployment. Di fatto si vuole che il motore di esecuzione possa essere utilizzato
in scenari diversi e questo modulo realizza le astrazioni necessarie per rendere indipendente l’engine dalle diverse tecnologie di comunicazione. Per esempio una
possibilità molto interessante potrebbe essere quella di integrare l’engine Blite
con un ESB (Enterprise Service Bus) basato sul protocollo JBI, in questo caso
basterebbe realizzare un sottomodulo con le funzionalità specifiche per dialogare
con il bus. Un’altra ancora potrebbe essere quella di realizzare un environment
capace di supportare direttamente gli standard tipici della tecnologia Web Services, come WSDL, SOAP e HTTP, e poter quindi far dialogare i nostri programmi
Blite con ogni servizio di Internet. Attualmente è stato implementato un environment (Local Environment) capace di eseguire localmente più Engine e simulare
la rete e la comunicazione remota. Tale environment è stato creato per realizzare
i test di verifica dell’implementazione dell’Engine2 , e per allestire l’ambiente di
esecuzione di Blide, strumento nato per fare prototipi e simulazioni di processi.
A livello di Engine è definita e utilizzata la seguente interfaccia EngineChannel:
2
Nel progetto Local Environment è stata realizzata una Test Suite basata su JUnit in cui vengono
verificate le attività e i costrutti del linguaggio con test eseguiti nell’ambiente locale.
58
Blite-se
/∗ ∗
∗ This i n t e r f a c e r e p r e s e n t s the communication channel between
∗ t h e E n g i n e and t h e E n v i r o n m e n t .
∗
∗ @author p a n k s
∗/
public i n t e r f a c e EngineChannel {
/∗ ∗
∗ T h i s method i n i t i a l i z e s a c o m m u n i c a t i o n e x c h a n g e f r o m
∗ the invoking process to the requested endpoint
∗
∗ @param s e r v i c e I d
∗ @param o p e r a t i o n
∗ @param m e s s a g e C o n t a i n e r
∗ @param i n s t a n c e t h e P r o c e s s I n s t a n c e i n i t i a t i n g t h e e x c h a n g e .
∗
∗ @return O b j e c t m e s s a g e E x c h a n g e I d
∗
the i d e n t i f i c a t i o n key f o r communication p r o t o c o l s t a t e .
∗
∗/
public Object createExchange ( S e r v i c e I d e n t i f i e r serviceId ,
String operation ,
ProcessInstance instance );
/∗ ∗
∗ Send a m e s s a g e c o n t a i n e r i n t o t h e c r e a t e d e x c h a n g e .
∗ This a c r e a t i o n post s t e p in the communication .
∗
∗ @param m e s s a g e C o n t a i n e r , t h e c o n t e i n e r f o r a p p l i c a t i o n m e s s a g e e
∗
p o s s i b l e metadata .
∗ @param m e s s a g e E x c h a n g e I d t h e i d e n t i f i c a t i o n k e y f o r c o m m u n i c a t i o n
∗
p r o t o c o l exchange .
∗/
public void sendIntoExchange ( Object messageExchangeId ,
MessageContainer messageContainer ) ;
/∗ ∗
∗ Closes the exchange .
∗
∗ @param m e s s a g e E x c h a n g e I d
∗/
public void closeExchange ( Object messageExchangeId ) ;
}
Tramite questa, la parti del motore di esecuzione interessate alla comunicazione,
potranno inizializzare, sviluppare e concludere le comunicazioni con l’Environment; quest’ultimo invece utilizzerà l’interfaccia dell’Engine stesso per instradare
le richieste, che dall’esterno arriveranno verso le porte dei programmi Blite.
Da osservare come tale interfaccia definisca un modello di comunicazione total-
3.1 Progetto di un motore per l’orchestrazione
59
mente generico, che può essere utilizzato per realizzare molteplici protocolli per
lo scambio di messaggi, dal più semplice “fire and forget” ai più complessi, che
necessitano un mantenimento dello stato. Alla base di questo ci sono l’astrazione
MessageContainer, che permette di raggruppare i messaggi applicativi con eventuali metainformazioni e il messageExchangeId, che costituisce un identificativo
univoco per la sessione corrente di comunicazione.
Le varie tipologie di Environment implementeranno in maniera opportuna tale interfaccia in modo da supportare la tecnologia di comunicazione desiderata. L’enviroment, dopo aver creato le istanze della classe Engine, imposterà in esse l’oggetto che realizza l’opportuna implementazione di EngineChannel. Il modulo
dipende da Blite-se Definition Model per svolgere le funzionalità di compilazione
e deploy e ovviamente da Blite-se Engine per la realizzazione delle esecuzioni.
3. Blite-se Engine: questo modulo contiene l’implementazione vera e propria del
motore di esecuzione per i programmi Blite. Esso potrà essere eseguito all’interno di un opportuno Environment e, nel caso in cui quest’ultimo supporti la
comunicazione remota, essere istallato su un nodo di rete per rendere disponibili
i processi Blite ai client. Nei paragrafi successivi del capitolo verrà illustrata nel
dettaglio l’architettura di questa parte di software. Questo modulo utilizza il Blitese Definition Model per accedere al modello statico rappresentante la definizione
dei programmi Blite.
In Figura 3.1 è illustrato un diagramma (senza un formalismo specifico) che
rappresenta i vari moduli e le loro dipendenze.
60
Blite-se
3.2
Specifica dell’Engine
In uno scenario reale, in cui i processi sono distribuiti, avremo un Engine per locazione o
nodo di rete, e su ognuno di questi componenti sarà possibile istallare o rimuovere definizioni di processi Blite. In pratica, un engine gestirà un insieme di definizioni, creando
a partire da queste istanze di processi e utilizzerà l’Environment per interagire con gli
altri Engine. Dall’Environment stesso l’Engine riceverà notifiche riguardo l’accadere di
eventi, quali l’arrivo di messaggi indirizzati alle porte delle sue definizioni.
Dal punto di vista logico relazionale abbiamo già individuato le macro entità e
relazioni rappresentate in Figura 3.2.
Each Network Location has one Engine
Engine
+has
+manages
ProcessManager
0_*
ProcessInstance
0_*
+uses
1_1
Blite Definition
(AST)
...
...
Figura 3.2: Un Engine, ha n definizioni di processo e per ognuna di esse un
ProcessManager. Questo gestisce le ProcessInstace istanziandole dalla definizione.
Per ogni definizione istallata sull’Engine sarà presente un oggetto della classe ProcessManager che avrà il compito di gestire, nel loro ciclo di vita, le istanze di processo
derivate dalla definizione.
Prima di entrare nel dettaglio delle scelte architetturali ricapitoliamo quali sono le
caratteristiche peculiari di un sistema che deve gestire programmi per l’orchestrazione
di servizi, in modo che sia più facile da una parte comprendere e dall’altra giustificare
le scelte fatte.
A nostro vantaggio abbiamo che:
3.2 Specifica dell’Engine
61
• Un Engine contiene in generale un numero contenuto di definizioni, per cui non
ci interessa la scalabilità rispetto alla quantità di definizioni istallate su singolo
engine. Tale scalabilità al contrario può essere ottenuta aggiungendo altri engine
e installando definizioni su engine diversi.
• Una definizione (o programma) Blite avendo principalmente funzionalità di
integrazione avrà una lunghezza generalmente limitata.
• Poiché le operazioni fondamentali di un programma di questo genere sono invocazioni remote, le durate delle esecuzioni hanno ordini di grandezza minimi
dettati dai tempi caratteristici della rete. Per questo motivo non risulta determinante l’efficienza di esecuzione delle operazioni interne di un processo. Il nostro
engine non necessiterà di una particolare ottimizzazione rispetto all’efficienza di
esecuzione interna.
Al contrario, risultano particolarmente critici i seguenti aspetti:
• Per ciascuna definizione potrà essere richiesta la creazione di numerose istanze. La scalabilità rispetto al numero delle richieste remote e quindi di istanze di
processo risulta essere un prerequisito fondamentale.
• Se da un lato abbiamo detto che l’efficienza di esecuzione non è un aspetto particolarmente critico, dall’altro però ogni attività interna necessita di un elevato
grado di controllo e tracciabilità. Ogni attività deve potere essere eventualmente
terminata o abortita. Poiché in generale ogni istanza potrebbe avere un’immagine persistente o perlomeno essere soggetta ad un’attività di monitoring, l’Engine
necessiterà di un grado di controllo a livello di singola attività Blite.
Tenendo conto di queste iniziali considerazioni sono state fatte alcune scelte basilari di organizzazione del progetto e l’architettura software è stata basata sulle seguenti
specifiche fondamentali:
1. La compilazione di una definizione Blite (che eventualmente in un ambiente distribuito può essere fatta in fase di deploy) produce un modello statico della definizione stessa, che può essere implementato con una struttura ad oggetti. Tale
struttura può essere mantenuta in memoria presso l’Engine ed esplorata a runtime
per ricavare il flusso e la logica di esecuzione. Sempre in fase di deploy l’Engine
può ricavare tutte le informazioni per popolare le strutture dati in cui sono memorizzati i binding fra i nomi delle porte e le definizioni; anche tali strutture dati
possono essere mantenute in memoria.
62
Blite-se
2. Le richieste che giungono all’Engine non devono produrre un aumento delle risorse complessive mantenute dall’Engine. Ogni istanza di processo nel suo svolgersi
deve, man mano che procede, rilasciare le risorse di memoria acquisite. Anche il
numero complessivo dei thread deve essere limitato superiormente (generalmente
dell’ordine dell’unità). La realizzazione del parallelismo di attività deve essere
attuata tramite il pattern “Resources Pool”: ogni Engine deve disporre di un pool
di thread con cui eseguire in parallelo le attività secondo le definizioni Blite.
3. Il modello di esecuzione deve essere Activity Centric. L’engine deve trattare
ogni attività secondo un’astrazione generica che possa permettere di fattorizzare
i comportamenti comuni e mantenere semplice e pulita l’implementazione della
semantica di esecuzione del linguaggio.
3.3
Un modello per le attività
A questo punto, dopo aver esposto a grandi linee quelle che devono essere le caratteristiche fondamentali di un Engine, entriamo nel dettaglio del progetto dell’architettura.
Nella realizzazione di questa abbiamo scelto di utilizzare il formalismo degli oggetti
e delle classi secondo il consueto paradigma “Object Oriented”. Inoltre abbiamo preso come fonte di ispirazione il pattern Composite [29] cercandone una trasposizione
nella problematica dell’esecuzione di un programma Blite. In particolare l’astrazione di componente è stata applicata all’entità “attività”. Come i componenti contribuiscono alla realizzazione di un documento o di un’interfaccia utente, le singole attività
contribuiscono allo svolgersi dell’esecuzione di un processo Blite.
Inoltre la tipica struttura gerarchica presente staticamente negli elementi sintattici di
una definizione può essere naturalmente riprodotta a runtime tra i singoli passi di esecuzione, andato a completare l’analogia con le strutture gerarchiche ad albero, tipiche
dei tradizionali domini di applicazione del Composite Pattern.
L’entità fondamentale del nostro dominio applicativo è stata quindi individuata nella ActivityComponent, trasposizione a runtime dell’elemento sintattico Activity definito dalla grammatica di Blite. Ogni ActivityComponent è rappresentabile tramite
interfaccia del Listato 3.1 e descritta in Tabella 3.1.
package i t . u n i f i . d s i . b l i t e s e . e n g i n e . r u n t i m e ;
import i t . u n i f i . d s i . b l i t e s e . p a r s e r . B l t D e f B a s e N o d e ;
/∗ ∗
3.3 Un modello per le attività
63
ProcessInstance
FlowExecutor
Node
currentActivity
...
ActCp
ActCp
Node
ActCp
Node
...
Node
...
Runtime Dynamic
Execution Tree
Static Definition Tree (AST)
Figura 3.3: Il modello statico è utilizzato a runtime per costruire il modello dinamico
delle ActivityComponent.
∗ The b a s e u n i t o f r u n t i m e e x e c u t i o n o f a R u n t i m e P r o c e s s I n s t a n c e .
∗ The method < t t > d o A c t i v i t y < / t t > i t t h e k e y o f t h e e x e c u t i o n model .
∗
∗ @author p a n k s
∗/
public i nt erf a ce ActivityComponent {
public boolean d o A c t i v i t y ( ) ;
public ActivityComponent getParentComponent ( ) ;
public BltDefBaseNode getBltDefNode ( ) ;
public ExecutionContext getContext ( ) ;
}
Listing 3.1: Interfaccia base del modello di escuzione dell’Engine Blite
Lo scenario che si va a delineare è quindi quello di due strutture gerarchiche associate: una costituita dall’AST (Abstract Syntax Tree) ricavato dal parsing del codice
Blite e che come si detto è mantenuta nella sua interezza; l’altra costituita dall’albero
dinamico delle ActivityComponent che realizzano l’esecuzione a runtime (Figura 3.3).
Quest’ultima struttura, una per ogni istanza, non è però costruita in un unico momento
64
Blite-se
ActivityComponent
boolean doActivity()
Costituisce il metodo centrale per lo svolgersi dell’esecuzione del programma. L’invocazione di tale metodo su
un oggetto attività fa sı̀ che essa possa essere eseguita. Il
valore booleano restituito sarà il discriminante del fatto
che il flusso di esecuzione corrente dovrà o meno interrompersi. Ogni attività, oltre che eseguire se stessa, sarà
quindi anche responsabile nel guidare il flusso nel passo
successivo. Utilizzando la gerarchia ad essa nota imposterà la nuova attività corrente da eseguire (l’attività padre o figlio) e restituirà il valore true. Al contrario, potrà
interrompere il flusso corrente restituendo false.
ActivityComponent
Tale metodo restituisce l’elemento padre dell’attività corrente. In questo modo si realizza la struttura gerarchica
fra i vari componenti dell’esecuzione.
getParentComponent()
BltDefBaseNode
getBltDefNode()
Ogni attività componente dell’esecuzione è strettamente
associata ad un elemento sintattico del programma. Con
questo metodo ogni oggetto attività restituisce il nodo che
la definisce nell’albero sintattico ricavato dal parsing del
codice Blite.
Tabella 3.1: Metodi principali dell’interfaccia ActivityComponent
in fase di inizializzazione del processo, ma al contrario è istanziata man mano che l’esecuzione procede. Come già accennato le attività stesse avranno il compito di creare i
loro successori e di metterli in esecuzione. Inoltre gli oggetti attività già eseguiti dovranno essere rilasciati il prima possibile in modo da poter essere collezionati dal Garbage
Collector e rilasciare le risorse di memoria.
Per ogni tipologia di attività prevista dalla grammatica di Blite esiste una sottoclasse
specifica che implementa l’interfaccia ActivityComponent e che realizza in maniera opportuna, in rispetto della semantica, il metodo boolean doActivity(). Per ottimizzare il
disegno e fattorizzare il codice comune è stata ovviamente introdotta una classe astratta ActivityComponentBase, da cui ogni altra implementazione di ActivityComponent
3.3 Un modello per le attività
65
eredita le funzionalità comuni di base.
Anche la classe ProcessInstance, che modella con i suoi oggetti le varie istanze di processo nell’engine, implementa l’interfaccia ActivityComponent uniformando la struttura gerarchica di esecuzione (anche le attività che avranno come padre oggetti ProcessInstance potranno interagire con questi tramite l’interfaccia
ActivityComponent).
Le varie istanze di ActivityComponent del tipo specializzato verranno create tramite
una classe Factory ActivityComponentFactory, Listato 3.2, che espone il metodo ActivityComponent makeRuntimeActivity(·). In Tabella 3.3 è descritta la segnatura di tale
metodo.
/∗ ∗
∗ Factory c l ass to create d i f f e r e n t ActivityComponent implemetation Objects .
∗ @author p a n k s
∗/
public class ActivityComponentFactory {
p r i v a t e s t a t i c f i n a l A c t i v i t y C o m p o n e n t F a c t o r y SINGLETON =
new A c t i v i t y C o m p o n e n t F a c t o r y ( ) ;
private ActivityComponentFactory ( ) { }
/∗ ∗
∗ gets singleton instance
∗ @return A c t i v i t y C o m p o n e n t F a c t o r y
∗/
public s t a t i c ActivityComponentFactory getInstance ( ) {
r e t u r n SINGLETON ;
}
public ActivityComponent makeRuntimeActivity ( BltDefBaseNode bltDefNode ,
ExecutionContext context ,
ActivityComponent parentComponent ,
FlowExecutor executor ) {
...
}
}
Listing 3.2: ActivityComponentFactory, permette di creare oggetti ActivityComponent
In Figura 3.4 viene riportato un diagramma di classe abbastanza dettagliato per le
entità ActivityComponent.
66
Blite-se
Figura 3.4: Diagramma di classe per la gerarchia delle ActivityComponent
3.4 Esecuzione e parallelismo
67
ActivityComponentFactory
ActivityComponent
makeRuntimeActivity(
BltDefBaseNode bltDefNode,
ExecutionContext context,
ActivityComponent parentComponent,
FlowExecutor executor)
Permette di ottenere istanze opportune di oggetti ActivityComponent. Il
parametro bltDefNode individua l’elemento sintattico che definisce l’attività
specifica, in pratica il nodo nell’AST.
Il parametro parentComponent è l’attività padre nella gerarchia di esecuzione, mentre gli altri due parametri individuano rispettivamente il contesto
di esecuzione e l’esecutore del flusso in cui l’attività verrà creata. Tali
entità verranno descritte nelle sezioni
successive
Tabella 3.2: Factory per creare le opportune sottoclassi che implementano le attività
3.4
Esecuzione e parallelismo
Abbiamo visto che i componenti base per l’esecuzione sono oggetti delle varie sottoclassi che implementano l’interfaccia ActivityComponent e che l’esecuzione ha atto tramite
l’invocazione del metodo doActivity() su tali oggetti. A questo punto però dobbiamo
domandarci da chi e in che modo tale metodo è invocato. Nel rispondere a questa domanda dobbiamo tenere conto che in un Engine verranno eseguite contemporaneamente
molteplici istanze di processo e che inoltre il formalismo stesso del linguaggio dà la possibilità di richiedere l’esecuzione concorrente di diverse attività (costrutto flow). Quindi
su ciascun Engine dovranno essere disponibili più thread per poter realizzare un livello
di parallelismo sufficiente.
Si capisce bene che istanziare un nuovo oggetto Thread per ogni nuovo flusso logico presente sull’engine non sia un approccio assolutamente vantaggioso. Come già
accennato infatti tale politica non sarebbe per nulla scalabile rispetto al numero delle
richieste remote gestite dell’engine; inoltre, poiché ogni istanza di processo tendenzialmente trascorrerà la gran parte del tempo in attesa di comunicazioni remote3 , ci
3
Come già osservato i tempi di comunicazione remota sono di vari ordini di grandezza maggiori
rispetto a quelli delle operazioni interne per cui ogni istanza nel suo ciclo di vita si troverà ad occupare
68
Blite-se
troveremo con un gran numero di thread in stato di attesa, con uno spreco di risorse
del tutto ingiustificabile. Più banalmente gestire direttamente oggetti Thread è una pratica alquanto sconsigliabile 4 , in quanto può portare ad errori di programmazione o a
Memory Leaks nel caso in cui il ciclo di vita di tali oggetti non sia sempre ben gestito
dal programmatore. Per questi motivi si è scelto di utilizzare la tecnica del Pooling per
gestire un insieme di Thread a livello di Engine. Con tale tecnica si isola la gestione
della tecnologia di multitasking e si possono applicare politiche anche molto raffinate
capaci di adattare la quantità di risorse utilizzate al carico di lavoro da svolgere. Nella
implementazione attuale dell’Engine si è fatto uso dei thread pool forniti dalla classe
java.util.concurrent.Executors presente nella piattaforma standard Java 5.
La scelta che quindi è stata fatta per realizzare il parallelismo è la seguente: ogni
flusso logico attivo presente nelle varie istanze di processo sarà associato all’entità FlowExecutor (tale entità è già comparsa in alcuni diagrammi precedentemente illustrati
in questo capitolo, per cui il lettore avrà già intuito la sua funzionalità). Tali oggetti
presentano l’interfaccia descritta dal Listato 3.3, essa permette da un lato di impostare l’attività corrente che dovrà essere eseguita da uno dei thread del pool, dall’altro di
eseguire effettivamente tale attività.
package i t . u n i f i . d s i . b l i t e s e . e n g i n e . r u n t i m e ;
/∗ ∗
∗ T h e s e o b j e c t s a r e one t o one r e l a t e d t o a c t i v e d r u n n i n g f l o w on t h e e n g i n e .
∗ I t ’ s p o s s i b l e t o s e t t o c u r r e n t A c t i v i t y and t o e x e c u t e i t w i t h t h e mathod
∗ < t t > e x e c u t e C u r r e n t A c t i v i t y ( ) < / t t >.
∗
∗ @author p a n k s
∗/
public i n t e r f a c e FlowExecutor {
void s e t C u r r e n t A c t i v i t y ( ActivityComponent activityComponent ) ;
ActivityComponent g e t C u r r e n t A c t i v i t y ( ) ;
FlowOwner getOwner ( ) ;
void e x e c u t e C u r r e n t A c t i v i t y ( ) ;
}
Listing 3.3: I FlowExecutors saranno gli oggetti che realizzeranno i flussi di esecuzione
realmente la CPU per tempi quasi trascurabili rispetto ai tempi di attesa di eventi remoti.
4
Questo è ancor più vero dalla versione 5 in poi di Java, in cui sono stati introdotti i pacchetti java.util.concurrent.* che mettono a disposizione un framework ad alto livello per la concorrenza che
ottimizza e astrae l’uso delle API a più basso livello.
3.4 Esecuzione e parallelismo
69
parallela all’interno dell’Engine
A questo punto, quando ci sarà bisogno di creare un nuovo flusso di esecuzione
per l’ActivityComponent act, si dovrà, creare un nuovo FlowExecutor, impostarvi act
come attività corrente e renderlo disponibile ad un thread per l’esecuzione. Quest’ultimo
passaggio verrà realizzato tramite l’interfaccia dell’Engine che dispone del seguente
metodo per notificare gli executor pronti per essere eseguiti e metterli a disposizione del
pool di thread:
/∗ ∗
∗ Add a r e a d y t o r u n e x e c u t o r t o q u e u e where t h e w o r k i n g t h r e a d s g e t
∗ the job to execute .
∗
∗ @param e x e c u t o r
∗/
public void queueFlowExecutor ( FlowExecutor e x e c u t o r ) ;
Inoltre, quando un flusso giungerà a conclusione (l’istanza di processo termina o una
esecuzione parallela definita in una flow Activity si conclude), si dovrà registrare tale
evento e produrre eventualmente altri effetti. Per far sı̀ che si realizzi questa necessità
si è introdotto il concetto di FlowOwner, Listato 3.4. Ogni attività che nel suo eseguirsi
si troverà a creare nuovi flussi di esecuzione, e quindi oggetti FlowExecutor, dovrà
implementare l’interfaccia FlowOwner e impostare se stessa nel FlowExecutor da essa
creato. In questo modo quando il flusso logico terminerà, il FlowOwner potrà ricevere
notifica di tale accadimento tramite l’invocazione del metodo flowCompleted().
package i t . u n i f i . d s i . b l i t e s e . e n g i n e . r u n t i m e ;
/∗ ∗
∗ T h i s i n t e r f a c e r e p r e s e n t s a owner o f a l o g i c a l p a r a l l e l f l o w e x e c u t i o n
∗
∗ @author p a n k s
∗/
p u b l i c i n t e r f a c e FlowOwner {
void flowCompleted ( ) ;
}
Listing 3.4: Interfaccia FlowOwner, gli oggetti che creeranno i flussi di esecuzione
dovranno implementare tale interfaccia.
A questo punto è lecito domandarsi quando si dovrà considerare terminato un flusso
di esecuzione. In generale si applica il seguente ragionamento. Le varie ActivityCompo-
70
Blite-se
nent, legate in una struttura gerarchica che riflette la definizione statica del programma,
termineranno la loro esecuzione mettendo come attività corrente nel loro FlowExecutor
la propria attività padre (parentComponent). In accordo con questa osservazione risulta
quindi corretto affermare che un flusso di esecuzione può essere considerato terminato
dal FlowExecutor quando questo si troverà ad eseguire come attività corrente proprio
il suo FlowOwner. In questo caso su di esso il FlowExecutor non dovrà invocare il
metodo doActivity(), ma il metodo flowCompleted() e fatto questo, dovrà terminare di
eseguire il flusso. Il codice del Listato 3.5 spiega meglio di mille parole la logica alla
base di tutto il modello di esecuzione dell’Engine; in poche righe è sintetizzato il cuore
dell’architettura.
package i t . u n i f i . d s i . b l i t e s e . e n g i n e . r u n t i m e . imp ;
...
/∗ ∗
∗ @author p a n k s
∗/
p u b l i c c l a s s F l o w E x e c u t o r I m p implements F l o w E x e c u t o r {
private ActivityComponent c u r r e n t A c t i v i t y ;
p r i v a t e FlowOwner flowOwner ;
...
/∗ ∗
∗ T h i s ’ s t h e c o r e o f b l i t e e n g i n e e x e c u t i o n model .
∗
∗ The c u r r e n t e a c t i v i t y i s e x e c u t e d u n t i l i t ’ s n o t t h e
∗ flowOwner .
∗/
public void e x e c u t e C u r r e n t A c t i v i t y ( ) {
w h i l e ( ! ( c u r r e n t A c t i v i t y . e q u a l s ( flowOwner ) ) ) {
boolean i s N e w C u r r e n t A c t i v i t y S e t = c u r r e n t A c t i v i t y . d o A c t i v i t y ( ) ;
i f (! isNewCurrentActivitySet ) {
return ; / / the flow i s suspended
}
}
flowOwner . f l o w C o m p l e t e d ( ) ;
/ / t h e f l o w has f i n i s h e d
}
...
}
Listing 3.5: Classe che implementa il FlowExecutor. Il metodo executeCurrentActivi-
3.4 Esecuzione e parallelismo
71
ty() è il cuore del modello d’esecuzione realizzato dall’Engine. Esso esegue l’attività
corrente fintanto che essa non sia uguale al FlowOwner.
In generale possiamo quindi dedurre le seguenti affermazioni che ci possono aiutare
a definire alcune proprietà invarianti.
• Un FlowExecutor non invocherà mai il metodo doActivity del suo FlowOwner.
Viceversa, di questo potrà invocare il metodo flowCompleted().
• Su un oggetto ActivityComponent che implementa anche l’interfaccia FlowOwner, il metodo doActivity() verrà invocato dal FlowExecutor dell’attività
padre.
• Su di un oggetto ProcessInstance, che è il FlowOwner del flusso principale dell’istanza e l’unica attività senza padre, il metodo doActivity() non verrà mai invocato
da nessun FlowExecutor.
• Il metodo doActivity() di una ProcessInstance verrà invocato dall’Engine stesso in
fase di creazione dell’istanza.
Oltre a creare, eseguire e terminare flussi sarà anche necessario sospenderne alcuni
già in esecuzione, si veda per esempio il caso dell’attività di ricezione che deve arrestare
il flusso corrente in attesa di un evento remoto. In questo caso, l’attività, utilizzando
l’interfaccia dell’Engine e in particolare il seguente metodo:
. . . <E n g i n e i n t e r f a c e > . . .
/∗ ∗
∗ Put t h e e x e c u t o r i n t h e w a i t i n g queue f o r t h e incoming e v e n t .
∗
∗ @param e x e c u t o r
∗ @param e v e n t K e y
∗/
p u b l i c v o i d a d d F l o w W a i t i n g E v e n t ( F l o w E x e c u t o r e x e c u t o r , InComingEventKey e v e n t K e y ) ;
potrà mettere in attesa il suo FlowExecutor su un evento identificato dalla chiave
eventKey, che essa stessa aveva provveduto precedentemente a ricavare5 . Fatto questo, l’attività ritornerà dal proprio metodo doActivity con il valore false, in questo modo il FlowExecutor terminerà l’esecuzione del Flow corrente. Nel momento in cui il
5
Si usa qui il termine ricavare e non generare in maniera voluta. Le chiavi di evento non sono identificate da oggetti in memoria, ma hanno una loro entità indipendente dagli oggetti che possono essere creati
per rappresentarle. Per un esempio le chiavi utilizzate per la ricezione saranno costituite dalla coppia
hservice-name, operation-namei, tale coppia verrà indicata come portId.
72
Blite-se
messaggio sarà recapitato, l’Engine potrà individuare il FlowExecutor tramite la chiave eventKey e rimetterlo in esecuzione. Poiché l’attività corrente di quest’ultimo sarà
rimasta l’attività di ricezione, essa potrà riprendere l’esecuzione, consumando il messaggio e permettendo al suo flusso di esecuzione di procedere. Nella sezione successiva
verrà illustrato nel dettaglio come si realizza la comunicazione e la notifica degli eventi.
In Figura 3.5 è rappresentato un diagramma di classe che raffigura, fra le altre entità,
il FlowExecutor e FlowOwner e le principali relazioni che le coinvolgono.
3.5
Comunicazione ed eventi
Da quanto abbiamo già esposto risulta chiaro che l’esecuzione di un processo è caratterizzata dallo svolgersi di attività interne e l’accadere di eventi esterni di cui le attività
possono essere in attesa. In generale si può considerare che gli eventi sono generati a
livello di Environment, e possono essere prodotti dall’arrivo di nuovi messaggi verso le
porte locali o dalla notifica degli ack/nok delle invocazioni locali verso porte remote. Attualmente le funzionalità espresse da Blite non individuano eventi diversi da queste due
tipologie. Nell’Engine però si è preferito realizzare un meccanismo generico di notifica
di eventi che soddisfacesse i requisiti attuali del linguaggio ma che non escludesse eventuali possibilità di estensione verso altre caratteristiche tipiche di BPEL (comunicazione
Request-Response, EventActivity, ecc).
Per l’implementazione dello schema di comunicazione prescelto sono richieste le
seguenti funzionalità:
1. A livello di attività o di ProcessManager stesso sarà necessario ricavare una chiave
univoca per uno specifico evento. I tipi di evento e le regole per generare di volta
in volta le chiavi verranno esposti di seguito. Per il momento può bastare aver
chiaro che una chiave individua in maniera univoca una particolare tipologia di
evento e, all’interno di questa, un evento specifico o un insieme di eventi gemelli.
2. In un qualsiasi momento al ProcessManager potranno essere notificati eventi. In
tal caso esso dovrà provvedere a ricavarsi la chiave e a memorizzare in una mappa
associativa l’evento con la chiave, per poterlo poi rendere disponibile alle attività
interessate. Il ProcessManager dovrà anche provvedere a “risvegliare” tutti i flussi
di esecuzione che eventualmente si sono messi in attesa di quell’evento specifico.
3. In un qualsiasi momento, e anche più volte nel suo ciclo di vita, un’attività potrà
interrogare l’Engine (o meglio il ProcessManager responsabile della sua istanza)
3.5 Comunicazione ed eventi
Figura 3.5: Diagramma di classe FlowExecutor, FlowOwner e ThreadPool
73
74
Blite-se
chiedendogli se un evento associato ad una particolare chiave sia avvenuto. In
caso affermativo l’attività potrà consumare l’evento.
4. Un’attività potrà avere la necessità di mettersi in attesa di un particolare evento
non ancora avvenuto; il tal caso dovrà sospendere il suo flusso di esecuzione
e notificare questo al ProcessManager con il riferimento alla chiave dell’evento
d’interesse.
In particolare, si può vedere come queste funzionalità di base possono essere utilizzate nel caso dell’attività ReceiveActivity e come l’attività stessa collabora con il
ProcessManager affinché si realizzi la ricezione di messaggi. La ReceiveActivity nel
suo metodo doActivity() compierà i seguenti passi:
1. Ricava la chiave d’evento eventKey. In questo caso particolare la chiave sarà di
tipo RequestInComingEventKey e la sua unicità sarà costituita dal portId (ovvero la coppia hservice-name, operation-namei) della porta su cui si sta eseguendo l’operazione di ricezione. Ovviamente la ReceiveActivity potrà ricavare
il portId dal nodo della definizione sintattica a lei associata.
2. Richiede al ProcessManager il set degli eventi associati all’eventKey. Se non c’è
alcun evento associato, mette in attesa il FlowExecutor sull’evento eventKey, e
termina restituendo false; al contrario, si procede con il passo successivo.
3. Analizza il set degli eventi (che in questo caso possono essere identificati con tutti
i messaggi indirizzati alla porta in questione non ancora consumati) per vedere se
ce ne possa essere uno indirizzabile all’istanza della ReceiveActivity in questione.
Questo controllo è fatto in base alle regole di correlazione, in pratica vengono
implementate la procedura e la funzione corr(x, v) presentate nella Sezione
2.4. Se un tale messaggio non viene identificato, la ReceiveActivity mette in attesa
il FlowExecutor sull’evento eventKey e termina restituendo false, al contrario
procede con il passo successivo.
4. In questo caso è stato individuato un messaggio inviato alla porta e all’istanza
in questione. Tale messaggio viene consumato aggiornando lo stato delle variabili coinvolte nella ricezione, viene impostata la parentComponet come attività
corrente del FlowExecutor e il metodo termina restituendo true.
3.5 Comunicazione ed eventi
75
D’altro canto un oggetto ProcessManager nel metodo manageRequest( ServiceIdentifier service, String operation, MessageContainer messageContainer)6 eseguirà
le seguenti operazioni, in accordo con la procedura presentata nella Sezione 2.4:
1. Verifica che la terna hservice-name, operation-name, n.parti messaggioi
identifichi effettivamente una porta valida per la definizione di processo in questione. Se non è cosı̀, notifica una situazione di errore all’Environment, il quale
provvederà ad inviare un NOK al processo invocante.
2. Ricava l’eventKey di tipo RequestInComingEventKey associata alla porta e
con questa provvede a memorizzare il messaggio in arrivo in una struttura dati
associativa.
3. Se la porta in questione individua una start activity e non vi è nessuna attività
di ricezione (fra tutte le varie istanze) in attesa su tale porta che possa correlare
con il messaggio, crea una nuova istanza di processo e la mette in esecuzione;
alternativamente, se la porta non è associata ad una start activity risveglia tutti i
FlowExecutor in attesa di eventi individuati dalla eventKey.
4. Se è stata creata una nuova istanza, il ProcessManager, prima di gestire un nuovo
messaggio, attende che tutte le start ReceiveActivity di questa si siano attivate.
Già pensando che le due procedure precedenti saranno eseguite concorrentemente,
si capisce che un punto particolarmente critico del meccanismo di notifica/consumo di
eventi sta nel fatto che l’elevato grado di parallelismo presente possa portare ad una
perdita eventi (ovvero mancata consegna di messaggi). Quest’ultima è di fatto un’ipotesi inammissibile e che deve essere assolutamente evitata. Di fatto essa si manifesterebbe allorché le precedenti attività parallele fossero sequenzializzate, per esempio nel
seguente modo:
• La ReceiveActivity arriva ad eseguire metà del passo 2, cioè ricerca eventi non
trovandoli ma non arriva a registrare il FlowExecutor come in attesa dell’evento.
• Il controllo passa al ProcessManager a cui viene effettivamente notificato l’evento
di interesse dell’attività. Esso però non trovando nessun FlowExecutor in attesa
registra l’evento e termina.
6
Bisogna notare che l’invocazione di tale metodo sarà scatenata lato Environment, e che la sua esecuzione avverrà a carico di un Thread allocato a livello stesso di Environment, logicamente del tutto
indipendente dai thread del pool dell’Engine.
76
Blite-se
• Il controllo passa di nuovo alla ReceiveActivity che registra il suo FlowExecutor
come in attesa. Essendo però l’evento già accaduto nessuno a questo punto sarà in
grado di notificarlo all’attività, di fatto si ha una mancata consegna del messaggio.
Per evitare questi scenari alquanto deprecabili sono attuabili due strategie. Una
potrebbe essere quella di dotare l’Engine di una procedura temporizzata che ogni dato intervallo di tempo analizza la mappa degli eventi accaduti e risveglia gli eventuali
FlowExecutor registrati in attesa di essi. In questo modo il ProcessManager sarebbe
sollevato da quest’ultimo compito e la possibilità di avere la sequenzializzazione sopra
descritta non sarebbe più un problema. L’altra strategia potrebbe essere quella di eseguire alcuni passi delle procedure in mutua esclusione, di fatto realizzare due sezioni
critiche su un medesimo monitor; la prima, per la ReceiveActivity dovrebbe comprendere i passi 2 e 3, mentre quella per il ProcessManager dovrebbe raggruppare i passi 2
e 4, o nel caso della creazione di una nuova istanza, i passi 2 e 3.
Per semplicità di realizzazione, e poiché ad una analisi poco più attenta si capisce
che di un certo grado di sincronizzazione non si può fare a meno7 , si è scelto di implementare la seconda strategia. Di fatto si è scelto la possibilità di realizzare una sincronizzazione a livello di definizione di processo. Il ProcessManager espone il seguente
metodo:
/∗ ∗
∗ Provides a lock at Process d e f i n i t i o n Level
∗
∗ @return O b j e c t . T h i s o b j e c t i s u s e d t o g e t a l o c k a t p r o c e s s d e f i n i t i o n l e v e l .
∗/
public Object getDefinitionProcessLevelLock ( ) ;
che restituisce un oggetto utilizzabile per sincronizzare un’istanza di processo con il
ProcessManager, ma anche le diverse istanze fra di loro. Tale necessità è evidente in
quanto ci saranno strutture dati (ad esempio la mappa che mantiene gli eventi/messaggi
per le porte) che saranno condivise fra le diverse istanze di una definizione di processo.
In un certo modo si può pensare che le diverse istanze siano in competizione (“race”) su
tali eventi nel limite di quelle che sono le regole di correlazione stabilite dal linguaggio.
Nel momento in cui è necessario accedere alle strutture dati condivise, una sezione
critica può essere creata nel seguente modo:
M e s s a g e C o n t a i n e r consumedMes = n u l l ;
InComingEventKey i c e k =
InComingEventKeyFactory . createRequestInComingEventKey ( p o r t I d ) ;
7
Se non altro l’accesso alle strutture dati deve essere sincronizzato fra i vari thread.
3.5 Comunicazione ed eventi
77
s y n c h r o n i z e d ( manager . g e t D e f i n i t i o n P r o c e s s L e v e l L o c k ( ) ) {
L i s t < M e s s a g e C o n t a i n e r > mcs = manager . p r o v i d e E v e n t s ( i c e k ) ;
f o r ( M e s s a g e C o n t a i n e r mc : mcs ) {
i f ( c o r r ( mc , f o r m a l P a r a m s ) ) {
consumedMes = mc ;
mcs . remove ( mc ) ;
break ;
}
}
i f ( consumedMes == n u l l )
manager . g e t E n g i n e ( ) . a d d F l o w W a i t i n g E v e n t ( g e t E x e c u t o r ( ) , i c e k ) ;
}
Il codice precedente è esattamente l’implementazione dei passi 2 e 3 della procedura, precedentemente descritta, realizzata dalla ReceiveActivity per consumare i
messaggi in arrivo.
Abbiamo detto che che il ProcessManager, nel momento in cui crea un nuova
istanza di processo, deve attendere che tutte le RecieveActivty di questa si siano attivate
prima di gestire un nuovo messaggio. Tale attesa viene implementata con la seguente
strategia: il metodo manageRequest(·), che è definito synchronized cosı̀ che sia
eseguito da un solo thread alla volta, si conclude con il seguente codice:
i f ( n e w I n s t a n c e != n u l l ) {
synchronized ( newInstance ) {
newInstance . a c t i v e t e ( ) ;
try {
newInstance . wait ( ) ;
} c a t c h ( I n t e r r u p t e d E x c e p t i o n ex ) { \ l d o t s }
}
}
in questo modo il processo di gestione dei messaggi in arrivo è bloccato. La nuova
istanza newInstance riceverà notifica delle sue start activity dell’avvenuta attivazione
e, nel momento in cui tutte saranno attivate, sbloccherà il thread in attesa, e quindi tutto
il processo di gestione dei messaggi in arrivo alla definizione di processo, tramite la
seguente operazione:
synchronized ( t h i s ) {
this . notify ();
}
Tramite questo accorgimento, come osservato in Sezione 2.4, si realizza la semantica
78
Blite-se
delle multiple start activity.
Lo schema di comunicazione di Blite risulta semplificato rispetto al modello proposto da BPEL. In Blite sono possibili solamente invocazioni asincrone, in cui un’istanza
invoca un’operazione remota con degli input senza attendere nessuna risposta; il risultato dell’elaborazione sarà reso disponibile all’invocante tramite una successiva ricezione
sfruttando il meccanismo della correlazione.
È stato scelto di realizzare tale schema di comunicazione applicativa tramite lo schema più semplice di comunicazione infrastrutturale, ovvero lo schema One-Way (o se si
preferisce In-Only secondo la terminologia delle “WSDL 2.0 Extensions” [12]). Tale
schema può essere rappresentato nell’ambito della nostra architettura come in Figura
3.6. In pratica un’istanza consumer invocherà tramite una request una porta presso
un Engine remoto, provider del servizio richiesto. Questo, dopo aver verificato la disponibilità della porta richiesta e la conformità formale del messaggio in arrivo rispetto
alla stessa, invierà un acknowledgment status-done al richiedente, il quale potrà continuare la sua esecuzione. Al contrario, se si sarà verificato un qualche problema, un
segnale status-error verrà recapitato al consumer; questo potrà esser fatto dall’Engine remoto ma anche dall’Environment locale stesso qualora si arrivasse allo scadere
di un certo timeout senza aver ricevuto alcuno status di risposta (caso quest’ultimo in
cui il provider risulta non raggiungibile). Da notare come tale schema di comunicazione realizzi un modello asincrono di cooperazione dei servizi secondo quanto specificato
dalla semantica Blite. Il consumo e l’elaborazione del messaggio da parte di un’istanza
provider sono eseguiti in maniera del tutto indipendente, in termini di dipendenze temporali, e quindi anche eventuali errori nella elaborazioni della richiesta non verranno
comunicate al consumer all’interno di questa comunicazione.
Tale modello ci è sembrato il più adatto per poter realizzare i comportamenti
seguenti:
• Un’istanza di processo può invocare operazioni di altri servizi in maniera asincrona potendo continuare a svolgere altre attività mentre l’elaborazione remota è in
atto.
• Se un’istanza invoca un servizio che non esiste, o che è momentaneamente indisponibile o con modalità non conformi, l’istanza deve ricevere notifica di tale
accadimento.
Di fatto questo è sembrato il giusto compromesso fra una comunicazione sincrona
3.5 Comunicazione ed eventi
79
Provider Engine
request
Consumer
Instance
Port
status-done
Provider
Instance
Consumer Engine
Figura 3.6: Il modello di comunicazione One-Way realizzato dai Blite Engine.
(che del resto può sempre essere riprodotta) e una comunicazione puramente “fire-andforget” come specificato nella semantica originale di Blite, in cui le istanze procedono
in maniera totalmente svincolata senza poter fare nessuna ipotesi sull’esito della comunicazione. Inoltre questo modello di comunicazione è quello che meglio si adatta
al binding su protocolli di trasporto tipo HTTP, in cui ad una richiesta c’è sempre una
risposta del server che chiude la connessione. In HTTP un’invocazione remota secondo
lo schema One-Way può essere implementata come segue:
• il client esegue una HTTP GET o HTTP POST
• il server risponde con 202 Accepted, nel caso il messaggio possa essere accettato,
o con un error code della serie 400 o 500 (es. 400 Bad Request), nel caso in cui
ci sia un qualche problema con la richiesta ricevuta.
Per concludere questa sezione facciamo vedere come lo schema di comunicazione One-Way può essere facilmente realizzato con il modello ad eventi previsto dalla
nostra architettura. Per far questo riportiamo i passi logici eseguiti dal componente
InvokeActivity:
1. Si costruisce il messaggio e l’oggetto MessageConteniner, utilizzato per inviare
il messaggio stesso nell’EngineChanel (si veda la Sezione 3.1). Si crea un identificativo del Service Provider tramite il seguente metodo messo a disposizione
dall’interfaccia del ProcessManager:
80
Blite-se
/∗ ∗
∗ Resolve PartnerLink at rintime .
∗
∗ @param p a r t n e r s m D e f S t a t i c d e f i n i t i o n o f t h e P a r t n e r L i n k
∗ @param v a r i a b l e S c o p e R u n t i m e v a r i a b l e s c o p e
∗
∗ @return S e r v i c e I d e n t i f i e r t h e r u n t i m e p a r t n e r l i n k .
∗/
p u b l i c S e r v i c e I d e n t i f i e r r e s o v l e P a r t e r L i n k ( BLTDEFInvPartners p a r t n e r s D e f ,
VariableScope variableScope ) ;
2. Tramite il seguente metodo:
InComingEventKey i n v o k e ( S e r v i c e I d e n t i f i e r s e r v i c e I d , S t r i n g o p e r a t i o n ,
MessageContainer messageContainer ) ;
fornito dall’interfaccia del ProcessManager, si avvia la comunicazione. Si osservi che il metodo restituisce un oggetto eventKey rappresentante come sempre
una chiave di tipo InComingEventKey. Nel caso particolare sarà un oggetto della classe StatusInComingEventKey che individuerà l’arrivo dello status associato
alla richiesta. Questo tipo di chiavi verranno generate a livello di Environment negli strati di più basso livello e le caratteristiche di unicità dipenderanno fortemente
dalla tecnologia di comunicazione utilizzata. Per esempio nell’ambito della tecnologia JBI [24] tali chiavi potranno coincidere con i MessageaExchange, mentre
in ambito puramente TCP potranno essere ricavate dagli identificativi di connessione. In ogni modo a livello di Engine è possibile astrarre (e lo si deve fare)
completamente dalla loro natura e utilizzarle semplicemente come identificatori
univoci di eventi.
3. In sezione critica sul monitor DefinitionProcessLevelLock, si prova a consumare l’evento associato all’eventKey. Se questo è effettivamente già disponibile
si valuta lo status di ritorno. Se si è avuto uno status-done si termina l’attività
mettendo come attività corrente del FlowExecutor il parentComponet e si restituisce true, se invece si è avuto status-error si avvia la procedura di errore. Se
l’evento associato all’eventKey non fosse ancora disponibile si registra il FlowExecutor come in attesa dello stesso e si termina il metodo doAtivity() restituendo
false.
4. Quando l’evento atteso sarà disponibile il FlowExecutor verrà risvegliato e l’invokeActivity potrà essere di nuovo messa in esecuzione. Questa, disponendo già di
3.6 Contesto, FaultHandler e Compensazione
81
eventKey, potrà rendersi conto di aver già eseguito l’invocazione e potrà semplicemente consumare l’evento atteso e procedere nell’analisi dello status come descritto sopra. Si osservi che in questo caso non sarà necessario operare in sezione
critica.
3.6
Contesto, FaultHandler e Compensazione
Il contesto è senz’altro quello uno dei costrutti più caratteristici di BPEL. Tale costrutto
è stato riprodotto con le opportune semplificazioni anche in Blite e nella versione qui
implementata.
Semplificando e rimanendo nell’ambito del linguaggio implementato, un contesto
è un raggruppamento logico di attività (di fatto realizzato da quella che verrà indicata con il nome ContextActivity), a cui possono essere associati un FaultHandler e un
CompensationHandler8 . I due handler non sono altro che due attività, la FaultHandlerActivity e la CompensationHandlerActivity che hanno funzionalità in un certo senso
complementari.
• La FaultHandlerActivity, è un’attività che deve essere eseguita nel caso in cui
sia sollevato un Fault non gestito durante l’esecuzione della ContestActivity. Di
fatto si può pensare che la ContestActivity non sia stata completata a causa del
verificarsi di una situazione di errore e che a livello del contesto di definizione
della ContestActivity stessa si possa gestire l’errore tramite l’esecuzione della
FaultHandlerActivity. Quindi la FaultHandlerActivity entra in gioco nel momento
in cui la ContestActivity non completa.
• Al contrario, CompensationHandlerActivity entra in gioco solo nel caso in cui
ContestActivity completi la propria esecuzione con successo, cioè senza che si
siano verificati errori non gestiti. Di fatto la CompensationHandlerActivity può
essere vista come una serie di operazioni capaci di annullare ciò che è stato fatto
dalla ContestActivity nel suo complesso. Supponiamo di avere un contesto c2,
a sua volta contenuto in un contesto c1 (cioè c2 è una sotto attività di a1, ContestActivity di c1), e che c2 definisca la ContestActivity a2 e la CompensationHandlerActivity ch1. Nel caso in cui a2 completi con successo la sua esecuzione,
8
Nel caso in cui il programmatore non codifichi direttamente gli Handler, sono previste due versioni
di default. Il DefaultFaultHandler prevede semplicemente la ThrowActivity, in questo modo si lascia la
gestione dell’eccezione al contesto di livello superiore, mentre il DefaultCompensationHandler prevede
la semplice EmptyActivity.
82
Blite-se
ch2 verrà resa nota a c1 che la potrà utilizzare per annullare gli effetti di a2 qualora si verifichi qualche successivo errore che impedisca a a1 di completare con
successo.
Da quanto detto si capisce che l’esecuzione degli handler di un contesto c è strettamente legata al verificarsi di situazioni di errori: la FaultHandlerActivity è lanciata qualora si verifichi un’eccezione nell’esecuzione della ContestActivity di c stesso,
la CompensationHandlerActivity è lanciata qualora completato c si verifichi un errore
nell’esecuzione di una qualche attività “sorella” di c 9 .
Il verificarsi di un’eccezione, oltre a mettere in esecuzione le eventuali CompensationHandlerActivity e FaultHandlerActivity associate, ha un altro effetto: la terminazione di tutti i flussi “fratelli” o discendenti dei fratelli del flusso in cui si genera il fault.
Per capire gli effetti prodotti dal verificarsi di un’eccezione si faccia riferimento alla
Figura 3.7 dove è rappresentato il seguente scenario a runtime:
• Il contesto base C0 definisce il FaultHandler fh0.
• L’esecuzione della ContestActivity di C0 produce tre flussi di esecuzione parallela
f1, f2 e f3.
• Nel flusso f1 viene eseguito in contesto C1 che termina, installando in C0 il
CompensationHandler ch1.
• Nel flusso f2 vine creato un nuovo contesto C2.
FlowActivity avvia altri due flussi paralleli f4 e f5.
All’interno del quale un
• Nell’esecuzione del flusso f3 viene generato un fault che dovrà essere gestito a
livello del cotesto C0.
• Il fault produce la terminazione dei flussi f1 e f2 e a cascata dei flussi f4 e f5.
• Il fault produce la creazione di un contesto protetto in cui saranno eseguite in
sequenza la CompensationHandlerActivity ch1 e la FaultHandlerActivity fh0.
Come si può vedere dalla Figura 3.7 la notifica e gli effetti di un fault si propagano
nelle due direzioni opposte nella gerarchia a runtime delle attività. Dall’attività che ha
9
Nell’ambito della sintassi e semantica di Blite l’espressione “attività sorella” ha il seguente significato: “attività che è eseguita nel medesimo contesto immediatamente contenente il contesto
c”.
3.6 Contesto, FaultHandler e Compensazione
83
C0: ch1; fh0
f1
f2
f3
C2
C1
f4
f5
FAULT!!
ch1
terminate
terminate
terminate
Figura 3.7: Un’eccezione sollevata in un flusso di esecuzione produce la terminazione
di tutti i flussi paralleli e la conseguente messa in esecuzione di Compensation Handler
istallati e FaultHandler definiti nel Contesto padre dei flussi.
scatenato il fault, risalendo i padri, raggiunge il primo contesto e da questo riscende
verso le attività figlie per interrompere i vari flussi paralleli. Questo andamento di salita
e discesa dell’informazione, legata al verificarsi di un’eccezione, è la caratteristica
peculiare della semantica di BPEL e quindi di Blite e, sulla base di questa caratteristica,
sono stati disegnati le componenti e le interfacce software dell’Engine predisposte alla
gestione dei contesti e delle eccezioni.
L’entità principale designata per la realizzazione dei meccanismi esposti è stata individuata nel ExecutionContext, la cui interfaccia, citata più volte nelle sezioni precedenti, è presentata nel Listato 3.6. In particolare si è visto che un oggetto conforme a tale
interfaccia era presente nella segnatura del metodo makeRuntimeActivity() della classe
ActivityComponentFactory. Difatti ogni oggetto ActivityComponent nascerà all’interno di un contesto di esecuzione ExecutionContext. Le classi concrete che implementano
l’interfaccia ExecutionContext sono tre, come si vede dalla Figura 3.8: la ProcessInstanceImp, la ScopeActivity e la ProtectedScope. Ogni istanza di processo infatti
costituirà il contesto base per un’esecuzione e all’interno di esso le varie attività ScopeActivity andranno a realizzare dei sottocontesti. Un discorso a parte invece merita il
tipo ProtectedScope, che permette di realizzare speciali contesti, “protetti”, in cui. come
si è visto, dovranno essere eseguiti i Fault e Compensation Handler. Ancora una volta
84
Blite-se
Figura 3.8: ABaseContext e le sue sottoclassi forniscono diverse implementazioni
dell’interfaccia ExecutionContext
per fattorizzare i comportamenti comuni si è definita una classe astratta ABaseContext
in cui è codificata gran parte della specifica dell’interfaccia ExecutionContext.
In pratica le varie attività che implementano l’Interfaccia ExecutionContext realizzano una sotto-gerarchia all’interno della gerarchia principale delle ActivityComponent. Infatti, a parte la ProcessInstance che costituisce la radice dell’albero, ogni
ExecutionContext avrà un parentContext ed eventualmente un insieme di contesti figli.
package i t . u n i f i . d s i . b l i t e s e . e n g i n e . r u n t i m e ;
...
/∗ ∗
∗ T h i s c l a s s r e p r e s e n t s an a c t i v i t y e x e c u t i o n c o n t e x t .
3.6 Contesto, FaultHandler e Compensazione
85
∗
∗ T h i s c o u l d be t h e P r o c e s s I n s t a n c e i t s e l f b u t a l s o o t h e r
∗ s t r u c t u r e d a c t i v i t i e s l i k e scope a c t i v i t y .
∗
∗ At runtime , E x e c u t i o n C o n t e x t s are i n a t r e e
∗
∗ @author p a n k s
∗/
public i n t e r f a c e ExecutionContext extends VariableScope {
ProcessInstance getProcessInstance ( ) ;
boolean m a t c h C o r r e l a t i o n ( S t r i n g v a r i a b l e , Object value ) ;
ExecutionContext getParentContext ( ) ;
void r e g i s t e r I n n e r C o n t e x t ( ExecutionContext c h i l d ) ;
void n o t i f y F a u l t ( F a u l t f a u l t ) ;
public boolean i s I n A F a u l t e d B r a n c h ( ) ;
void r e g i s t e r F l o w ( FlowExecutor flow ) ;
void resumeWaitingFlows ( ) ;
public ContextState g e t S t a t e ( ) ;
public void s e t S a t e ( C o n t e x t S t a t e s t a t e ) ;
p u b l i c v o i d a d d C o m p l e t e d S c o p e ( AScope s c o p e ) ;
}
Listing 3.6: Interfaccia ExecutionContext.
In Tabella 3.3 analizziamo l’interfaccia ExecutionContext nei suoi metodi principali, mentre di seguito vediamo come questi possono essere utilizzati per realizzare la
semantica specificata.
Come abbiamo già osservato si è scelto un modello di esecuzione Activity Centric,
nel senso che le varie attività saranno totalmente responsabili nel realizzare la loro
esecuzione, ma anche nel collaborare per far sı̀ che i flussi globali dei processi evolvano
secondo quanto specificato dalla semantica. Nell’ambito della terminazione questo
concetto si esemplifica nel fatto che ogni attività (con la sola esclusione delle attività
short-lived) ogni qualvolta venga eseguita, dovrà per prima cosa verificare se si
stia trovando nella discendenza di un contesto fallito (questo potrà essere verificato
invocando il metodo isInAFaultedBranch() del proprio ExecutionContext). In caso
di risposta positiva l’attività dovrà collaborare alla terminazione del proprio flusso di
86
Blite-se
esecuzione, cioè dovrà impostare il proprio parentComponent come attività corrente
del FlowExecutor e concludere immediatamente il metodo doActivity() restituendo il
valore true. In questo modo, con un processo a catena, il flusso corrente raggiungerà il
FlowOwner e potrà cosı̀ terminare.
Di fatto la ricerca di un fault a ritroso nella gerarchia dei contesti può essere semplicemente fatta implementando il metodo isInAFaultedBranch() secondo il seguente
algoritmo ricorsivo10 :
1. Se lo stato corrente del contesto è uguale a FAULTED (cioè al contesto è stato
notificato direttamente un fault) si restituisce true. Altrimenti si procede al passo
successivo.
2. Se il contesto non ha un contesto padre si restituisce false. Altrimenti si procede
al passo successivo.
3. Ricorsivamente si restituisce il valore restituito dal metodo isInAFaultedBranch()
invocato sul contesto padre.
È anche interessante osservare qual è la procedura alla base dell’implementazione
del metodo notifyFault(Fault fault)11 :
1. Si imposta lo stato interno al valore FAULTED.
2. Si invoca sul contesto corrente il metodo resumeWaitingFlows(). Tale metodo ha
un comportamento ricorsivo:
• Su ogni contesto figlio del contesto corrente si invoca ricorsivamente il
metodo resumeWaitingFlows() stesso.
• Si risveglia il ogni flusso di esecuzione registrato nel contesto corrente. Tale
operazione viene fatta in sezione critica sul consueto semaforo disponibile a
livello di definizione di processo:
10
Quella qui presentata è l’implementazione fornita dalla classe ABaseContext ereditata anche per i
contesti ProcessInstanceImp e ScopeActivity. Come si vedrà, il contesto ProtectedScope invece, fornisce
una riscrittura di questa implementazione per garantire la protezione dell’esecuzione.
11
Anche questa implementazione è fornita dalla classe ABaseContext ed è conforme alla semantica dei
contesti ProcessInstanceImp e ScopeActivity. Per il contesto ProtectedScope come si vedrà è necessario
una piccola modifica.
3.6 Contesto, FaultHandler e Compensazione
87
s y n c h r o n i z e d ( manager . g e t D e f i n i t i o n P r o c e s s L e v e l L o c k ( ) ) {
engine . resumeWaitingFlow ( flow ) ;
}
Le due implementazioni saranno valide sia per i contesti di tipo ProcessInsatnceImp che per i contesti definiti dalle ScopeActivity. In più, il metodo doActivity()
di quest’ultima classe realizzerà un algoritmo del tipo seguente:
• Se isInAFaultedBranch() è true:
– Se lo stato corrente è FAULTED, cioè il contesto ha ricevuto direttamente
notifica di un’eccezione, si devono avviare gli Handler:
∗ Si crea una SequenceActivity con la sequenza dei CompensationHandler istallati e con il FaultHandler definito.
∗ Si crea un ProtectedScope in cui si imposta come ContestActivity la
SequenceActivity appena creata.
∗ Si imposta come attività corrente del FlowExecutor il ProtectedScope e
si restituisce il valore true.
– Se lo stato corrente non è FAULTED, cioè un’eccezione è stata notificata in
un qualche contesto padre:
∗ Si imposta lo stato a TERMINATED.
∗ Si crea un ProtectedScope in cui si imposta come ContestActivity la
sequenceActivity dei CompesationHandler installati.
∗ Si imposta come attività corrente sul FlowExecutor la parentComponent
e si restituisce dal metodo con true .
• Altrimenti, cioè se isInAFaultedBranch() è false:
– Se lo stato corrente è STARTED, cioè uguale allo stato iniziale all’attività, il
contesto deve iniziare l’esecuzione, per cui:
∗ Si imposta lo stato a RUNNING.
∗ Si mette come attività corrente del FlowExecutor la ContestActivity e
si ritorna con true.
– Se lo stato corrente è RUNNING allora la ContestActivity ha completato
correttamente la sua esecuzione:
88
Blite-se
∗ Si imposta lo stato a COMPLETED.
∗ Si registra il CompensationHandler nel contesto padre.
∗ Si imposta come attività corrente sul FlowExecutor la parentComponent e si restituisce dal metodo con true.
Concludiamo questa sezione analizzando il contesto ProtectedScope. Come già
detto tale contesto è utilizzato per la messa in esecuzione dei CompesationHandler e
dei FaultHandler di un contesto oggetto di un fault. La semantica di Blite caratterizza
tali contesti, differenziandoli da quelli definiti dalle ScopeActivity, solo per il fatto che
questi sono immuni alla terminazione o, se si vuole parlare in termini della semantica
Blite, la funzione end(·) agisce come la funzione identità per questi contesti:
end( LaM ) = LaM
Questa caratterizzazione è implementata nel nostro modello andando a riscrivere il
metodo isInAFaultedBranch() nella classe ProtectedScope in modo tale da evitare la
chiamata ricorsiva sui contesti padre. Di fatto l’implementazione in questo caso seguirà
il seguente semplice schema:
• Se lo stato corrente del contesto è uguale a FAULTED (cioè sul contesto è stato
notificato direttamente un fault) si restituisce true.
• Altrimenti, si restituisce false.
In questo modo l’esecuzione che avviene in un ExecutionContext di tipo ProtectedScope è protetta dai fallimenti che possono avvenire in altri flussi paralleli.
Un’altra caratteristica della semantica di Blite è che il ProtectedScope protegge l’attività interna dai fallimenti esterni, ma non fa il viceversa, cioè i fallimenti che avvengono all’interno del ProtectedScope si propagano verso il contesto in cui il ProtectedScope
è eseguito. Nel momento in cui il ProtectedScope viene creato, in fase di gestione dell’eccezione, il contesto fallito imposta il proprio contesto padre come contesto padre del
ProtectedScope stesso; a questo punto la “semi trasparenza” definita dalla semantica di
Blite per il ProtectedScope, si realizza nel momento in cui il metodo notifyFault(Fault
fault) viene riscritto secondo la seguente logica:
• Si imposta lo stato a al valore FAULTED,
3.6 Contesto, FaultHandler e Compensazione
89
• Si invoca lo stesso metodo notifyFault(fault) su parentContext
@Override
public void n o t i f y F a u l t ( F a u l t f a u l t ) {
s e t S a t e ( C o n t e x t S t a t e . FAULTED ) ;
getParentContext ( ) . notifyFault ( fault );
}
Si può facilmente capire che con questa implementazione il ProtectedScope è del
tutto trasparente per le eccezioni che avvengono al suo interno, le quali si propagano
direttamente sul contesto padre dove eventualmente possono essere anche gestite.
90
Blite-se
ExecutionContext
ExecutionContext getParentContext()
Restituisce il contesto padre del contesto corrente
void registerInnerContext(
Aggiunge un contesto figlio al contesto corrente
ExecutionContext child)
void notifyFault(Fault fault)
Con questo metodo è possibile sollevare eccezioni
(faults). Le attività che necessiteranno di lanciare
eccezioni (per esempio la ThrowActivty) potranno
invocare tale metodo sul loro ExecutionContext
e terminare il loro metodo doActivity rimettendo in esecuzione il loro parentComponent. L’ExecutionContext potrà aggiornare il proprio stato
per riflettere la situazione di errore. Con questo
metodo ha inizio la propagazione del fault.
boolean isInAFaultedBranch()
Questo metodo sarà utilizzato da ogni attività per
vedere se nella loro gerarchia di contesti ne sia
presente uno su cui sia stata notificata un’eccezione. La gerarchia in questo caso sarà ispezionata dal contesto padre verso i predecessori. In tal
caso, le attività dovranno collaborare per attuare
la terminazione del proprio flusso, cioè dovranno terminare mettendo in esecuzione il proprio
parentComponent.
void registerFlow(FlowExecutor flow)
Poiché il processo di terminazione deve coinvolgere tutti i flussi, anche quelli che eventualmente
sono in attesa di eventi, il contesto, una volta notificato del fault, dovrà risvegliare tutti i flussi creati sotto lui per far sı̀ che questi possano terminare.
Ovviamente, per poter far questo, il contesto dovrà conoscere i flussi che sono stati creati sotto di
lui. Con tale metodo di fatto si realizza proprio
questo, si metterà a conoscenza il contesto sotto
cui si sta operando di ogni nuovo flusso creato.
void addCompletedScope(
Con questo metodo i contesti completati con successo potranno registrare nel proprio contesto padre il loro CompensationHandler per un’eventuale
esecuzione futura.
AScope scope)
Tabella 3.3: Interfaccia ExecutionContext
Capitolo 4
Blide
Questo capitolo presenta Blide (Blite Integrated Development Environment) uno strumento che racchiude un ambiente di esecuzione locale e fornisce un’interfaccia grafica
che permette di svolgere in maniera integrata tutte le operazioni legate allo sviluppo
di programmi Blite. In questo capitolo vengono descritte le caratteristiche e le funzionalità dell’interfaccia e successivamente viene presentato un caso d’uso ispirato ad un
esempio presentato nella specifica di BPEL 1.1 [2].
4.1
Un IDE per Blite
Per verificare se uno strumento è funzionale la cosa migliore da fare è provarlo. In
tal senso per verificare se il linguaggio e il motore di esecuzione realizzati risultino
veramente utilizzabili è stato sviluppato un vero e proprio IDE che permetta di scrivere
rapidamente e testare, tramite l’esecuzione di simulazioni, i programmi Blite.
Questo strumento prevede la possibilità di gestire i file con le definizioni dei processi, editarli, compilarli e metterli in esecuzione. E’ presente anche la funzionalità
per visualizzare, tramite una rappresentazione grafica, l’esecuzione delle istanze di
processo.
Blide è stato realizzato tramite il progetto NetBeans Platform [27], un framework
studiato per facilitare lo sviluppo di applicazioni Java con interfaccia grafica. E’ stato
scelto tale progetto per la sua completezza e per il modello architetturale offerto.
NetBeans Platform permette allo sviluppatore di realizzare le proprie applicazioni
componendo diversi moduli, ciascuno dei quali offre una funzionalità specifica. La modularizzazione molto fine e la gestione formale delle dipendenze permettono di costruire
applicazioni riuscendo a selezionare in modo molto mirato solamente i moduli realmen-
92
Blide
Figura 4.1: Come si presenta Blide al primo avvio.
te necessari e quindi a mantenere limitate le dimensioni complessive dell’applicazione.
Per una presentazione approfondita di NetBeans Platform si consulti [34].
In Figura 4.1 è presentata l’interfaccia di Blide al primo avvio. Oltre alle consuete
barre dei menù e dei pulsanti posizionati in alto, l’applicazione presenta quattro aree
distinte:
1. Un pannello identificato dall’etichetta “Favorites” che permette di accedere al
filesystem e organizzare i file “preferiti” in raggruppamenti logici.
2. Un pannello identificato con l’etichetta “Engines” in cui è possibile accedere ai
motori di esecuzione attualmente disponibili, alle definizioni di processo e alle
istanze create da queste.
4.1 Un IDE per Blite
93
File Blite che deve essere compilato. Il file non è mai stato
compilato o ha subito modifiche dopo l’ultima compilazione.
File Blite che presenta errori di compilazione.
compilazione eseguita sul file ha riportato errori.
L’ultima
File Blite compilato con successo. Delle definizioni presenti
nel file sono disponibili i modelli statici.
Tabella 4.1: Icone associate ai file Blite con estensione .blt
3. Un’area centrale in cui verrano visualizzati gli editor con i file sorgenti e le
rappresentazioni grafiche delle istanze eseguite.
4. Un pannello in basso in cui è possibile visualizzare l’output di sistema, come per
esempio l’esito della compilazione dei programmi Blite.
Di seguito andiamo ad analizzare più nel dettaglio queste aree e altre funzionalità
dell’interfaccia di Blide.
Favorites - Gestione dei Sorgenti Blite
Abbiamo detto che quest’area permette di accedere al filesystem, in realtà non offre
un’unica visione dell’albero come i consueti file manager, ma permette in pratica di visualizzare direttamente più sotto-alberi del filesystem. Di fatto l’utente può selezionare
un direcotry e con la funzione “Add to Favorites. . . ” aggiungere tale directory, con
tutto il suo sotto albero, come una nuova radice che compare direttamente nel pannello.
In questo modo l’utente si può creare una specie di Bookmarks alle directory a cui accede maggiormente e in cui probabilmente ha salvato i file di lavoro. Da notare che tali
configurazioni vengono salvate automaticamente dall’applicazione e al successivo riavvio l’utente si troverà mantenute le scorciatoie alle directory preferite. Al primo avvio
l’applicazione mostrerà come unica risorsa favorita la directory “home” dell’utente.
All’interno degli alberi di Favorites i file con estensione .blt vengono riconosciuti
come file sorgenti Blite e vengono visualizzati con icone speciali. Inoltre l’applicazione
decora tali icone in base al fatto che i file necessitino compilazione, siano stati compilati
94
Blide
Figura 4.2: Una direcory con alcuni sorgenti Blite.
con successo o presentino degli errori che ne abbiano compromesso la compilazione.
In Figura 4.2 è rappresentato il pannello in cui è stato aggiunto nei favoriti la directory
data, questa contiene una serie di file sorgenti Blite che vengono visualizzati con le
opportune icone. In Tabella 4.1 sono rappresentate le varie icone associate ai file Blite
con il riferimento alla loro semantica.
Su ogni nodo rappresentante un file Blite possono essere eseguite diverse azioni. In
particolare tali azioni, come è consuetudine nelle applicazioni con interfaccia grafica,
possono essere eseguite in modi diversi: dai menù a discesa della “Menu Bar”, che
si trova nella parte alta della finestra, dai menù contestuali che si aprono premendo
il tasto destro del mouse su un oggetto selezionato e dai bottoni che si trovano nello
spazio subito sotto la menù bar detto “Tool Bar”. In Figura 4.3 è rappresentato il menù
contestuale che viene visualizzato premendo il tasto destro su un file Blite, che nel caso
specifico deve essere ancora compilato, per cui risulta abilitata l’azione “Compile”,
4.1 Un IDE per Blite
95
Compilazione del codice Blite.
Istallazione dei Deployment contenuti nel file sugli Engine.
Rimozione dei Deployment contenuti nel file dagli Engine.
Tabella 4.2: Azioni che possono essere eseguite su un file Blite.
mentre le azioni “Re/Deploy” e “Undeploy” sono ovviamente disabilitate, in quanto
non è ancora disponibile un modello di definizione da poter istallare. In generale tutta
l’interfaccia è stata realizzata secondo il paradigma Context-Sensitive Interface, per cui
le sue varie parti sono sensibili allo stato degli oggetti che si trovano a gestire.
Oltre che nel menù contestuale che si apre sui file Blite, le operazioni che risultano
più frequentemente eseguite dall’utente, “Save”, “Compile”, “Deploy” e “Undeploy”,
sono state rese disponibili tramiti pulsanti, facilmente raggiungibili, posizionati nella
Tool Bar:
Si osservi come anche questa parte dell’interfaccia risulti sensibile al contesto, cioè
in questo caso al file attualmente selezionato o visualizzato nell’editor, e come siano
attivati solamente i pulsanti, le cui azioni siano attualmente applicabili.
In Tabella 4.2 sono ricapitolate le azioni eseguibili su file Blite con le icone
presentate nell’interfaccia.
96
Blide
Figura 4.3: Facendo click con il tasto destro su un nodo rappresentante un file Blite, si
apre un menù contestuale che visualizza le azioni che l’utente può compiere sul file stesso. Da osservare come siano abilitate solo le azioni applicabili, di fatto viene realizzato
il paradigma Context-Sensitive Interface, secondo cui ogni componente dell’interfaccia
è sensibile allo stato degli oggetti contestuali.
Editing e compilazione del codice Blite
Facendo doppio click, o usando l’azione contestuale “Open”, su un nodo degli alberi
di Favorites rappresentante un file Blite, si apre un pannello di editing nell’opportuna
sezione dell’interfaccia grafica.
Per creare un nuovo file invece si devono eseguire i seguenti passi:
1. Nel pannello Favorites, selezionare la directory in cui si desidera creare il nuovo
file.
2. Premere il tasto destro e visualizzare il menù contestuale.
3. Su di questo posizionarsi sulla voce “New” in modo da visualizzare il sottomenù
associato:
4.1 Un IDE per Blite
97
4. A questo punto le possibilità sono due:
(a) Creare un file vuoto, selezionando la voce di menù “Empty File”. In questo
caso si aprirà una finestra in cui l’utente potrà digitare il nome del nuovo file
comprensivo dell’estensione .blt.
(b) Utilizzare un file di esempio da cui iniziare a sviluppare il proprio programma Blite scegliendo la voce “All Templates . . . ”. In questo caso tramite un
“Wizard” l’utente potrà scegliere fra una serie di file che possono costituire
un punto di partenza (Template) per scrivere il proprio codice.
Abbiamo mostrato come aprire un sorgente Blite all’interno di un editor. Blide
fornisce un editor specializzato per scrivere programmi Blite, che mette a disposizione
le funzionalità specifiche di:
• Syntax Highlight: Tramite la grammatica formale, presentata nel Capitolo 2, è
stato possibile realizzare una tecnologia capace di riconoscere la sintassi Blite
ed evidenziare in maniera opportuna le parole riservate e i costrutti specifici del
linguaggio. Tale tecnologia si basa sul progetto “Generic Languages Framework
(GLF)” [28], che mette a disposizione un modulo per NetBeans Platform con cui
è possibile sviluppare il supporto ad un linguaggio di programmazione all’interno
delle proprie applicazioni.
• Autocompletamento: L’editor dei file Blite assistono l’utente nella scrittura del
codice andando a completare automaticamente alcune parti del codice. In particolare l’editor inserisce automaticamente i delimitatori di chiusura dei blocchi.
Per esempio, all’utente basterà digitare la stringa seq e il sistema inserirà automaticamente la chiusura inserendo il corrispettivo qes. Inoltre anche le operazioni di
invocazione e ricezione sono completate automaticamente, digitando per esempio
98
Blide
semplicemente la stringa rcv l’utente si troverà a disposizione l’intera struttura
dell’operazione di ricezione come: rcv<"on_me"> operation(x), e in questa
potrà modificare le parti necessarie.
Abbiamo visto nel Capitolo 2, come la sintassi originale di Blite sia stata arrichita
con alcune parole riservate e come tutte le attività strutturate siano state dotate di delimitatori di blocco. Con il supporto dell’editor tale appesantimento sintattico non verrà
avvertito dal programmatore.
In Figura 4.4 è raffigurato un editor con il sorgente Blite in cui sono evidenziate
tramite colori e font specifici le parole riservate e i costrutti del linguaggio.
La compilazione del file visualizzato nell’editor attualmente attivo, come detto, può
essere eseguita con l’opportuno pulsante nella Tool Bar. All’utente verrà notificato l’esito della compilazione nell’area in basso dell’interfaccia grafica identificata con l’etichetta “Output”. In caso di errore tale area riporterà il messaggio opportuno e l’utente
potrà direttamente fare click su di esso per posizionare automaticamente il cursore dell’editor in corrispondenza della linea e della colonna in cui è presente l’errore. In Figura
4.5 è rappresentata questa situazione.
Engines - Deploy ed esecuzione dei processi
Blide è dotato di un ambiente Local Environment per l’esecuzione dei processi Blite. Come già detto il Local Environment permette di eseguire localmente (in una Java
Virtual Machine) più Engine e di gestire la comunicazione fra questi simulando la rete.
Quando un file sorgente di Blite è compilato, è abilitata per questo l’azione di “Deploy”; a questo punto se l’utente la esegue i Deployment (si ricorda che nella sintassi i
deployment sono individuati dai delimitatori { . . . }) definiti nel file vengono istallati su
opportuni Engine.
Attualmente, per velocizzare il processo di sviluppo dell’utente, l’associazione
Engine-Deployment è fatta in maniera automatica dal tool secondo il seguente schema:
• Ogni Deployment ha il proprio Engine di esecuzione.
• L’Engine di un Deployment viene identificato nel seguente modo:
(“nome file che definisce il deployment” : “numero d’ordine del dep. nel file”)
Per cui, se abbiamo che un deployment è il terzo definito nel file source.blt
esso verrà istallato in un engine identificato con l’etichetta: source.blt:3.
4.1 Un IDE per Blite
Figura 4.4: L’editor di Blide per con il “Syntax Highlight” per il codice Blite.
Figura 4.5: Feedback interattivo per gli errori di compilazione.
99
100
Blide
Figura 4.6: Rappresentazione del Local Environment con gli Engine, in questi le definizioni di processo istallate. Per ogni definizione sono visualizzate le istanze eseguite o
in esecuzione.
Nell’interfaccia, il pannello identificato con l’etichetta “Engines” visualizza un albero, la cui radice è contrassegnata come “Local Engines”. I nodi figli della radice
rappresentano gli Engine attualmente presenti nel Local Environment identificati con la
convenzione sopra esposta; espandendo i loro figli troviamo le definizioni di processo,
e per ognuna di essa, a livello inferiore troviamo le istanze.
In Figura 4.6 è presentato l’albero dei Local Engines. Come si può osservare fra
le definizioni istallate negli Engine, si può distinguere le cosiddette Ready-to-Run Instances, da queste l’utente può avviare direttamente delle istanze tramite l’operazione
contestuale “Run”, o tramite l’opportuno pulsante posizionato nella Tool Bar.
4.1 Un IDE per Blite
101
Engine.
Definizione di Processo.
Ready-to-Run Instance.
Istanza in esecuzione.
Istanza completata.
Instanza terminata con errori .
Tabella 4.3: I vari tipi di nodi nell’albero Local Engines.
In tabella sono riepilogati (con le rispettive Icone) i vari tipi di nodi che si trovano
nei livelli dell’albero dei Local Engines.
Monitor - Visualizzazione dell’istanze
Abbiamo visto come l’utente possa scrivere i propri programmi Blite, istallarli ed eseguirli all’interno di un Local Environment. L’esecuzione viene avviata facendo partire
una definizione di tipo Ready-to-Run Instance, da questa verrà creata immediatamente
un’istanza che probabilmente, invocando le porte di start activity di altre definizioni di
processo, provocherà la creazione di altre istanze. Per esempio si consideri il seguente
caso:
102
Blide
⇒
in cui si hanno due Engine: faulthandler.blt:1 con la definizione Definition 1
e faulthandler.blt:2 con la Ready-to-Run Instance Definition 1. Quest’ultima
può essere messa in esecuzione direttamente dall’utente facendo click con il tasto destro
e selezionando dal menù contestuale l’azione “Run”. A questo punto come si vede dalla
figura a destra vengono generate due istanze; la Ready to Run Instance ha provocato la
creazione di un’istanza della definizione di processo istallata su faulthandler.blt:1.
Già dalla rappresentazione delle istanze nell’albero Local Engines si ha un’informazione sull’esito della loro esecuzione. Come precedentemente detto le icone riportano
graficamente il fatto che un’istanza abbia concluso con esito positivo o meno la propria
elaborazione.
All’utente viene data inoltre la possibilità di visualizzare il flusso completo di esecuzione di un’istanza. Facendo doppio click sul nodo dell’istanza, si apre, nella zona
dedicata agli editor, un pannello che propone una rappresentazione grafica dell’esecuzione della stessa. Tale pannello viene di seguito identificato con il termine Monitor
View.
Disponendo già di un Monitor View aperto, l’utente vi può aggiungere altre istanze
trascinandocele sopra, secondo il paradigma Drag-and-Drop. In questo modo è possibile visualizzare in un’unica rappresentazione grafica l’interazione di più istanze di
processo che nel loro ciclo di vita si sono scambiate messaggi. In Figura 4.7 è rappresentato il Monitor View con la rappresentazione dell’esecuzione di due istanze generate
dal codice di Figura 4.8. Si noti come la Ready-to-Run Instance tramite la prima invocazione produca la creazione dell’istanza derivante dalla definizione di processo, quest’ultima, con una successiva invocazione, risponde con un messaggio di correlazione.
Si osservi inoltre come l’istanza di destra concluda con successo la sua esecuzione, in
quanto l’eccezione sollevata è gestita in un Fault Handler (è il Fault Handler stesso che
esegue l’invocazione di risposta). Quella di sinistra invece fallisce poiché l’eccezione
sollevata non è gestita da alcun Fault Handler e la stessa produce la terminazione del
ramo parallelo che era fermo in attesa di ricevere un messaggio.
4.1 Un IDE per Blite
103
Figura 4.7: Rappresentazione grafica dell’esecuzione delle istanze e dalla loro
comunicazione.
104
Blide
Figura 4.8: Programma Blite associato alla rappresentazione grafica di Figura 4.7.
A prima vista potrebbe sorprendere la presenza della seconda Throw (racchiusa
dal rettangolo tratteggiato di rosso) nell’istanza fallita, in quanto non sembrerebbe
prevista dal codice. In realtà essa non è altro che l’attività Throw prevista dal Default
4.1 Un IDE per Blite
105
Fault Handler (si veda la Sezione 2.3). C’è sembrato che la scelta migliore per
rendere la rappresentazione grafica più chiara possibile fosse quella di rappresentare
esplicitamente i Default Fault Handler, in quanto condizionano fortemente il flusso
di esecuzione, e di nascondere al contrario i Default Compensation Handler (Empty
Activity), in quanto del tutto ininfluenti.
Concludiamo questa sezione dando una panoramica dei costrutti grafici utilizzati per
rappresentare i flussi di esecuzione:
Le istanze di processo sono rappresentate tramite
scatole (ombreggiate) che contengono le attvità. Le
scatole presentano alle estremità due cerchi, di cui
quello in basso, tramite la sua colorazione indica lo
stato della richiesta: grigio, in esecuzione; verde,
completata con successo; rosso, fallita.
Le attività di base (Basic Activity), sono rappresentate come dei quadrati con all’interno un’opportuna icona, la cui grafica rimanda alla semantica della
attività.
L’attività di sequenza è rappresentata con una freccia
verticale, che unisce dall’alto verso il basso le attività
sequenzializzate. Le attività che cronologicamente
sono eseguite prima si trovano più in alto.
L’attività di composizione parallela è rappresentata
come una barra orizzontale sotto di cui, da sinistra a
destra, vengono rappresentati i flussi di esecuzione.
106
Blide
I contesti sono rappresentati come rettangoli tratteggiati, al cui interno viene svolta la Context Activity.
Se il contesto attiva un Protected Scope, questo viene rappresentato a partire dell’angolo in alto a destra
come un rettangolo con il bordo tratteggiato di colore
rosso.
La scelta esterna è rappresentata tramite la disposizione orizzontale delle attività di ricezione unite dal
simbolo +.
La valutazione della scelta condizionata è rappresentata dall’icona a sinistra. Nel caso in cui la condizione di test è valutata a false, l’icona ha contorno rosso
(come nell’immagine riportata); viceversa, se valuta
a true, ha contorno verde.
Le operazioni eseguite in un ciclo d’iterazione sono
precedute dall’icona rappresentata a sinistra.
4.2
Un esempio d’uso
In questa sezione presentiamo un esempio d’uso degli strumenti realizzati. L’esempio
considerato è stato introdotto nel documento di specifca BPEL 1.1 [3] (sezione 16.1) e
ripreso in [1].
L’esempio, pur risultando essere una semplificazione di uno scenario reale, permette
di sperimentare un buon numero delle caratteristiche del linguaggio, come i partner
link dinamici, i correlation set, i fault handler e i compensation handler.
Si realizza un servizio di spedizioni (Shipping Service) che gestisce gli ordini di
clienti. Gli ordini sono composti da un numero di articoli e il servizio mette a disposizione due tipi di spedizioni: una in cui tutti gli articoli dell’ordine sono inviati insieme,
4.2 Un esempio d’uso
107
e l’altra, in cui è possibile spedire in parti gruppi di articoli, in base alla disponibilità
degli stessi in un ipotetico magazzino.
Il servizio di spedizione da noi realizzato interagisce con altri due servizi di backend, un servizio di gestione del magazzino (Store Service) e un servizio di addebito
dei pagamenti (Billing Service). I due servizi sono realizzati con interfacce stateless, la
cui implementazione è esclusivamente rivolta a fornire un supporto per un’esecuzione
verosimile del processo realizzato dal servizio spedizioni e non vuole essere in nessun
modo una soluzione alle problematiche presenti in scenari reali.
Il servizio spedizioni è realizzato con la definizione di processo Blite di Figura 4.9.
Il Deployment definisce un correlation set con la variabile id che rappresenta un identificativo univoco per gli ordini. Tale identificativo è utilizzato per creare la correlazione
dei messaggi che arrivano dai servizi di backend con l’istanza appropriata del processo spedizioni. Anche i processi client del servizio spedizioni utilizzano tale valore per
correlare le diverse istanze con i messaggi scambiati.
La definizione di processo presenta:
rcv<"ship", cust> req(id, c, items)
come attività di creazione delle istanze, in cui la ricezione è fatta tramite un partner link
bidirezionale, in cui la locazione del richiedente è resa dinamica tramite l’identificativo
cust. I valori ricevuti rappresentano rispettivamente: id, l’identificativo dell’ordine
(già introdotto e utilizzato per correlare la comunicazione); c, un valore booleano che
discrimina fra le due modalità di invio degli articoli; items, un intero che rappresenta
il numero degli articoli nell’ordine.
Se c ha il valore true il processo prova ad inviare tutti gli articoli in una sola
spedizione. Tramite l’invocazione:
inv<"bend", "ship"> packall(id, items)
richiede al servizio di magazzino l’imballaggio di tutti gli articoli e tramite:
rcv<"ship"> packallcb(id, ok)
attende la risposta di questo. In questo caso il partner link è pure bidirezionale, ma le
due parti sono staticamente note: "bend" identifica il servizio di magazzino e "ship"
il servizio spedizione. Il valore di ok discrimina se la spedizione sia possibile o meno.
Se questo ha valore true, il processo sottomette il conto e invia la nota di spedizione
108
Blide
{[
seq
rcv<"ship", cust> req(id, c, items);
if (c)
//invio completo
seq
inv<"bend", "ship"> packall(id, items);
rcv<"ship"> packallcb(id, ok);
if (ok)
seq
inv<"bill"> bill(id, items);
inv<cust> notice(id, items);
qes
inv<cust> err(id, "sorry")
qes
//else − invio in parti
[
seq
shiped := 0;
[ //accredito pagamento
inv<"bill"> bill(id, items)
ch: //compensazione accredito
seq
noshiped := items − shiped;
inv<"bill"> revoke(id, noshiped);
qes
];
while (shiped < items)
seq
inv<"bend", "ship"> pack(id, shiped, items);
rcv<"ship"> packcb(id, count);
if (count > 0)
seq
inv<cust> notice(id, count);
shiped := shiped + count;
qes
//else − articoli non disponibili
throw;
qes
qes
fh:
inv<cust> err(id, "sorry")
]
qes
]}(id)
Figura 4.9: Il codice Blite che realizza lo Shipping Service.
4.2 Un esempio d’uso
109
tramite le invocazioni:
inv<"bill"> bill(id, items);
inv<cust> notice(id, items)
in cui è utilizzato il valore attuale di cust per identificare il client; altrimenti, se il valore
di ok è false, si comunica un errore con:
inv<cust> err(id, "sorry")
Quando c ha il valore false il processo può inviare gli articoli in maniera differita.
Per prima cosa il processo inizializza uno scope (o contesto) in cui viene svolta l’attività
principale ed è definito un fault handler per mezzo del quale, in caso di eccezione, si
comunica l’errore al client.
La politica del servizio è quella di attribuire subito tutto il costo della spedizione, per
cui si esegue il sotto scope seguente, in cui vi è l’interazione con il servizio di backend
per la gestione dei pagamenti:
[
//accredito pagamento
inv<"bill"> bill(id, items)
ch: //compensazione accredito
seq
noshiped := items - shiped;
inv<"bill"> revoke(id, noshiped);
qes
]
Se l’attribuzione del pagamento avviene con successo tramite l’invocazione:
inv<"bill"> bill(id, items)
viene istallato un compensation handler che, tramite la seguente sequenza di azioni,
annulla il pagamento degli articoli che non sono stati effettivamente inviati:
seq
noshiped := items - shiped;
inv<"bill"> revoke(id, noshiped);
qes
110
Blide
Si deve osservare che, nel momento in cui il compensation handler verrà eseguito, la
variabile shiped sarà effettivamente valorizzata al numero degli articoli inviati. Questo
in accordo con la semantica di Blite per cui lo stato della memoria dell’istanza è unico
e condiviso da tutte le attività, comprese quelle eseguite nei vari fault e compesation
handler.
Terminato quest’ultimo scope il processo inizia un’iterazione, in cui ad ogni passo interroga il servizio di magazzino, richiedendo l’invio degli articoli attualmente
disponibili. Le due attività seguenti eseguono in maniera asincrona l’interazione:
inv<"bend", "ship"> pack(id, shiped, items);
rcv<"ship"> packcb(id, count);
La prima invocazione invia i seguenti valori al servizio di backend: id, identificativo
dell’ordine, shiped, numero degli articoli attualmente inviati, items, numero complessivo degli articoli. La successiva ricezione, utilizzando ancora il valore dell’id per
attuare la correlazione, valorizza nella variabile count il numero degli articoli inviati in
questo passo.
Se il valore di count è maggiore di zero, il processo invia al client una nota di
spedizione con tale valore ed aggiorna la variabile shiped con il numero di tutti gli
articoli attualmente inviati. Al contrario, se count ha valore minore o uguale a zero, il
processo solleva un’eccezione e l’esecuzione esce forzatamente dal ciclo iterativo.
L’eccezione viene gestita nello scope che contiene l’iterazione. In questo, come
precedentemente è stato fatto osservare, verrà eseguito il compensation handler del sottoscope completato, revocando il pagamento degli articoli non inviati. Poiché lo scope
che gestisce l’eccezione definisce un faulthandler, questo verrà eseguito subito dopo il
compensation handler, comunicando cosı̀ al client l’errore.
Se le politiche e le disponibilità del magazzino sono tali che, anche in più passi
differiti, il valore di shiped diventi uguale a quello di items, l’iterazione termina e il
processo si conclude avendo eseguito l’invio di tutti gli ordini.
Si deve osservare che il processo di invio articoli termina positivamente anche
quando è sollevata l’eccezione, in quanto questa è gestita internamente.
In Figura 4.10 è presentato il codice Blite in cui è definita la composizione di due
Deployment, ciascuno dei quali definisce una Ready-to-Run-Instance che realizza un
processo client per lo Shipping Service. Il primo dei due richiede l’invio completo
dell’ordine, il secondo l’invio anche differito. Di seguito tali processi verrano identificati
rispettivamente come: All e Dif.
4.2 Un esempio d’uso
111
{ // richiesta invio completo − (All)
:: seq
id := 123;
c := true;
items := 5;
inv<"ship", "cust−all"> req(id, c, items);
pck
rcv<"cust−all"> notice(id, items);
empty;
+
//tale invio non e’ disponibile
rcv<"cust−all"> err(id, err);
exit; //si fallisce l’istanza
kcp
qes
} (id)
||
{ // richiesta invio anche in parti − (Dif)
:: seq
id := 15;
c := false;
items := 20;
inv<"ship", "cust−dif"> req(id, c, items);
shiped := 0;
while (shiped < items)
pck
rcv<"cust−dif"> notice(id, count);
shiped := shiped + count;
+
rcv<"cust−dif"> err(id, err);
exit; //si fallisce l’istanza
kcp
qes
} (id)
Figura 4.10: Il codice Blite che definisce i client del Servizio Spedizioni.
Il codice è abbastanza semplice ed intuibile. In entrambi i casi i processi eseguono l’inizializzazione preliminare delle variabili utilizzate, in particolare: alla variabile
id è assegnato il valore identificativo dell’ordine, tale variabile è anche utilizzata per
realizzare la correlazione, alla variabile c viene assegnato un valore booleano per discriminare la tipologia di invio articoli richiesta, alla variabile items viene assegnato il
numero di articoli che compongono l’ordine.
Fatto questo entrambi i processi eseguono un’invocazione del servizio di spedizioni,
producendone rispettivamente la creazione di un’istanza. L’invocazione comunica an-
112
Blide
che la locazione dei servizi client andando a valorizzare dinamicamente il partner link
con i rispettivi identificativi. Il client All esegue:
inv<"ship", "cust-all"> req(id, c, items)
e il client Dif:
inv<"ship", "cust-dif"> req(id, c, items)
A questo punto i comportamenti dei due client differiscono in base alla tipologia di
invio articoli richiesta. In particolare All espone allo Shipping Service semplicemente
la scelta fra le due operazioni notice ed err, tramite la seguente Pick Activity:
pck
rcv<"cust-all"> notice(id, items);
empty;
+
//tale invio non e’ disponibile
rcv<"cust-all"> err(id, err);
exit; //si fallisce l’istanza
kcp
Se viene attivata la prima opzione l’istanza di processo termina con successo; mentre nel
caso della seconda viene eseguita l’attività exit, che produce la terminazione forzata e
il fallimento dell’istanza.
L’istanza client Diff invece esegue la Pick Activity all’interno del seguente ciclo
while:
while (shiped < items)
pck
rcv<"cust-dif"> notice(id, count);
shiped := shiped + count;
+
rcv<"cust-dif"> err(id, err);
exit; //si fallisce l’istanza
kcp
in cui viene accumulato nella variabile shiped il numero degli articoli attualmente inviati. L’iterazione termina quando tale valore diviene uguale a quello di items e l’i-
4.2 Un esempio d’uso
113
stanza completa positivamente l’esecuzione. Nel caso in cui venga attivata dal servizio spedizioni l’opzione associata all’operazione err, l’attività exit produce l’uscita
immediata dall’iterazione e il fallimento dell’istanza.
Per completezza in Figura 4.11 e in Figura 4.12 vengono riportati anche i programmi Blite che definiscono rispettivamente lo Store Service e il Billing Service.
Facciamo osservare ancora che tale codice ha semplicemente la funzione di definire
delle interfacce verosimili utilizzabili dal servizio spedizioni; l’implementazione invece
è di volta in volta adeguata in base alle esigenze delle simulazioni realizzate.
Di seguito vengono mostrate alcune simulazioni effettuate tramite Blide. In Figura 4.13 è rappresentato lo scenario, in cui un’istanza client richede l’invio di tutti gli
articoli allo Shipping Service. Poichè la disponibiltà di magazzino è tale da soddifare
la richiesta, il servizio di spedizione risponde invocando l’operazione notice del servizio client. Si attua la correlazione del messaggio con l’istanza e questa termina con
successo la propria esecuzione.
In Figura 4.14 ad una richiesta di invio dell’ordine completo, il servizio di spedizione risponde invocando l’operazione err, questo produce la terminazione forzata e il
fallimento dell’istanza client.
In Figura 4.15 è rappresentata la comunicazione fra un client che supporta l’invio
differito degli articoli e il servizio di spedizione. Come si può osservare le esecuzioni
presentano la ripetizione delle attività contenute all’interno del ciclo while. In particolare il servizio spedizioni interagisce due volte con il magazzino e con il client, facendo
sı̀ che quest’ultimo porti a termine positivamente la propria esecuzione.
In Figura 4.16 sono illustrati ancora i processi associati al tentativo di spedizione
differita. In questo caso però al servizio spedizioni, nel secondo ciclo dell’iterazione,
viene comunicata l’indisponibilità degli articoli. Si genera cosı̀ un’eccezione che avvia
il Proteced Scope, in cui viene prima compensata l’attività di accredito pagamenti e poi
comunicato l’errore al client. Quest’ultimo processo, ricevuto tale messaggio di errore,
termina forzatamente la propria esecuzione.
114
Blide
{ // Store Service
[
pck
// invio completo dell’ordine
rcv<"bend", "ship"> packall(id, items);
seq
inv<"ship"> packallcb(id, true);
qes;
+
// invio anche differito dell’ordine
rcv<"bend", "ship"> pack(id, shiped, items);
seq
if (shiped > 0)
inv<"ship"> packcb(id, 0)
seq
packed := items / 2;
inv<"ship"> packcb(id, packed)
qes
qes;
kcp
]
}
Figura 4.11: Il codice Blite che definisce il Servizio di gestione del magazzino.
{ // Billing Service
[
pck
rcv<"bill"> bill(id, items);
empty;
+
rcv<"bill"> revoke(id, items);
empty;
kcp
]
}
Figura 4.12: Il codice Blite che definisce il Servizio di gestione pagamenti.
4.2 Un esempio d’uso
115
Figura 4.13: La spedizione completa dell’ordine è possibile, l’istanza client All termina
con successo la propria esecuzione
116
Blide
Figura 4.14: La spedizione completa dell’ordine non è possibile, l’istanza client All
fallisce.
4.2 Un esempio d’uso
Figura 4.15: Spedizione differita degli articoli di un ordine.
117
118
Blide
Figura 4.16: Il processo di spedizione differita fallisce, il compensation e il fault handler
sono eseguiti in un Proteced Scope (rettangolo tratteggiato di rosso).
Capitolo 5
Conclusioni e sviluppi
5.1
Osservazioni conclusive
Realizzare un’implementazione del linguaggio ci ha permesso di analizzare approfonditamente la semantica formale definita per Blite e di capire quali aspetti di essa
risultassero particolarmente critici in fase di implementazione e, più in generale, ci ha
permesso di fare alcune riflessioni su BPEL e sul significato di alcune sue funzionalità.
Il lavoro svolto in questa tesi non è da considerarsi esaustivo, ma vorrebbe essere
solamente l’inizio di un procedimento iterativo, il cui fine dovrebbe essere quello di
ottenere uno strumento funzionale per l’orchestrazione di servizi, con una semantica
rigorosa da cui sia possibile ricavare implementazioni coerenti.
In particolare abbiamo capito che un linguaggio per l’orchestrazione di servizi, come BPEL, risulta essere uno strumento molto complesso e per questo riuscirne a sintetizzare gli aspetti cruciali in una semantica formale è veramente un’attività delicata.
Contemporaneamente, sviluppare un’ implementazione di tale semantica, in rispetto dei
requisiti che ci possono essere in sistemi di produzione, non è da meno complicato.
Per questo può essere utile che le varie attività trovino sostegno reciproco. Come
l’implementazione è guidata dalla semantica, cosı̀ può essere utile che l’esperienza
ricavata dal processo di sviluppo ritorni nella fase di specifica formale per apportare
eventualmente revisioni e migliorie, e cosı̀ iterativamente fino al raggiungimento di un
accettabile grado di funzionalità.
Un punto molto critico dell’implementazione della semantica formale è stato quello
in cui essa definisce la correlazione dei messaggi con le diverse istanze di processo e
120
Conclusioni e sviluppi
risolve il problema delle multiple start activity. In particolare la semantica di Blite specifica tale comportamento in maniera molto brillante e con un formalismo estremamente
sintetico ed efficace, ma che mal si presta a guidare lo sviluppo del software.
In un’implementazione che usa il multithreading per realizzare il parallelismo, non
ha senso parlare di più istanze di processo che eseguono contemporaneamente la ricezione sulla medesima porta, per cui diventa inutile valutare una priorità nell’attribuzione
dei messaggi alle diverse istanze.
Di fatto un’istanza di processo, nel momento in cui si trova nella condizione di poter
consumare un messaggio, può stabilire se questo è ad essa correlato semplicemente
valutando la funzione booleana corr(c, µ, x, v) introdotta nella Sezione 2.4.
Se dal punto di vista dell’attribuzione dei messaggi alle istanze il problema può considerarsi risolto, rimane il fatto di distinguere se un messaggio in arrivo debba produrre
o meno una nuova istanza. Il ProcessManager deve di fatto prendere questa decisione
al momento della ricezione in base alle informazioni di cui dispone in quel momento.
In generale si potrebbe pensare di risolvere il problema semplicemente a livello
sintattico1 , rendendo distinguibili le porte di ricezione su cui vengono create le istanze
(create port). È ovvio che questa tecnica non permette di realizzare le multiple start
activity, in cui una porta è contemporaneamente di creazione e di possibile correlazione.
Per realizzare le multiple start activity è necessario implementare un meccanismo di
notifica da parte delle start activity di ricezione nei confronti delle istanze di processo
e di quest’ultime nei confronti del ProcessManager. Questo flusso d’informazione e
l’utilizzo opportuno dei metodi per la sincronizzazione dei thread (si veda la Sezione 3.5
e in particolare il codice presentato a pagina 76) permette di realizzare il comportamento
specificato dalla semantica di Blite, in particolare la correlazione e le multiple start
activity.
È chiaro che la semantica attuale di Blite specifica alla perfezione il comportamento
voluto, ma crea un notevole scarto tra la sfera teorica e quella tecnologica che rende
difficile capire quanto l’implementazione realizzi del tutto tale comportamento. Inoltre diversi processi di sviluppo possono attuare strategie di implementazione tra loro
completamente diversi, aumentando di fatto la probabilità di difformità.
In tal senso speriamo che questa esperienza e queste osservazioni aiutino ad affiancare alla semantica attuale una versione, in cui i formalismi e le strategie di specifica
siano più in linea con le necessità di sviluppo che abbiamo incontrato e che le soluzioni
ideate possano in qualche modo essere d’ispirazione.
1
La soluzione può sembrare ingenua, ma per esempio è ciò che di fatto fa Oracle Process Manager.
5.2 Sviluppi futuri
5.2
121
Sviluppi futuri
Per ciò che concerne lo sviluppo software, le attività da svolgere sono essenzialmente
due:
• Sviluppare diversi Environment per l’Engine di Blite per realizzare diverse
strategie di comunicazione.
• Migliorare il formalismo e la tecnologia grafica usata per la rappresentazione delle
istanze in Blide.
Per quanto riguarda il primo punto, come già più volte detto, le possibilità sono
molteplici.
Un primo passo molto semplice potrebbe essere quello di creare un Environment
che supporti la comunicazione remota fra programmi Blite. In questo caso si potrebbe
utilizzare una tecnologia nativa per implementare la comunicazione. Per esempio, utilizzando le socket e la Java Object Serialization si potrebbe di fatto scambiare in rete
gli oggetti attualmente utilizzati dal Local Environment e avere quindi la possibilità di
distribuire i Deployment Blite su diversi nodi di rete. Ovviamente per poter fare questo
bisogna che ai nomi dei servizi sia associato un indirizzo di rete.
Questa associazione può essere fatta direttamente nel file di definizione di Blite, in
pratica la sintassi stessa delle invoke potrebbe esplicitare l’indirizzo dell’host a cui è
diretto il messaggio.
inv <"hostserver.dom/serviceName"> operation (params)
Alternativamente, per non appesantire troppo la struttura del codice (specialmente
nel caso di partner link bidirezionali), potrebbe essere prevista una sezione a parte in cui
si va a legare un nome simbolico utilizzato nelle invoke con un indirizzo fisico.
Sicuramente molto più interessante sarebbe poter far dialogare i nostri programmi
Blite con altri tipi di servizi definiti con linguaggi e tecnologie diverse, primi fra tutti
i Web Service. Per far questo la cosa fondamentale è creare un meccanismo che relazioni il codice Blite alle definizioni WSDL. In pratica serve, da una parte ricavare
una definizione dell’interfaccia del processo Blite e dall’altra associare le operazioni
d’invocazione ad alcune definizioni di servizi preesistenti.
Si potrebbe pensare di realizzare un tool blite2WSDL che, preso in input un file
.blt, esegua le operazioni necessarie ad impostare un legame fra il programma e le
definizioni WSDL.
122
Conclusioni e sviluppi
Il processo di creazione dell’interfaccia può essere fatto, per esempio, combinando un’attività utente con un processo automatico. Tramite un’analisi del codice Blite
si potrebbe pensare di creare uno scheletro per la definizione WSDL, che l’utente può
successivamente raffinare, andando per esempio ad esplicitare: i tipi XSD delle parti dei
messaggi, alcuni riferimenti dei namespace o alcuni dettagli del binding WSDL/SOAP.
In questo modo non ci sarebbe ovviamente nessun controllo statico che i messaggi ricevuti siano usati, all’interno del codice Blite, in maniera conforme con il tipo esplicitato
nel contratto, ma questo concorda con la gestione attuale, in cui a runtime si attua una
conversione implicita dei tipi o, se in ultima analisi nessuna conversione è applicabile,
si genera un errore.
L’associazione delle operazioni di output con le definizioni WSDL dei servizi partner può essere fatta inserendo alcune meta-annotazioni nel codice Blite, associando,
per esempio i nomi dei servizi alla coppia hservice:name/port:namei di una definizione
WSDL che deve essere disponibile a runtime.
Il tool blite2WSDL nella sua esecuzione potrebbe anche fare alcuni controlli sulla
conformità della struttura sintattica fra l’operazione Blite di invocazione e la definizione
WSDL associata.
Una volta che si dispone dei file WSDL e delle associazioni fra questi e le operazioni
di comunicazione di Blite, si può pensare di generare un tool (blite2Java) in grado
di produrre il codice Java, che a runtime andrà ad eseguire l’effettiva integrazione fra il
framework per Web Service utilizzato (ad esempio uno fra JAX-WS [31], Axis2 [32],
CXF [33], ecc) e l’interfaccia EngineChannel. Il nostro tool dovrà essere utilizzato
in sinergia con il tool del framework scelto che produce il codice Java a partire dalle
definizioni WSDL e dovrà attuare un’integrazione di tale codice.
Probabilmente in fase di invocazione è anche possibile evitare la generazione statica del codice e utilizzare le API che framework come JAX-WS o WSIF mettono a
disposizione per l’invocazione dinamica di Web Service.
Un approccio totalmente dinamico in fase di ricezione risulta anche possibile
limitandosi ad una versione specifica del protocollo SOAP.
Probabilmente risulta più facile realizzare un Environment capace di dialogare con
un Enterprise Service Bus basato su JBI [24], in quanto tale standard è stato ideato
appositamente per l’integrazione di Service Engine e quindi esistono, oltre ad una serie
di API appositamente studiate, una buona documentazione e diversi esempi su come
realizzare tale l’integrazione Ad esempio il progetto Open nESB [25] può essere un
valido punto di partenza per iniziare a sviluppare componenti software basati sullo
5.2 Sviluppi futuri
123
standard JBI.
Per quanto riguarda Blide l’attività potrebbe concentrarsi sull’apportare migliorie
al formalismo grafico utilizzato per rappresentare l’esecuzione delle istanze e a potenziarne l’implementazione, nel senso di fornire un maggiore grado di interattività. Si
potrebbe dare all’utente la possibilità di spostare le varie istanze nel Monitor View al
fine di ottenere una rappresentazione ottimale o di posizionare il mouse sulle diverse
attività e ricavare da queste informazioni sullo stato dell’esecuzione.
124
Conclusioni e sviluppi
Appendice
Grammatica EBNF per Blite
CompilationUnit := Deployments <EOF>
Deployments := Deployment ( "||" Deployment )*
Deployment := "{" Service
"}" ( CorrelationSet )?
CorrelationSet := "(" <IDENTIFIER> ( "," <IDENTIFIER> )* ")"
Service := ServiceDef | ( ServiceInstance ( "," Service )? )
ServiceInstance := "::" Activity
ServiceDef := ServiceScope
ServiceScope := "[" StartActivity ( <FH> Activity )? "]"
Scope := "[" Activity ( <FH> Activity )? ( <CH> Activity )? "]"
StartScopeActivity :=
"["
StartActivity
( "fh:" Activity )?
( "ch:" Activity )?
"]"
StartActivity := ReceiveActivity | StartSequenceActivity |
126
Conclusioni e sviluppi
StartScopeActivity | StartFlowActivity |
StartPickActivity
Activity := BasicActivity | SequenceActivity | Scope |
PickActivity | FlowActivity | ConditionalActivity |
IterationActivity
BasicActivity := EmptyActivity | ReceiveActivity | InvokeActivity |
ExitActivity | AssignActivity | ThrowActivity
StartSequenceActivity :=
SequenceActivity :=
StartFlowActivity :=
FlowActivity :=
"seq" Activity
( ";" ( Activity )? )*
"qes"
"flw" StartActivity
( "|" StartActivity )+
"wlf"
"flw" Activity
( "|" Activity )+
"wlf"
StartPickActivity :=
PickActivity :=
"seq" StartActivity
( ";" ( Activity )? )*
"qes"
"pck" ReceiveActivity ";" Activity ";"
( "+" ReceiveActivity ";" Activity ";" )+
"kcp"
"pck" ReceiveActivity ";" Activity ";"
( "+" ReceiveActivity ";" Activity ";" )+
"kcp"
EmptyActivity := "empty"
5.2 Sviluppi futuri
127
ReceiveActivity := "rcv" RecPartners OperationId "(" RecParams ")"
InvokeActivity := "inv" InvPartners OperationId "(" InvParams ")"
ExitActivity := "exit"
ThrowActivity := "throw"
RecPartners := "<" PartnerLitId ( "," PartnerId )? ">"
InvPartners := "<" PartnerId ( "," PartnerLitId )? ">"
PartnerId := PartnerLitId | BoundId
PartnerLitId := <STRING_LITERAL>
OperationId := <IDENTIFIER>
RecParams := BoundId ( "," BoundId )*
InvParams := InvParam ( "," InvParam )*
InvParam := Expression
AssignActivity := BoundId ":=" Expression
VarId := <IDENTIFIER>
BoundId := <IDENTIFIER>
ConditionalActivity := "if" "(" Expression ")" Activity Activity
IterationActivity := "while" "(" Expression ")" Activity
128
Conclusioni e sviluppi
Expression := ConditionalOrExpression
ConditionalOrExpression := ConditionalAndExpression
( "or" ConditionalAndExpression )*
ConditionalAndExpression := EqualityExpression
( "and" EqualityExpression )*
EqualityExpression := RelationalExpression
(
"==" RelationalExpression
| "!=" RelationalExpression
)*
RelationalExpression := AdditiveExpression
(
"<" AdditiveExpression
| ">" AdditiveExpression
| "<=" AdditiveExpression
| ">=" AdditiveExpression
)*
AdditiveExpression := MultiplicativeExpression
(
"+" MultiplicativeExpression
| "-" MultiplicativeExpression
)*
MultiplicativeExpression := UnaryExpression
(
"*" UnaryExpression
| "/" UnaryExpression
)*
UnaryExpression := "!" UnaryExpression | BaseExpression
BaseExpression :=
VarId | Literal | "(" Expression ")"
Literal := <STRING_LITERAL> | <NUMBER_LITERAL> |
BooleanLiteral
5.2 Sviluppi futuri
BooleanLiteral := <TRUELITERAL>
129
| <FALSELITERAL>
/* LITERALS */
< TRUELITERAL: "true" >
< FALSELITERAL: "false" >
< STRING_LITERAL:
"\""
(
(˜["\"","\\","\n","\r"])
| ("\\"
( ["n","t","b","r","f","\\","’","\""]
| ["0"-"7"] ( ["0"-"7"] )?
| ["0"-"3"] ["0"-"7"] ["0"-"7"]
)
)
)*
"\""
>
< NUMBER_LITERAL:
(["0"-"9"])+ ( "." )? (["0"-"9"])*
(<EXPONENT>)? (["f","F","d","D"])?
| "." (["0"-"9"])+ (<EXPONENT>)? (["f","F","d","D"])?
| (["0"-"9"])+ <EXPONENT> (["f","F","d","D"])?
| (["0"-"9"])+ (<EXPONENT>)? ["f","F","d","D"]
>
< #EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ >
/* IDENTIFIERS */
< IDENTIFIER: <LETTER> (<PART_LETTER>)* >
130
< #LETTER:
[
"$",
"A"-"Z",
"_",
"a"-"z"
]>
< #PART_LETTER:
[
"\u0000"-"\u0008",
"\u000e"-"\u001b",
"$",
"0"-"9",
"A"-"Z",
"_",
"a"-"z",
]>
Conclusioni e sviluppi
Riferimenti
[1] Alessandro Lapadula, Rosario Pugliese, Francesco Tiezzi. “A formal account of
WS-BPEL”. Proc. 10th international conference on Coordination Models and Languages (COORDINATION’08). Springer “Lecture Notes in Computer Science”
5052. 2008.
[2] IBM, Bea, Microsoft. “Business Process Execution Language for Web Services,
Version 1.0” - July 31, 2002.
(http://download.boulder.ibm.com/ibmdl/pub/software/dw/specs/ws-bpel/ws-bpel1.pdf)
[3] IBM, Bea, Microsoft, SAP, Siebel System. “Business Process Execution Language for Web Services Version 1.1” - May 5, 2003.
(http://download.boulder.ibm.com/ibmdl/pub/software/dw/specs/ws-bpel/ws-bpel.pdf)
[4] OASIS “Web Services Business Process Execution Language Version 2.0” OASIS
Standard - April 11, 2007.
(http://docs.oasis-open.org/wsbpel/2.0/OS/wsbpel-v2.0-OS.html)
[5] OASIS (Organization for the Advancement of Structured Information Standards).
Oasis Web Site: http://www.oasis-open.org/
[6] IBM. Web Services Flow Language (WSFL). 2001.
(http://xml.coverpages.org/wsfl.html)
[7] Microsoft. XLANG - XML Business Process Language. 2005
(http://xml.coverpages.org/xlang.html)
[8] W3C Recommendation. “Extensible Markup Language (XML) 1.1 (Second Edition)” August 16, 2006.
(http://www.w3.org/TR/2006/REC-xml11-20060816)
132
RIFERIMENTI
[9] W3C Recommendation. “Extensible Markup Language (XML) 1.0 (Fifth Edition)” November 26, 2008.
(http://www.w3.org/TR/REC-xml/)
[10] W3C Recommendation. “XML Schema Part 0: Primer Second Edition” October
28, 2004.
(http://www.w3.org/XML/Schema)
[11] W3C, Official WSDL Web Site. “Services Description Working Group”.
(http://www.w3.org/2002/ws/desc/)
[12] W3C Working Draft. “Web Services Description Language (WSDL) Version 2.0
Part 2: Predefined Extensions” August 3, 2004.
(http://www.w3.org/TR/2004/WD-wsdl20-extensions-20040803/)
[13] W3C Recommendation. “SOAP Version 1.2 Part 1: Messaging Framework (Second Edition)” April 27, 2007.
(http://www.w3.org/TR/2007/REC-soap12-part1-20070427/)
[14] W3C Architecture Domain. “HTTP - Hypertext Transfer Protocol” Specifications,
Drafts, Papers and Reports. 1999-2008
(http://www.w3.org/Protocols/)
[15] Tim Berners-Lee. “HyperText Transfer Protocol Design Issues” CERN Geneva,
1991. (http://www.w3.org/Protocols/DesignIssues.html)
[16] Tim Berners-Lee. “Web Services, Program Integration across Application
and Organization boundaries” W3C Web Services Workshop, 2002-2003.
(http://www.w3.org/DesignIssues/WebServices.html)
[17] S. C. Cheung, Hui Lei, Michael R. Lyu. “Service Oriented Computing
and Applications ” Springer London ISSN 1863-2386, February 22, 2007.
(http://www.springerlink.com/content/1863-2386)
[18] W3C Recommendation. “Web Services Addressing 1.0 - Core” May 9, 2006.
(http://www.w3.org/TR/2006/REC-ws-addr-core-20060509/)
[19] Bruno Giorgio “Linguaggi formali e compilatori” UTET Università, ISBN
8877502045. 1992.
RIFERIMENTI
133
[20] Alfred V. Aho, Ravi Sethi, Jeffrey D. Ullman “Compilers: Principles, Techniques,
and Tools. Second edition.” ISBN 0-321-48681-1. 2006.
[21] Java Official Web Site: http://java.sun.com/
[22] Java Compiler Compiler - The Java Parser Generator.
Web Site: https://javacc.dev.java.net/.
[23] JJTree Reference Documentation.
Web Site: https://javacc.dev.java.net/doc/JJTree.html
[24] Java Comunity Process. “Java Business Integration (JBI) 1.0 - Final Release”
JSR-000208 - August 17, 2005.
(http://jcp.org/aboutJava/communityprocess/final/jsr208/index.html)
[25] OpenEsb - Open Source JBI Enterprise Service Bus.
Web Site: https://open-esb.dev.java.net/
[26] Java Web Start Documentation. Web Site:
http://java.sun.com/javase/6/docs/technotes/guides/javaws/developersguide/contents.html.
[27] NetBeans Platform. Web Site: http://platform.netbeans.org/.
[28] Generic Languages Framework (GLF). Web Site:
http://languages.netbeans.org/.
[29] Gamma Erich, Richard Helm, Ralph Johnson, John Vlissides “Design Patterns:
Elements of Reusable Object-Oriented Software”, Addison-Wesley. ISBN 0-20163361-2.
[30] Lorenzo Bettini, Rocco De Nicola, Daniele Falassi, Michele Loreti “Implementing
a distributed mobile calculus using the IMC framework” ENTCS. Elsevier. 2006.
(http://imc-fi.sourceforge.net/)
[31] Java Comunity Process. “Java API for XML-Based Web Services 2.0” JSR-000224
- October 7, 2005.
(http://jcp.org/aboutJava/communityprocess/pfd/jsr224/index.html)
[32] Apache Axis2 Web Services Engine.
Web Site: http://ws.apache.org/axis2/.
134
RIFERIMENTI
[33] Apache CXF: An Open Source Service Framework.
Web Site: http://cxf.apache.org/.
[34] Geertjan Wielenga, Jaroslav Tulach and Tim Boudreau “Rich Client Programming: Plugging into the NetBeans Platform ” 2007 Sun Press. ISBN
0132354802.
[35] Subversion: An Open Source Version Control System.
Web Site: http://subversion.tigris.org/.
[36] Google Code. Web Site: http://code.google.com/.
[37] GNU General Public License Version 3, June 29, 2007.
(http://www.gnu.org/licenses/gpl-3.0.html)