Pattern per la Service-Oriented Architecture

III parte: Pattern sul design del serviziodi

I pattern sul servizio che verranno descritti in questo articolo sono stati catalogati nelle seguenti sotto-famiglie: pattern di fondamento del servizio; pattern d'implementazione del servizio; pattern sulla sicurezza del servizio; pattern di design del contratto del servizio; pattern sulla governance del servizio. In tal modo è possibile avere una guida ulteriore nella scelta del pattern opportuno nel momento in cui ci si imbatte in una tipologia di problematica tipica nella definizione di un servizio.

Categorie dei pattern sul design del servizio

Le categorie di pattern relative al design del servizio sono le seguenti:

  • pattern di fondamento del servizio;
  • pattern d'implementazione del servizio;
  • pattern sulla sicurezza del servizio;
  • pattern di design del contratto del servizio;
  • pattern sulla governance del servizio.

Pattern di fondamento del servizio

In questa sezione saranno descritti i pattern dedicati all'individuazione di contenuti in supporto alla service-orientation, durante la quale la logica viene scomposta e resa disponibile come servizio.

Functional Decomposition

L'approccio per risolvere molti problemi business è stato sempre quello di definire delle applicazioni. Prima dell'avvento del distribuito, le applicazioni erano realizzate come blocchi monolitici, unità di logica auto-contenuta. Pertanto, risolvere problemi di grandi dimensioni sotto questa ottica andava bene sino a quando i problemi erano auto contenuti, applicabili in ambienti a silos.

Per molte organizzazioni invece, che si pongono delle sfide più importanti, come quella dell'estendibilità e della connettività cross-application, una soluzione silos può diventare troppo espansiva da mantenere e soprattutto da cambiare ed evolvere.

La soluzione proposta sta nella scomposizione funzionale, applicazione della teoria della "separazione dei contenuti". Questa stabilisce la scomposizione di un grande problema in altri molto più piccoli (concerns), per ciascuno dei quali può essere realizzata una soluzione logica. In questo modo ci si predispone a un disegno di possibile soluzione distribuita e ciascuna singola unità logica, che risolve un sotto-problema, potrà essere evoluta e usabile.

Service Encapsulation

Una collezione di programmi software, che rappresentano la soluzione logica di una enterprise, può esistere all'interno dei limiti di un'applicazione silos; infatti, nel passato molti sistemi distribuiti erano realizzati in questa modalità. La successiva decisione di partizionare la soluzione logica in unità più piccole è dovuto e motivato da queste considerazioni:

  • possibilità di incrementare la scalabilità mediante la separazione delle parti del sistema più soggette alla concorrenza o ad alti volumi di traffico;
  • possibilità di migliorare la sicurezza isolando delle parti specifiche del sistema mediante accessi speciali e requisiti di privacy;
  • possibilità di consentire la riusabilità dentro i confini del sistema (o dentro una parte dell'enterprise).

Per cui, quando una enterprise è composta da soluzioni distribuite a silos, si rischia di incorrere in molte sfide di design e governance:

  • quantitativo di sprechi e ridondanze;
  • applicazioni inefficienti;
  • infrastruttura complessa;
  • integrazioni difficili da realizzare e costose;
  • costi operazionali dell'IT sempre crescente.

  

Figura 1 - Enterprise formata da soluzioni distribuite, ma ancora silos.

 

La soluzione sta nel definire delle risorse che possono essere incapsulate ed esposte come servizi. Ciò significa che parte della logica presente già nelle varie soluzioni va a costituire le basi del nuovo servizio o può essere incapsulata in uno già esistente:

 

Figura 2 - Enterprise dove le singole soluzioni usano la logica incapsulata come servizi e viceversa.

 

Agnostic Context

Altro problema è quello che molto spesso logica multi-purpose si trova raggruppata insieme a della logica single-purpose, limitando in questo modo la possibilità di riuso della logica multi-purpose:


Figura 3 - Logica single-purpose e multi-purpose nelle stesse unità logiche.

 

Come illustra la figura 3, si può vedere nella parte superiore dell'immagine che un problema A è composto in concern individuali. Nella parte inferiore invece ci sono le singole unità logiche che risolvono ogni singolo concern; il problema è che le unità 1, 3 e 6 hanno la logica multi-purpose intrappolata in quella single-purpose.

La soluzione consiste nel separare la logica multi-pupose (agnostic) da quella single-purpose (non- agnostic) e definire dei contesti applicativi di logica multi-purpose come servizi, di modo da poter rispondere a più esigenze e riutilizzo:

 

Figura 4 - Contesto di service agnostic.

 

Come illustra la figura 4, la soluzione consiste nel definire delle unità di servizi agnostici, contenente delle logiche muti-purpose e nell'estrapolare la logica single-purpose (non-agnostic) in altri servizi, mentre rimane indifferente quella parte di logica che non era incapsulata in nessuna unità.

Non-Agnostic Context

Quando si applica un approccio service-oriented, c'è una grande enfasi e un forte interesse nel definire e posizionare la logica agnostica (multi-purpose) ai processi business; ciò è alle basi del principio di riusabilità dei servizi e dei pattern ad esso associati.

Il risultato è che la logica non-agnostica (single-purpose) è spesso relegata dentro programmi software che non rientrano nell'inventario dei servizi dell'enterprise ma che sono invece dedicati come consumatori di servizi. In queste situazioni, la service-orientation non viene applicata alle soluzioni non-agnostiche, il che limita il loro potenziale a diventare una effettiva risorsa enterprise, che può compromettere la qualità della logica di composizione dal momento che potrebbe essere responsabile di tale controllo.

La soluzione consiste dunque nell'incapsulare la logica non-agnostica da un servizio che abbia un relativo contesto funzionale non-agnostico, con la differenza però che questo contesto rientra nell'inventario dei servizi. Un altro beneficio sta nel fatto che tale logica può essere disponibile per potenziali coinvolgimenti nelle composizioni dei servizi.

Per cui, la logica dei servizi non-agnostici sarà in questo modo sottoposta agli stessi principi di design applicati ai servizi agnostici, con la differenza della riusabilità del servizio, dal momento che ricopre una funzionalità single-purpose.

Pattern di implementazione del servizio

Questa sezione è dedicata ai pattern applicabili nell'effettiva realizzazione dei servizi, cioè nella fase implementativa.

Service Facade

Uno dei problemi che porta all'utilizzo di questo pattern sta nel fatto che l'accoppiamento tra la logica core del servizio e la rispettiva interfaccia può inibire la sua evoluzione e impattare negativamente sui consumatori del servizio.

Quando un servizio è soggetto a cambiamenti dovuti a richieste di modifiche dell'interfaccia, la logica core si può ritrovare a essere estesa o modificata per rispondere ai cambiamenti. Alcuni esempi possono essere i seguenti:

  • la logica del servizio deve supportare più di un contratto, pertanto introduce nuove logiche decisionali e richiede ai processi business di elaborare vari tipi di messaggi di input e output;
  • l'utilizzo delle risorse condivise, accedute dal servizio, è stato cambiato, e ciò va a incidere negativamente sui consumatori dello stesso servizio;
  • l'implementazione del servizio è stata aggiornata o rifattorizzata, con impatti sulla stessa logica core per ospitare l'implementazione nuova/modificata.

La soluzione al disaccoppiamento tra interfaccia e relativa implementazione è offerta dal pattern Facade, che viene inserito nell'architettura del servizio per stabilire uno o più layer di astrazione, utili per supportare futuri cambiamenti al contratto, alla stessa logica e alla tipologia di implementazione.

Vediamo alcune applicazioni di questo pattern. Un esempio può essere quando l'implementazione della logica core, legata alla versione 1, è soggetta a dei cambiamenti, arrivando alla definizione della versione 2. Dal momento che tali modifiche si traducono anche in cambiamenti nel comportamento, i consumer del servizio risentono di tali modifiche, dal momento che la logica core è accoppiata direttamente al contratto:

 

Figura 5 - Accoppiamento tra contratto e logica core di un servizio.

 

L'introduzione del Service Facade permette di disaccoppiare il contratto del servizio dalla stessa logica core di modo che, se la logica viene aggiornata per cambiamenti all'implementazione, i cambiamenti del comportamento sono catturati dalla facade che contiene delle routine addizionali che preservano il comportamento originale, atteso dai consumer del servizio, anche quando già è in uso la versione 2 del servizio:

Figura 6 - Introduzione della Service Facade.

 

Altro esempio di applicazione di tale pattern è quando viene utilizzato per astrarre l'utilizzo di alcune risorse.

 

Figura 7 - Service Facade.

 

Come mostra la figura 7, un Service Facade astrae l'utilizzo di un database, mentre l'altro astrae un sistema legacy. Tali astrazioni aiutano a proteggere la logica core dai possibili cambiamenti d'implementazione per accesso ad altre risorse di back end.

Service Data Replication

Il problema che qui si mette in evidenza è quello di voler preservare l'autonomia del servizio quando più servizi richiedono l'accesso a delle sorgenti dati condivise. Molti servizi infatti necessitano di interagire con dei database per aggiornare i loro dati business, ma questi repository sono spesso condivisi con altri servizi, incorrendo dunque nel problema di performance o di lock-in delle informazioni, che portano a una inconsistenza delle informazioni utili al servizio che le richiede.

Figura 8 - Replication Data.

 

In queste situazioni, la soluzione consiste nel fornire all'implementazione del servizio dei database dedicati. In questo modo, i servizi possono accedere ai dati centralizzati in maniera autonoma, senza richiedere il possesso esclusivo dei dati:

Partial State Deferral

Quando i servizi sono composti come parte di un' attività, c'è spesso il bisogno che il servizio rimanga attivo e mantenga lo stato, sino a quando l'attività non viene completata. Se al servizio è richiesto di mantenere lo stato, ciò può risultare in una perdita di performance nell'ambiente di riferimento e risulta dispendioso soprattutto quando solo un subset di dati viene richiesto per portare a termine l'attività.

 

Figura 9 - Stato in memoria.

 

Come illustra la figura 9, ci possono essere più istanze del servizio A impegnate nella composizione di vari servizi, in cui lo stesso servizio A viene acceduto in maniera concorrente e ciascuna istanza richiede un certo consumo di memoria per la gestione dei dati di stato.

Per venire incontro a questo, la logica del servizio può essere realizzata per differire un subset delle sue informazioni di stato con la relativa gestione, a un'altra sezione dell'enterprise. Ciò consentirebbe al servizio di rimanere sempre stateful ma con un consumo molto minore delle risorse del sistema. Inoltre, lo stato differito può essere recuperato in qualsiasi momento si reputi necessario.

Partial Validation

I servizi agnostici vengono realizzati con l'obiettivo di poterne attuare il riuso, per cui c'è una certa enfasi nel fornire una serie di funzionalità alle varie tipologie di consumer del servizio e questo può imporre dei requisiti di validazione su alcuni consumer. Un esempio tipico è quando una funzionalità viene realizzata per essere intenzionalmente a "grana grossa" per fornire un ampio set di dati nei suoi messaggi di risposta. In questo caso, quando il consumatore del servizio richiede solo un subset dei dati forniti dal servizio, deve prima validare tutto il set di dati in ingresso prima di poter eliminare quelli non necessari.

La soluzione consiste nel realizzare un consumatore del servizio che intenzionalmente non si attiene al contratto del servizio. Cioè, la sua logica si concentra nel validare solo i messaggi che gli occorrono, ignorando subito quella parte di dati irrilevante, riducendo in questo modo il processo di validazione e i grado di accoppiamento dei consumatori ai rispettivi contratti dei servizi. Tipiche applicazioni della Partial Validation stanno nelle implementazioni dei consumatori del servizio; per esempio, vengono aggiunte delle routine custom per consentire la ricezione e il parsing dei messaggi in arrivo dai servizi, seguendo algoritmi di questo tipo:

  • ricezione del messaggio di risposta dal servizio;
  • identificazione delle parti di messaggio che sono rilevanti per il consumatore;
  • validazione delle parti di messaggio identificate al passo precedente, ed eliminazione della rimanente parte del messaggio;
  • se i dati son validi, vengono mantenuti, altrimenti, il messaggio viene rifiutato.

Pattern sulla sicurezza del servizio

Questa sezione verrà adesso dedicata all'introduzione di quattro pattern che estendono il design del servizio per supportare la sua protezione dalle minacce di sicurezza.

Exception Shielding

Quando si verifica una condizione di errore o eccezione nell'implementazione di un servizio, quest'ultimo generalmente può sollevare un messaggio di risposta per trasmettere l'eccezione generata. Ma il messaggio potrebbe inavvertitamente contenere delle informazioni sensibili che possono essere sfruttate per attaccare il servizio o l'ambiente circostante.

L'applicazione di questo pattern consiste infatti nel catturare il possibile messaggio non sicuro e sottoporlo ad una operazione di "ripulitura".  Come mostra infatti la figura 10, dopo che il database lancia un'eccezione, il messaggio di eccezione non sicuro viene gestito dalla logica core. Quest'ultima identifica il tipo di messaggio e lo rimpiazza con delle informazioni che sono più sicure e non consentono la divulgazione di informazioni sensibili.

Figura 10 - Exception shielding.

 

Dopo che il servizio viene sottoposto a questo processo di "ripulitura", viene restituito un messaggio di errore ai consumatori del servizio che precedentemente ne avevano fatto richiesta.

Message Screening

Uno dei possibili problemi di sicurezza sul servizio può essere quando un messaggio in ingresso al servizio stesso contiene dei dati malformati o malevoli, inseriti accidentalmente da un consumatore del servizio o intenzionalmente da un utente malevolo, che possono alterare il funzionamento del servizio stesso.

Per queste situazioni, vengono aggiunte delle specifiche routine di screening delle minacce all'interno della logica core del servizio. Tali routine rafforzano le politiche che specificano quali parti di un messaggio sono effettivamente richieste dal servizio per poter processare la richiesta. Dal momento che tali politiche di controllo risiedono però nel servizio, non si fa affidamento ai controlli che stanno lato consumatore.

Vediamo alcune possibili applicazioni di tale pattern , o meglio alcune logiche che realizzano le routine indicate:

  • confrontare la dimensione del messaggio di richiesta con la dimensione massima consentita per il messaggio di richiesta;
  • effettuare il parsing del messaggio di richiesta per verificare la presenza di contenuti malevoli (per esempio, per i messaggi dei web services, il contenuto malevolo potrebbe essere piazzato nell'header o nel body del messaggio SOAP).

Per cui, quando si realizzano le logiche di screening delle minacce, devono essere presi in considerazione alcuni aspetti come i seguenti:

  • se il messaggio è criptato, potrebbe risultare impossibile ispezionare i dati per contenuti malevoli sino a quando il messaggio non viene decriptato o sino a quando la logica di screening non ha accesso alla chiave di decriptazione;
  • sono richieste delle logiche di screening custom per la verifica di messaggi binari, come gli attachment, e ciò implica che la logica deve essere capace di riconoscere ciascun tipo di messaggio binario per assicurare che si tratti di un contenuto sicuro o malevolo: questa forma di validazione spesso richiede il coinvolgimento di filtri anti-virus o di meccanismi analoghi:
  • l'utilizzo degli schemi XML può essere rafforzato in supporto a tale pattern, riducendo l'utilizzo di tipi di dati di grana grossa (come gli xsd:string), che sono potenzialmente più propensi ad accettare un range più vasto di dati malevoli.

Trusted Subsystem

Quando le risorse del servizio, come per esempio i database, possono essere acceduti direttamente dai programmi che li consumano, la sicurezza della risorsa può essere compromessa da attacchi malevoli.

La soluzione consiste nell'impostare il servizio come sottosistema di verifica per l'accesso alle proprie risorse. In questo caso, i consumatori possono accedere alle risorse solo mediante il servizio che userà le proprie credenziali per consentire l'accesso invece che quelle del consumatore. Quando si accede ad una risorsa remota, il servizio deve prevedere i seguenti passi quando arriva una richiesta con le credenziali:

  1. autenticare e autorizzare il messaggio mediante Direct Authentication o Brokered Authentication;
  2. inviare una richiesta alla risorsa accompagnata dalle stesse credenziali del servizio (o dall'account del servizio sotto il quale il sottosistema di verifica viene eseguito);
  3. ricevere ed elaborare la risposta dalla risorsa, restituendo questa al consumer.

Per l'effettiva realizzazione di questo pattern, la risorsa deve essere in grado di verificare che il servizio chiamante sia di fiducia e, richiedendo questi tipi di verifica, si migliora la sicurezza rendendo più difficile agli utenti malevoli la simulazione di un sottosistema di verifica o la realizzazione di attacchi del tipo "man-in-the-middle".

Per realizzare tale pattern, vediamo quali sono i possibili approcci o le tecnologie a disposizione.

  • Gli account di servizio sono usati all'interno del sottosistema di verifica: un metodo comune per implementare la verifica con il protocollo kerberos è quello di usare un account che è valido solo all'interno di un dato sottosistema.
  • Ci sono account usati solo su determinati host: quando non è possibile effettuare l'autenticazione usando il servizio di account di kerberos, è possibile creare degli account locali per ciascun host, presenti in un sottosistema di fiducia. Questo tipo di account sono spesso riconosciuti come "account speculari", che richiedono delle password complesse da cambiare frequentemente.
  • X.509 PKI: può emettere un certificato per ciascuna applicazione all'interno del sottosistema. Per accedere alle risorse, il servizio deve usare un certificato X.509 come base per l'autenticazione. In aggiunta, il certificato deve risultare nella lista dei possibili certificati che sono autorizzati ad accedere a quella risorsa.
  • IPSec: assicura i messaggi che viaggiamo tra host a livello di rete per fornire integrità dei dati. Può essere configurato per iniziare una comunicazione sicura col protocollo kerberos, il certificato X.509, o una chiave precondivisa. IPSec non garantisce un controllo ottimale per l'accesso alle risorse ed è per questo motivo che, con un IPSec, un sottosistema di fiducia può essere realizzato solo tra computer che partecipano a un sottosistema e non su specifici programmi che fanno accesso alle risorse.

Service Perimeter Guard

Il problema si pone quando dei consumer richiedono l'accesso a delle risorse che sono deployate in una rete privata. L'accesso diretto alla rete privata potrebbe però esporre i servizi a possibili attacchi malevoli che comprometterebbero lo stesso servizio e la rete.

La soluzione proposta dal pattern è quella di definire un servizio intermediario e di posizionarlo al perimetro della rete privata, di modo che rappresenti l'unico punto di contatto col servizio da parte degli utenti esterni:

Figura 11 - Service Perimeter Guard.

 

Esempi di applicazione di tale pattern è quando per esempio il servizio è deployato in un perimetro di rete (conosciuto meglio come DMZ), che ha accesso alle risorse nella rete privata mediante un firewall. Una richiesta inviata da un utente esterno sarà indirizzata al contratto del servizio presente nel perimetro, e sarà poi quest'ultimo a fare il forwarding del messaggio al servizio interno opportuno. In maniera analoga sarà gestita la fase di risposta.

Pattern di design del contratto del servizio

L'applicazione della Service-Oriented Architecture pone una grande attenzione ed enfasi alla definizione del contratto del servizio; i principi di design infatti richiedono che tutti i contratti, all'interno di un dato inventario, siano conformi alle stesse convenzioni, di modo da stabilire un layer federato.

Decoupled Contract

I servizi possono essere realizzati utilizzando delle tecnologie di sviluppo che siano component-centric, come Java o .NET. Nonostante queste tecnologie forniscano un'adeguata piattaforma per la realizzazione dei componenti come servizi, richiedono che però il contratto del servizio sia legato alla logica core dal punto di vista della tecnologia adoperata. Ciò richiede praticamente che il contratto del servizio sia espresso con la stessa tecnologia che è stata adoperata per lo sviluppo del componente.

Il risultato è che l'utilizzo e l'evoluzione dei servizi è inibita perche' essi possono essere usati solo da consumatori che sono compatibili alla tecnologia. Anche se esistono dei prodotti che consentono l'integrazione, rimane comunque una limitazione, considerando anche gli sforzi per l'integrazione che ogni volta si dovrebbero pagare.

La soluzione prevista è quella di disaccoppiare tecnicamente il contratto del servizio dalla rispettiva implementazione, di modo anche che quest'ultima possa evolvere senza impattare direttamente sui consumatori del servizio. Per applicare questo pattern, la forma più usata per la definizione di un contratto di servizio è quella dei Web Services. La forza di questo linguaggio sta nella possibilità di descrivere il servizio in un linguaggio del tutto indipendente da quella che è l'effettiva implementazione dello stesso.

Contract Centralization

Molto spesso vi sono dei programmi consumatori che sono stati realizzati per accedere direttamente alle risorse del servizio, evidenziando in questo modo una sorta di accoppiamento tra consumatore e implementazione che inibisce lo stesso servizio dall'evolversi in seguito a cambiamenti.

La soluzione è quella di forzare l'accesso alla logica del servizio solo mediante il contratto dello stesso, di modo anche da evitare l'accoppiamento diretto tra consumatore e implementazione del servizio.

Contract Denormalization

I servizi vengono di solito utilizzati in varie composizioni, per cui potrebbe essere difficile esprimere ciascuna operazione offerta da un servizio di modo da essere appropriata e idonea per ogni consumatore. Per esempio, una operazione potrebbe non restituire sufficienti dati in risposta alla richiesta di un consumatore, o in maniera contraria, fornirne troppi e creare un overhead verso il programma consumatore.

La soluzione consiste nel definire un certo livello di de-normalizzazione delle operazioni offerte dal servizio. Per supportare infatti i requisiti di più consumatori del servizio, vengono offerte delle operazioni anche ridondanti ma con un diverso livello di granularità.

Concurrent Contracts

Per default, il servizio ha un contratto che esprime le sue funzionalità, ma ci possono essere dei casi in cui occorre supportare le esigenze specifiche di un certo tipo di consumatori. Per esempio, potrebbe essere necessario dover incorporare nel contratto delle estensioni (come le policy assertions) non supportate da tutti i programmi consumatori ma richieste per alcuni di essi. Pertanto, per supportare diversi tipi di consumatori, possono essere definiti contratti separati anche se l'implementazione risulta essere però la stessa.

Anche se ciò introduce una sorta di ridondanza nella rappresentazione funzionale, consente però ad ogni contratto di essere gestito ed esteso in maniera del tutto indipendente. Permette inoltre la possibilità di esporre solo un subset delle funzionalità a specifici gruppi di utenza che magari devono avere visibilità solo su certe funzionalità e non su tutte.

Pattern sulla governance del servizio

Nonostante gli sforzi fatti in fase di analisi e di modellazione per la realizzazione di un servizio, quest'ultimo potrà essere soggetto a nuovi requisiti e, pertanto, a nuovi cambiamenti che sfidano il campo di applicazione originario del design del servizio. Per questo motivo, ci sono dei pattern che sono emersi per aiutare a evolvere un servizio senza compromettere le sue responsabilità, quando è un membro attivo dell'inventario.

Compatible Changes

Dopo che un servizio è stato deployato, le sue funzionalità sono messe a disposizione come risorse dell'enterprise. I consumatori potranno interagire con esso mediante il suo contratto e di conseguenza, si forma una sorta di legame tra il contratto del servizio e i programmi che lo utilizzano. Se il contratto deve essere sottoposto a dei cambiamenti, questi ultimi rischiano di impattare sui consumatori che sono stati realizzati in accordo al contratto originario.

Per esempio, il nome dell'operazione di un servizio viene modificata dalla versione 1 alla versione 2 del servizio. Dal momento che ci sono dei consumatori A che stanno già facendo uso della versione 1, il risultato è che la versione 2 sarà incompatibile con i consumatori A di quel contratto. La soluzione consiste nel definire dei cambiamenti al contratto del servizio cercando di preservare la retrocompatibiltà con i consumatori esistenti. Questo consentirebbe al servizio di evolvere come richiesto, evitando dunque degli impatti negativi sui programmi che già fanno uso del servizio.

Per risolvere, ad esempio, il problema del renaming dell'operazione del contratto, la soluzione consiste nel non fare il renaming ma nell'aggiungere una nuova operation magari anche analoga a quella già esistente, di modo da preservare la compatibilità coi consumatori A già esistenti e con quelli nuovi che faranno uso della nuova operation.

Version Identification

Quando un contratto è soggetto a delle modifiche, ciascun cambiamento dovrebbe in teoria definire una nuova versione del contratto. Se non si mantiene una sorta di associazione tra la versione del contratto e il cambiamento che l'ha portato a questo, la compatibilità tra il servizio e i rispettivi consumatori è in rischio, anche perch� lo stesso servizio diverrebbe poco conosciuto e dunque utilizzabile anche in fase di design.

Per cui, la soluzione consiste nel realizzare il servizio esprimendo la versione che consente al consumatore di poter stabilire se esso è compatibile col servizio. Per esempio, se il consumatore A nasce compatibile con la versione 3 del servizio i-esimo, anche se ci saranno successive versioni dello stesso servizio, il consumatore A continuerà a poter utilizzare liberamente la versione idonea alle specifiche esigenze.

Termination Notification

Un servizio evolve nel tempo e sono varie le condizioni e le circostanze che possono portare un servizio a essere ritirato, o al ritiro anche solo di parte delle operazioni del suo contratto. Ecco alcuni esempi:

  • il contratto del servizio è soggetto a dei cambiamenti che non sono retro-compatibili;
  • un cambiamento compatibile viene applicato al contratto ma le politiche di versionamento richiedono l'emissione di una nuova versione del contratto;
  • le funzionalità del servizio originale non sono più applicabili in relazione al cambiamento business;
  • un servizio è scomposto in altri servizi più granulosi o combinato insieme ad altri servizi.

Nelle IT enterprise di grandi dimensioni e specialmente quando i servizi sono resi accessibili ad organizzazioni di terze parti, può risultare difficoltoso dover comunicare ai consumatori di un dato servizio che questo dovrà essere ritirato in parte o del tutto. L'invocazione di un servizio che non è possibile identificare e riconoscere come ritirato condurrà a degli scenari di continui fallimenti a runtime:

Figura 12 - Service Termination

 

La soluzione consiste nel corredare il servizio con informazioni di terminazioni, consentendo in questo modo ai consumatori di essere a conoscenza del fatto che il servizio sarà presto ritirato. Il contratto del servizio include uno statement standard che comunica quando sarà schedulato per la terminazione. Come risultato, il consumatore non effettuerà dei tentativi di invocazioni dopo che il contratto sarà in disuso:


           
                       
                                   Mar-01-2009
                       
           


Come mostra la configurazione sopra, questo è un esempio di ritiro di una operazione del contratto del servizio, in cui viene indicata ai vari team di sviluppo la data di "scadenza" dell'operazione.

Service Refactoring

Dopo il primo rilascio di un servizio, potrebbero essere richiesti dei miglioramenti implementativi alla logica del servizio, per incrementare ad esempio le performance, la disponibilità dello stesso servizio. Sostituire per intero il servizio potrebbe non essere favorevole, specialmente se più programmi consumatori hanno già definito le loro dipendenze col contratto.

Il refactoring del servizio può essere affrontato in maniera graduale e soprattutto in maniera trasparente ai consumatori. L'approccio consiste nell'applicare questo pattern che consente di ottimizzare la logica e l'implementazione del servizio, lasciando invariato il contratto del servizio che espone sempre le stesse funzionalità.

Proxy Capability

Il problema che qui si pone è quando un servizio deve essere scomposto nelle sue funzionalità per definire più servizi appartenenti sempre allo stesso inventario. Ciò impatta sui consumatori del servizio che hanno già definito delle dipendenze col contratto iniziale del servizio.

Se si elimina dal contratto iniziale del servizioA una funzionalità che andrà a far parte del contratto del nuovo servizioA1, i consumatori del servizioA subiranno degli impatti negativi. La soluzione consiste nel mantenere sempre lo stesso contratto per il servizioA, aggiungere una service Facade per invocare la funzionalità che è stata spostata per scomposizione nel servizioA1:

Figura 13 - Proxy Capability.

 

Uno degli impatti negativi dell'applicazione di questo pattern sta nel fatto che viene introdotta una sorta di de-normalizzazione del servizio, il che va contro gli obiettivi del pattern Service Normalization.

Per cui, il Proxy Capability deve essere in un certo modo marcato con dei metadata di modo da comunicare che non rappresenterà a lungo l'endpoint ufficiale della rispettiva logica, di modo da evitare che i nuovi consumatori si leghino ad esso piuttosto che al nuovo contratto.

Conclusioni

Con questo terzo articolo della serie si è voluto mettere in luce quanto sia abbastanza complesso definire la realizzazione di ogni singolo servizio di una enterprise. Inoltre, anche una volta definite le interfacce, le implementazioni, le tecnologie, occorre supportare la sicurezza e la governance di ciascuno di essi, aspetti che molto spesso vengono un po' tralasciati o messi in secondo piano. Avere conoscenza di questi pattern o dei più importanti di essi, rispecchianti le problematiche nelle quali è più frequente imbattersi, permette di evitare situazioni di errore di analisi e design che alle lunghe possono portare il sistema a essere poco usabile ed estendibile.

Con il prossimo e ultimo articolo della serie, verranno trattati i pattern utili a supporto della definizione di una composizione di servizi.

Riferimenti

[1] Thomas Erl, "SOA Design Patterns", Prentice Hall. Un lavoro fondamentale sulle tematiche trattate, che si consiglia per ulteriori approfondimenti.

 

[2] SOA Patterns. A community site for SOA design patterns. Un sito di riferimento sulla materia.

http://www.soapatterns.org/

 

Condividi

Pubblicato nel numero
161 aprile 2011
Vittoria Caranna è nata a Rimini nel 1982. Lauretasi in Ingegneria Informatica presso l‘Università degli studi di Bologna nel dicembre del 2007, da gennaio 2008 lavora per il Gruppo Imola. Svolge attività di consulenza, in particolare per quanto riguarda le tematiche architetturali e di processo.
Articoli nella stessa serie
Ti potrebbe interessare anche