Pariiamo ora di sviluppo per componenti.
Partiamo con alcuni design pattern per gestire le dipendenze per poi passare ad un modello per lo sviluppo per componenti, ovvero EJB 3.0, che è uno standard implementato in framework come Glassfish che verrà usato spesso negli esempi presenti nelle slide e nel materiale del corso.
Con sviluppo per componenti si parla di uno sviluppo che ci permette di creare unità, i componenti, di cui si può fare indipendentemente il deployment, senza quindi dipendenze statiche ma con dipendenze che possono essere soddisfatte a runtime nell’ambiente di esecuzione.
Parliamo quindi nel dettaglio dei pattern di gestione delle dipendenze.
Un aspetto chiave è il decoupling, ovvero rendere indipendenti le componenti da deployare anche se a runtime tali componenti devono comunque interagire. Non si parla quindi della rimozione della dipendenza funzionale ma si ha una soddisfazione dinamica (a runtime) delle dipendenze tramite linking di diverse componenti per differenti sistemi, senza che queste interferiscano con il deployment.
Si hanno quindi alcuni design pattern per gestire le dipendenze dinamicamente (su slide anche esempi con diagrammi UML):
inversion of control, dove i campi che memorizzano riferimenti ad altri componenti vengono popolati automaticamente da un componente esterno anche se non viene chiesto di farlo. Questo pattern è anche chiamato dependency injection. Si introduce un elemento esterno chiamato assembler che ha la responsabilità di individuare il componente che deve essere usato e “iniettare” la dipendenza di cui ha bisogno, questo senza che la classe che necessità della dipendenza faccia nulla. L’assembler, se pensiamo all’UML, ad esempio, è collegato all’interfaccia della classe in cui iniettare le dipendenze.
La dipendenza viene iniettata alla creazione dell’oggetto nella maggior parte dei casi concreti (nella maggioranza dei framework che implementano il pattern). Per fare l’iniezione si hanno diverse modalità:
Solitamente comunque l’assembler è già messo a disposizione dei vari framework e può essere configurato eventualmente tramite file di configurazione (per la scelta dell’implementazione) e annotazioni nelle classi che devono subire la injection di dipendenze. In alcuni framework le classi vengono “strumentate” per non dover aggiungere esplicitamente i metodi delle injection (magari usando annotazioni specifiche per dire come valorizzare una variabile tramite dependency injection, tipo @PersistenceContext
). In ogni caso i riferimenti non devono essere ambigui e devono essere ovvi nel contesto. Eventualmente si possono usare informazioni aggiuntive per disambiguare il tutto. L’accesso alla variabile viene fatto dall’assembler, in java, tramite accesso diretto, con la reflection, o “strumentando” la classe aggiungendo metodi di setting. Il framework rende tutto questo “trasparente” nel codice.
service locator, dove la classe che ha bisogno di popolare un campo con un riferimento chiede esplicitamente al service locator il valore del riferimento. Questo pattern è anche chiamato dependency lookup. L’idea è quella di avere un “registro”, il service locator appunto, che conosce qual è l’implementazione che deve essere usata di volta in volta. A questo punto la classe che deve soddisfare la dipendenza fa un accesso al service locator chiedendo che venga ritornato un riferimento al componente che deve essere utilizzato. Si ha l’assembler che popola il service locator per specificare che componenti esistono e che interfaccia implementano queste componenti. Il service locator può essere più o meno complesso a seconda delle informazioni che devono essere aggiunte al suo interno e al tipo di query che devono essere usate per recuperare i vari riferimenti.
Nella sua accezione più semplice il service locator è un singleton che memorizza coppie chiave-valore dove il valore è il riferimento al componente che deve essere usato e la chiave può essere l’interfaccia che il componente implementa. In questo modo nei componenti possiamo chiedere quali altri implementano l’interfaccia con cui bisogna interagire per poi utilzzarli.
Nelle classi, per interrogare il service locator, serve un riferimento al service locator stesso che può essere stabilito tramite dependency injection, si aggiunge quindi un’annotazione e l’assembler, in modo trasparente, imposta un reference al service locator che andremo ad usare direttamente nel codice. Alcuni framework permettono di assegnare nomi ai componenti che scriviamo e poi laddove in un altro componente serva un reference ad un componente già scritto posso usare annotazioni che specificano il nome cossichè il framework recuperi in modo trasparente il riferimento del componente, che nel frattempo viene registrato nel service locator, assegnando quindi il valore della variabile.
Si raggiunge quindi un ottimo livello di astrazione.
Vediamo quindi un modello di sviluppo per componenti Java: EJB 3.0, uno standard implementato in molti framework.
Tra i framework principali che implementano questo standard abbiamo:
Si hanno varie tecnologie come EJB, ogni tecnologia con il suo modello, ciclo di vita dei componenti, servizi. EJB comunque include tanti servizi, fornendo un supporto completo, ma si hanno framework che supportano solo sottoinsiemi degli stessi (tra questi troviamo anche Spring).
In generale all’interno di applicazioni EJB troviamo, lato server-side e component-model: