Разработвайте лесно конфигурируеми софтуерни приложения

Разработването на лесно конфигурируем софтуер е от първостепенно значение в съвременната бизнес среда. Софтуерните приложения вече не се оценяват само по количеството бизнес логика, което те капсулират; те се оценяват и по това колко лесно се поддържат. Възможността за промяна на поведението на софтуера чрез конфигурация формира важен аспект от този цикъл на поддръжка.

Докато езикът Java предоставя редица функции, като файлове с имоти и пакети ресурси, за да подпомогне конфигурирането, те нямат функциите, необходими за днешната динамична бизнес среда. Много стандарти, инструменти и контейнери на Java вече използват по-усъвършенствани и персонализирани XML формати за конфигуриране.

Obix Framework е рамка с отворен код, която предоставя общите средства и формати за съхранение на конфигурационни данни в XML и за достъп до тези данни чрез прости Java обекти. Той дава възможност за модуларизация на конфигурационните данни, като позволява конфигурационните файлове да бъдат импортирани и включени един в друг, както и чрез организиране на конфигурационната информация в „модули“.

В допълнение, той поддържа "горещи" изменения на конфигурацията - чрез автоматично откриване и автоматично презареждане на промените в конфигурационните данни - и също така осигурява поддръжка за Java Naming и Directory Interface API (JNDI). Освен това, той може да се интегрира в Java приложения по много начини, включително чрез Java Management Extensions (JMX) и Java Platform, слушатели на Enterprise Edition, които не изискват кодиране, както и обикновени Java класове, които могат да бъдат извикани директно. И накрая, рамката предоставя лесен за използване API за приставки, който позволява на разработчиците да я разширят, за да изпълняват задачи, свързани с инициализацията. Този API е използван от екипа на Obix за предоставяне на помощни програми за инициализация за други рамки с отворен код като log4j на Apache, Hibernate и Commons DBCP (пулове за свързване на база данни).

В този урок описвам хипотетичен сценарий, който изисква конфигурируем софтуер и за който създаваме скелетни приложения, използвайки Obix. Първият пример предоставя най-близкото нещо до доказателство за концепция в стил "Hello World", докато вторият и третият разширяват това приложение, за да покажат по-малко тривиални аспекти на конфигурацията.

Моля, обърнете внимание, че всички образци на кода в тази статия са опаковани като архив, който може да бъде изтеглен чрез връзката, предоставена в Ресурси.

Проблемен сценарий

Оценяването на финансови активи като акции или опции понякога включва симулиране на цената на актива хиляди пъти и вземане на средната стойност на тези стойности - с убеждението, че средната стойност дава най-доброто предположение за „истинската“ бъдеща стойност на актива. Такива симулации обикновено изискват статистически данни под формата на текущата цена на актива (ите), средната цена за даден период от време, както и отклонението от средната стойност.

Да предположим, че създаваме приложение за оценка на такива инструменти. Като такова приложението ще трябва да изтегли статистическите входове чрез уеб услуга, а подробностите - като URL и информация за удостоверяване - за свързване с тази услуга се съхраняват в конфигурационен документ. Достатъчно е да се каже, че броят на симулациите, които трябва да бъдат извършени за дадена заявка за оценка, също трябва да бъде гъвкав и като такъв ще бъде посочен чрез конфигурация.

Пример 1: Основен конфигурационен файл

В този пример ние създаваме основен конфигурационен файл, example1-config.xml, за нашето приложение, който съдържа подробностите за свързване към уеб услугата, която предоставя статистическите данни за процеса на оценяване. Този конфигурационен файл ще съхранява и броя на симулациите, които трябва да бъдат извършени за всяка заявка за оценка. Този файл (както и конфигурационните файлове за другите примери) е в конфигурационната директория на архива за изтегляне, свързан с този урок. Съдържанието на конфигурационния файл е изброено, както следва:

//www.some-exchange.com/marketdata

trading_app_dbo

nopassword

10000

Ако разгледаме файла по-подробно, забележете, че той започва с коренния възел ; това бележи началото на конфигурационен документ на Obix. Има четири възела, всеки от които капсулира запис за конфигурация. Първите три съдържат URL, потребителски идентификатор и парола за свързване към услугата за въвеждане; последният запис съдържа броя на симулациите, които трябва да се извършат за всяка заявка за оценка. Забележете, че всеки запис има уникален ключ, както е посочено от entryKeyатрибута, и че стойността във всеки запис е капсулирана от възел.

След това създаваме скелета на нашето приложение за оценка и, което е по-важно, демонстрираме как се чете конфигурационният документ по време на изпълнение. Класът на интерес се извиква Example1.javaи може да бъде намерен в папката src на архива за изтегляне, свързан с този урок. Определението на класа е както следва:

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

За да стартирате този и следващите примери, трябва да изтеглите двоичните файлове на Obix Framework на място, достъпно чрез вашия път на класа. Вашият път към класа трябва да се позовава на библиотеката Obix, obix-framework.jar , която може да бъде намерена в папката lib на основната директория на рамката. Ще ви трябват и следните библиотеки с отворен код на трети страни: dom.jar , jaxen -full.jar , sax.jar , saxpath.jar и xercesImpl.jar , които могат да бъдат намерени в папката lib / thirdParty на root root директория.

Изпълнението на този клас трябва да доведе до следния резултат:

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

За да направим дисекция на този клас, започваме с основния метод. Първият ред на този метод създава екземпляр на класа org.obix.configuration.ConfigurationAdapterFactory, който е отговорен за създаването на конфигурационен адаптер (екземпляр на клас org.obix.configuration.ConfigurationAdapter). Адаптерът от своя страна е отговорен за действителното четене на конфигурационен документ от дадено място (посочено като път на файл или URL).

Следният извлечение на код чете съдържанието на нашия конфигурационен файл в глобалния / статичен екземпляр на конфигурация чрез извикване на метода на адаптера adaptConfiguration()и чрез предаване на препратка към глобалния екземпляр - както е получено от обаждането Configuration.getConfiguration()- и пътя към нашия конфигурационен файл config / example1 -config.xml:

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

Note that it is possible to create a new configuration instance to store our configuration data, rather than use the static (global) instance, but for the sake of simplicity (and brevity), we use the static instance for this example.

Next, we briefly examine the method printMarketDataInfo(), which simply reads the configuration entries (i.e., the XML nodes) and prints their values (i.e., their child nodes). Notice that each entry's value is obtained by calling the method getValue (...) on the associated Configuration instance, passing in the name/key of the entry—as specified for the entry node's entryKey attribute. As an aside, note that an entry can have multiple values, which will be demonstrated later in this tutorial.

Example 2: Modularizing configuration data

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]

При подробно разглеждане на допълнителния метод забелязваме, че той първо получава препратка към глобалния Configurationекземпляр; след това се пристъпва към придобиване на препратка към конфигурационния модул, съдържащ информацията за конфигурационната отчетност. Методът постига тези задачи чрез извикване на метода getModule(...)в родителския модул, предавайки идентификатора на модула, който трябва да бъде получен. Обърнете внимание, че този синтаксис е общ в смисъл, че получаването на дъщерния елемент на който и да е модул - дори ако не е основният модул - се постига чрез извикване getModule(...)на дадения модул.