Sviluppa facilmente applicazioni software configurabili

Lo sviluppo di software facilmente configurabile è di fondamentale importanza nell'ambiente aziendale di oggi. Le applicazioni software non vengono più giudicate semplicemente dalla quantità di logica aziendale che incapsulano; sono anche giudicati in base alla loro facilità di manutenzione. La capacità di alterare il comportamento del software, tramite la configurazione, costituisce un aspetto importante di questo ciclo di manutenzione.

Sebbene il linguaggio Java fornisca una serie di funzionalità, come file di proprietà e bundle di risorse, per facilitare la configurazione, a questi mancano le funzionalità richieste per gli ambienti aziendali dinamici di oggi. Molti standard, strumenti e contenitori Java utilizzano già formati di configurazione XML più avanzati e personalizzati.

Obix Framework è un framework open source che fornisce i mezzi ei formati comuni per memorizzare i dati di configurazione in XML e per accedere a questi dati tramite semplici oggetti Java. Consente la modularizzazione dei dati di configurazione consentendo l'importazione e l'inclusione dei file di configurazione l'uno nell'altro e l'organizzazione delle informazioni di configurazione in "moduli".

Inoltre, supporta le modifiche alla configurazione "a caldo", tramite rilevamento automatico e ricaricamento automatico delle modifiche ai dati di configurazione, e fornisce anche il supporto per Java Naming and Directory Interface API (JNDI). Inoltre, può essere integrato nelle applicazioni Java in numerosi modi, anche tramite Java Management Extensions (JMX) e Java Platform, listener Enterprise Edition che non richiedono codifica, nonché semplici classi Java che possono essere richiamate direttamente. Infine, il framework fornisce un'API plug-in di facile utilizzo che consente agli sviluppatori di estenderla per eseguire attività relative all'inizializzazione. Questa API è stata utilizzata dal team Obix per fornire utilità di inizializzazione per altri framework open source come log4j di Apache, Hibernate e Commons DBCP (pool di connessione al database).

In questo tutorial, descrivo uno scenario ipotetico che richiede software configurabile e per il quale creiamo applicazioni scheletriche utilizzando Obix. Il primo esempio fornisce la cosa più vicina a una dimostrazione del concetto in stile "Hello World", mentre il secondo e il terzo estendono questa applicazione per mostrare aspetti meno banali della configurazione.

Si noti che tutti gli esempi di codice in questo articolo sono confezionati come un archivio, che può essere scaricato tramite il collegamento fornito in Risorse.

Scenario problematico

La valutazione di attività finanziarie come azioni o opzioni a volte implica la simulazione del prezzo dell'asset migliaia di volte e il calcolo della media di questi valori, nella convinzione che la media fornisca la migliore ipotesi sul valore futuro "reale" dell'asset. Tali simulazioni richiedono tipicamente input statistici sotto forma di prezzo corrente degli asset, il prezzo medio in un dato periodo di tempo e la deviazione dalla media.

Supponiamo di creare un'applicazione per valutare tali strumenti. In quanto tale, questa applicazione dovrà scaricare gli input statistici tramite un servizio Web e i dettagli, come l'URL e le informazioni di autenticazione, per la connessione a questo servizio vengono memorizzati in un documento di configurazione. Basti pensare che anche il numero di simulazioni da eseguire per una data richiesta di valutazione dovrebbe essere flessibile e, come tale, sarà specificato tramite configurazione.

Esempio 1: un file di configurazione di base

In questo esempio, creiamo un file di configurazione di base, example1-config.xml, per la nostra applicazione, che contiene i dettagli per la connessione al servizio Web che fornisce gli input statistici al processo di valutazione. Questo file di configurazione memorizzerà anche il numero di simulazioni da eseguire per qualsiasi richiesta di valutazione. Questo file (così come i file di configurazione per gli altri esempi) si trova nella directory config dell'archivio scaricabile associato a questo tutorial. I contenuti del file di configurazione sono elencati come segue:

//www.some-exchange.com/marketdata

trading_app_dbo

nopassword

10000

Se esaminiamo il file in modo più dettagliato, notiamo che inizia con il nodo radice ; questo segna l'inizio di un documento di configurazione Obix. Ci sono quattro nodi, ognuno dei quali incapsula una voce di configurazione. I primi tre contengono l'URL, l'ID utente e la password per la connessione al servizio di input; la voce finale contiene il numero di simulazioni da eseguire per ciascuna richiesta di valutazione. Si noti che ogni voce ha una chiave univoca, come specificato dall'attributo entryKey, e che il valore in ogni voce è incapsulato da un nodo.

Successivamente, creiamo lo scheletro della nostra applicazione di valutazione e, cosa più importante, dimostriamo come viene letto il documento di configurazione in fase di esecuzione. Si chiama la classe di interesse Example1.javae si trova nella cartella src dell'archivio scaricabile associato a questo tutorial. La definizione della classe è la seguente:

import org.obix.configuration.Configuration; import org.obix.configuration.ConfigurationAdapter; import org.obix.configuration.ConfigurationAdapterFactory;

public class Example1 { public static void main(String[] args) { ConfigurationAdapterFactory adapterFactory = ConfigurationAdapterFactory.newAdapterFactory();

ConfigurationAdapter adapter = adapterFactory.create(null);

adapter.adaptConfiguration(Configuration.getConfiguration(), "config/example1-config.xml"); printMarketDataInfo(); }

private static void printMarketDataInfo() { Configuration globalConfig = Configuration.getConfiguration();

System.out.println("Data Service URL :\t\t" + globalConfig.getValue("market.data.service.url"));

System.out.println("Data Service User-ID :\t\t" + globalConfig.getValue("market.data.service.uid"));

System.out.println("Data Service Password :\t\t" + globalConfig.getValue("market.data.service.password"));

System.out.println("Simulation Count :\t\t" + globalConfig.getValue("number.of.valuation.simulations")); } }

Per eseguire questo e gli esempi successivi, è necessario scaricare i binari di Obix Framework in una posizione accessibile tramite il proprio percorso di classe. Il tuo classpath deve fare riferimento alla libreria Obix, obix-framework.jar , che può essere trovata nella cartella lib della directory principale del framework. Avrai anche bisogno delle seguenti librerie open source di terze parti: dom.jar , jaxen-full.jar , sax.jar , saxpath.jar e xercesImpl.jar , che possono essere trovati nella cartella lib / thirdParty della radice del framework directory.

L'esecuzione di questa classe dovrebbe produrre il seguente risultato:

Data Service URL : //www.some-exchange.com/marketdata Data Service User-ID : trading_app_dbo Data Service Password : nopassword Simulation Count : 10000 

Per sezionare questa classe, iniziamo con il metodo principale. La prima riga di questo metodo crea un'istanza della classe org.obix.configuration.ConfigurationAdapterFactory, che è responsabile della creazione di un adattatore di configurazione (un'istanza della classe org.obix.configuration.ConfigurationAdapter). L'adattatore, a sua volta, è responsabile della lettura effettiva di un documento di configurazione da una data posizione (specificata come percorso di file o URL).

Il seguente estratto di codice legge il contenuto del nostro file di configurazione nell'istanza di configurazione globale / statica invocando il metodo dell'adattatore adaptConfiguration()e passando un riferimento all'istanza globale, come ottenuto dalla chiamata, Configuration.getConfiguration()e il percorso al nostro file di configurazione config / esempio1 -config.xml:

adapter.adaptConfiguration(Configuration.getConfiguration(), "config/example1-config.xml"); 

Si noti che è possibile creare una nuova istanza di configurazione per memorizzare i nostri dati di configurazione, piuttosto che utilizzare l'istanza statica (globale), ma per motivi di semplicità (e brevità), utilizziamo l'istanza statica per questo esempio.

Successivamente, esaminiamo brevemente il metodo printMarketDataInfo(), che legge semplicemente le voci di configurazione (cioè i nodi XML) e stampa i loro valori (cioè i loro nodi figli). Si noti che il valore di ciascuna voce viene ottenuto chiamando il metodo sull'istanza getValue (...)associata Configuration, passando il nome / chiave della voce, come specificato per l' entryKeyattributo del nodo della voce . Per inciso, nota che una voce può avere più valori, che verranno dimostrati più avanti in questo tutorial.

Esempio 2: modulare i dati di configurazione

Applications of this nature will typically generate a report detailing a request's results in some sort of format. Our hypothetical application is no different; it is capable of producing valuation reports in a variety of formats. In addition, the reporting formats used in a given application run are dictated by a configuration entry, and all generated reports are emailed to a list of recipients within our organization—where the recipients are also specified in the configuration set.

Logically, reporting is a distinct piece of functionality—when compared to valuation—even though both are related; so it would be quite reasonable to encapsulate our "reporting" configuration data. This not only provides a cleaner separation of the configuration data, but also makes it simpler for a novice to visualize the delineation of functionality within the application.

We encapsulate the reporting configuration for this example by creating a configuration module for reporting, which is a child of our root module. We modify the configuration file from the last example by appending the node shown below to its list of nodes; the resulting file is called example2-config.xml and can be found in the config directory of the source archive.

.................... .................... ................... [email protected]

spreadsheet text-file pdf

Two things immediately stand out in this configuration file: the first, of course, is our module definition , followed by the module's second entry node . We begin with the module definition. An Obix configuration document can contain any number of submodules. Barring two elements—not discussed in this tutorial—modules support the same node set as the root module. In other words, modules have entries and can contain other modules; hence, modules can effectively be used to replicate a tree structure.

Recall that in the last example, I mentioned that a configuration entry can have multiple values. This functionality is demonstrated by the configuration entry for holding reporting formats, i.e., . As you can see, this differs from other entries in that it has three values—specifying the three formats in which reports should be generated.

We now examine the Java code for reading the entries in our reporting configuration module. We modify the Java source for the previous example by adding the following method; the modified source file (class) is renamed Example2.java, and can be found in the src folder of the archive associated with this tutorial:

private static void printReportingConfig() { Configuration globalConfig = Configuration.getConfiguration();

Configuration reportingConig = globalConfig.getModule("reporting.parameters");

System.out.println("Reports Destination :\t\t" + reportingConig.getValue("reports.destination.email"));

System.out.println("Reporting Formats :\t\t" + reportingConig.getValues("report_formats")); }

On executing this class, it should produce the output:

Data Service URL : //www.some-exchange.com/marketdata Data Service User-ID : trading_app_dbo Data Service Password : nopassword Simulation Count : 10000

Reporting Config Parameters= Reports Destination : [email protected] Reporting Formats : [spreadsheet, text-file, pdf]

Dopo aver esaminato in dettaglio il metodo aggiuntivo, notiamo che ottiene prima un riferimento Configurationall'istanza globale ; quindi procede ad acquisire un riferimento al modulo di configurazione che contiene le informazioni di configurazione del report. Il metodo realizza questi compiti invocando il metodo getModule(...)sul modulo genitore, passando l'ID del modulo da ricevere. Si noti che questa sintassi è generica nel senso che ottenere il figlio di qualsiasi modulo, anche se non il modulo radice, si ottiene invocando getModule(...)il modulo dato.