Développez facilement des applications logicielles configurables

Le développement de logiciels facilement configurables est d'une importance capitale dans l'environnement commercial actuel. Les applications logicielles ne sont plus jugées simplement par la quantité de logique métier qu'elles encapsulent; ils sont également jugés par leur facilité d'entretien. La possibilité de modifier le comportement du logiciel, via la configuration, constitue un aspect important de ce cycle de maintenance.

Alors que le langage Java fournit un certain nombre de fonctionnalités, telles que des fichiers de propriétés et des ensembles de ressources, pour faciliter la configuration, celles-ci n'ont pas les fonctionnalités requises pour les environnements d'entreprise dynamiques d'aujourd'hui. De nombreux standards, outils et conteneurs Java utilisent déjà des formats de configuration XML plus avancés et personnalisés.

Obix Framework est un framework open source qui fournit les moyens et les formats courants pour stocker les données de configuration au format XML et pour accéder à ces données via de simples objets Java. Il permet la modularisation des données de configuration en permettant aux fichiers de configuration d'être importés et inclus les uns dans les autres, et en organisant les informations de configuration en «modules».

En outre, il prend en charge les modifications de configuration «à chaud» - grâce à la détection automatique et au rechargement automatique des modifications apportées aux données de configuration - et prend également en charge l'API Java Naming and Directory Interface (JNDI). En outre, il peut être intégré aux applications Java de nombreuses manières, notamment via les extensions de gestion Java (JMX) et la plate-forme Java, les écouteurs Enterprise Edition qui ne nécessitent pas de codage, ainsi que des classes Java simples qui peuvent être appelées directement. Enfin, le framework fournit une API de plug-in facile à utiliser qui permet aux développeurs de l'étendre pour effectuer des tâches liées à l'initialisation. Cette API a été utilisée par l'équipe Obix pour fournir des utilitaires d'initialisation pour d'autres frameworks open source tels que log4j, Hibernate et Commons DBCP d'Apache (pools de connexion à la base de données).

Dans ce tutoriel, je décris un scénario hypothétique qui nécessite un logiciel configurable et pour lequel nous créons des applications squelettiques à l'aide d'Obix. Le premier exemple fournit ce qui se rapproche le plus d'une preuve de concept de type "Hello World", tandis que les deuxième et troisième étendent cette application pour présenter des aspects moins triviaux de la configuration.

Veuillez noter que tous les exemples de code de cet article sont présentés sous forme d'archive, qui peut être téléchargée via le lien fourni dans les ressources.

Scénario de problème

L'évaluation des actifs financiers tels que les actions ou les options implique parfois de simuler le prix de l'actif des milliers de fois et de prendre la moyenne de ces valeurs, dans la conviction que la moyenne fournit une meilleure estimation de la «vraie» valeur future de l'actif. Ces simulations nécessitent généralement des données statistiques sous la forme du prix actuel du ou des actifs, du prix moyen sur une période donnée, ainsi que de l'écart par rapport à la moyenne.

Supposons que nous créons une application pour évaluer de tels instruments. En tant que telle, cette application devra télécharger les entrées statistiques via un service Web, et les détails - tels que l'URL et les informations d'authentification - pour se connecter à ce service sont stockés dans un document de configuration. Autant dire que le nombre de simulations à effectuer pour une demande de valorisation donnée doit également être flexible et, en tant que tel, sera spécifié via la configuration.

Exemple 1: un fichier de configuration de base

Dans cet exemple, nous créons un fichier de configuration de base, example1-config.xml, pour notre application, qui contient les détails de connexion au service Web qui fournit les entrées statistiques au processus de valorisation. Ce fichier de configuration stockera également le nombre de simulations à effectuer pour toute demande de valorisation. Ce fichier (ainsi que les fichiers de configuration pour les autres exemples) se trouve dans le répertoire config de l'archive téléchargeable associée à ce tutoriel. Le contenu du fichier de configuration est répertorié comme suit:

//www.some-exchange.com/marketdata

trading_app_dbo

nopassword

10000

Si nous examinons le fichier plus en détail, notez qu'il commence par le nœud racine ; ceci marque le début d'un document de configuration Obix. Il existe quatre nœuds, chacun encapsulant une entrée de configuration. Les trois premiers contiennent l'URL, l'ID utilisateur et le mot de passe pour se connecter au service d'entrées; l'entrée finale contient le nombre de simulations à effectuer pour chaque demande de valorisation. Notez que chaque entrée a une clé unique, comme spécifié par l' entryKeyattribut, et que la valeur de chaque entrée est encapsulée par un nœud.

Ensuite, nous créons le squelette de notre application de valorisation et, plus important encore, nous montrons comment le document de configuration est lu lors de l'exécution. La classe d'intérêt est appelée Example1.javaet se trouve dans le dossier src de l'archive téléchargeable associée à ce tutoriel. La définition de classe est la suivante:

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")); } }

Pour exécuter cet exemple et les suivants, vous devez télécharger les binaires Obix Framework dans un emplacement accessible via votre chemin de classe. Votre classpath doit référencer la bibliothèque Obix, obix-framework.jar , qui se trouve dans le dossier lib du répertoire racine du framework. Vous aurez également besoin des bibliothèques open source tierces suivantes: dom.jar , jaxen-full.jar , sax.jar , saxpath.jar et xercesImpl.jar , qui se trouvent dans le dossier lib / thirdParty de la racine du framework annuaire.

L'exécution de cette classe devrait produire le résultat suivant:

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

Pour disséquer cette classe, nous commençons par la méthode principale. La première ligne de cette méthode crée une instance de la classe org.obix.configuration.ConfigurationAdapterFactory, qui est responsable de la création d'un adaptateur de configuration (une instance de classe org.obix.configuration.ConfigurationAdapter). L'adaptateur, à son tour, est responsable de la lecture effective d'un document de configuration à partir d'un emplacement donné (spécifié comme chemin de fichier ou URL).

L'extrait de code suivant lit le contenu de notre fichier de configuration dans l'instance de configuration globale / statique en appelant la méthode adaptateur adaptConfiguration(), et en passant une référence à l'instance globale - telle qu'obtenue à partir de l'appel Configuration.getConfiguration()- et le chemin vers notre fichier de configuration config / exemple1 -config.xml:

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

Notez qu'il est possible de créer une nouvelle instance de configuration pour stocker nos données de configuration, plutôt que d'utiliser l'instance statique (globale), mais par souci de simplicité (et de concision), nous utilisons l'instance statique pour cet exemple.

Ensuite, nous examinons brièvement la méthode printMarketDataInfo(), qui lit simplement les entrées de configuration (c'est-à-dire les nœuds XML) et imprime leurs valeurs (c'est-à-dire leurs nœuds enfants). Notez que la valeur de chaque entrée est obtenue en appelant la méthode getValue (...)sur l' Configurationinstance associée , en passant le nom / la clé de l'entrée, comme spécifié pour l' entryKeyattribut du nœud d'entrée . En passant, notez qu'une entrée peut avoir plusieurs valeurs, ce qui sera démontré plus loin dans ce tutoriel.

Exemple 2: Modularisation des données de configuration

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]

En examinant la méthode supplémentaire en détail, nous remarquons qu'elle obtient d'abord une référence à l' Configurationinstance globale ; puis il procède à l'acquisition d'une référence au module de configuration contenant les informations de configuration de rapport. La méthode réalise ces tâches en invoquant la méthode getModule(...)sur le module parent, en passant l'ID du module à recevoir. Notez que cette syntaxe est générique dans le sens où l'obtention de l'enfant de n'importe quel module - même si ce n'est pas le module racine - est obtenue en invoquant getModule(...)sur le module donné.