Руководство программиста

Общие рекомендации

При разработке приложения следует иметь ввиду, что элементы любое приложение могут эксплуатироваться как на одном узле, так и на множестве узлов. В каком варианте запускается приложение зависит от конфигурации приложения или узлов приложения. При многоузловой эксплуатации следует учитывать, что данные и программные объекты могут передаваться по сетевому соединению между узлами. Некоторые объекты, зависящие от конфигурации узла на котором они выполняются, могут быть переданы по сети. Например, источник данных (javax.sql.DataSource). Поэтому, при реализации адаптеров (адаптеры обеспечивают прозрачность вызовов локального или удаленного кода) следует обращать внимание на типы данных в параметрах методов и в возвращаемых результатах.

При разработке приложений в архитектуре приложения MVFA, как правило, требуется разрабатывать программные объекты следующих классов:

  1. Классы-функций
  2. Классы-приложений (классы наследуемые от класса ru.funsys.avalanche.Application)
  3. Адаптеры
  4. Классы собственных программных элементов

Разработка класса функции

Класс-функции в архитектуре приложения MVFA - это полностью изолированный класс от других классов приложения или класс, зависящий от других классов приложения, которые реализуют какую то "изолированную" функциональность.

В качестве класса-функции может быть использован любой класс и разработка классов-функций ни чем не отличается от разработки любых других классов в любом другом Java приложении.

Если в классах-функциях нет необходимости при реализации в системе, то они могут отсутствовать.

Классы-функций подключаются в приложении тегом <function> в конфигурационном файле и могут быть опубликованы в коннекторе. Публикация класса-функции позволяет использовать реализованный в функции функционал с других узлов системы. Для доступа к локальным или удаленным функциям используются адаптеры.

Класс-функции не имеет собственного реализованного функционала установки параметров конфигурации и инициализации объектов при старте приложения. Но для них действуют принятые во фреймворке соглашения. В этих классах можно использовать имена зарезервированных атрибутов конфигурации name, description, logger в качестве полей класса. Эти поля будут автоматически установлены в значения из файла конфигурации. При декларации поля logger, это поле будет установлен логгер из файла конфигурации системы ведения журналов log4j по указанному имени, либо в это поле будет заполнен root логгером.

Для инициализации и уничтожения экземпляров классов-функции можно реализовать методы init() и done() соответственно.

Пример:

package ru.mypackage;

import ...;

public class MyFunction {

	@CfgAttribute(description="Имя экземпляра функции")
	private String name;

	@CfgAttribute(description="Логгер вывода событий, если в конфигурации функции атрибут logger не указан, будет использоваться root логгер ")
	private Logger logger;

	@CfgAttribute(description="Собственный атрибут конфигурации класса")
	private int myAttribute;

	public void init() {
		// инициализация объекта
		...
	}
	
	public void done() {
		// уничтожение (освобождение захваченных ресурсов) объекта 
		...
	}
	
	// другие методы класса-функции
	...
	
}

Пример секции <function> для класса MyFunction

<function name=""myFunction" class+"ru.mypackage.MyFunction" logger="MyLogger" myAttribute="12" > 

</function>
Примечание! В конфигурационном файле log4j должен присутствовать логгер c именем MyLogger. Если такого логгера нет, то будет использоваться root логгер.

При необходимости для реализации более сложной логики установки значений из атрибутов файла конфигурации можно реализовывать методы set. Например, для поля myAttribute требуется определить метод setMyAttribute

	public void setMyAttribute(int myAttribute) {
		// реализация логики установки значения поля this.myAttribute 
		...
	}

Разработка класса-приложений

Класс-приложения более высокоуровневый функционал приложения, который может использовать любые классы-функции локального или удаленных узлов системы. Класс-приложения может сам выступать в качестве класса-функции для других классов-приложений, как локальных так и удаленных при установленном атрибуте function в значении true. Таким образом можно строить любую требуемую иерархическую программную модель приложения.

Классы-приложений обязательно должны наследоваться от класса ru.funsys.avalanche.Application напрямую или через другие родительские классы.

Для доступа к классам-функциям в классе-приложения используются адаптеры. Какой именно экземпляр класса-функции будет использоваться (локальный или удаленный) определяется конфигурацией адаптера в конфигурационном файле приложения. Если используется несколько адаптеров одного типа, то имена полей класса должны совпадать с именами адаптеров в файле конфигурации.

Пример

package ru.mypackage;

import ...;
import ru.funsys.avalanche.sql.Adapter;

public class MyApplication extends Application {

	@CfgAttribute(description="Адаптер доступа к БД") 
	private Adapter database;

	// Другие атрибуты конфигурации и поля класса
	...
	
	@Override
	public void init() {
		// реализация инициализации объекта, если в этом есть необходимость
	}
	
	@Override
	public void done() {
		// реализация уничтожения (освобождения захваченныз ресурсов) объекта, если в этом есть необходимость
	}
	 
}

Пример секции конфигурации для локального объекта Database, функция Database и класс-приложения описываются в одном конфигурационном фале. В атрибуте uri указывается имя локальной функции.

	<!-- Сервисные функции работы с БД, источник данных БД определяется в конфигурации           -->
	<!-- контекста WEB приложение (для Tomcat см. вложенный элемент <Resource> секции <context>) -->
	<application class="ru.funsys.avalanche.sql.Database" name="database" description="Функция доступа к БД"
	             resource="java:/comp/env/jdbc/demo" function="true">
	</application>          

	<application class="ru.mypackage.MyApplication" name="myApp" 
	             description="WEB приложение">
	
		<adapter class="ru.funsys.avalanche.sql.Adapter" name="database" uri="database" />
	

	</application>

Пример секции конфигурации для удаленного объекта Database, описывается интерфейс доступа к удаленному узлу и атрибут uri имеет составное имя - имя-локального-интерфейса/имя-удаленного-объекта

	<interface name="http-interface" url="http://ip:port/name-context/avalanche/http-connector" 	/>          

	<application class="ru.mypackage.MyApplication" name="myApp" 
	             description="WEB приложение">
	
		<adapter class="ru.funsys.avalanche.sql.Adapter" name="database" uri="http-interface/database" />
	

	</application>

Где в атрибуте url (для HTTP протокола, для RMI протокола свой формат строки)

  1. ip - IP или DNS имя удаленного узла
  2. port - HTTP порт удаленного узла
  3. name-context - имя контекста (имя приложения) удаленного узла
  4. avalanche - имя сервлета Avalanche (определяется в файле web.xml) на удаленном узле
  5. http-connector - имя коннектора удаленного узла

Пример секции конфигурации объекта Database на удаленном узле, описывается локальный объект на удаленном узле и этот объект публикуется в коннекторе

	<!-- Сервисные функции работы с БД, источник данных БД определяется в конфигурации           -->
	<!-- контекста WEB приложение (для Tomcat см. вложенный элемент <Resource> секции <context>) -->
	<application class="ru.funsys.avalanche.sql.Database" name="database" description="Функция доступа к БД"
	             resource="java:/comp/env/jdbc/demo" function="true">
	</application>          

	<connector class="HttpConnector" name="http-connector">          
		<public name="database" function="database" />
	</connector>

Разработка адаптеров

С точки зрения языка программирования Java адаптер это интерфейс. Он не имеет методов реализации. При создании адаптера указываются только методы, которые будут вызываться с помощью адаптера. В этих методах обязательно должны декларироваться исключения либо java.io.IOException, либо ru.funsys.avalanche.AvalancheRemote

По сути, при разработке адаптера создается внутренняя спецификация, которая повышает модифицируемость приложения на протяжении всего жизненного цикла приложения. Наличие адаптера в будущем при возникновении новых требований позволяет заменить одну реализацию функции на другую, перехватывать вызовы, перемещать функцию между узлами, дублировать функцию на множестве узлов создавая систему.

Пример. Пусть в классе-функции определен метод

package ru.mypackage;

import ...;

public class MyFunction {

	...
	
	public int summa(int x, int y) {
		return X + y;
	}
	
}

Для вызова этого метода с использованием адаптера требуется создать интерфейс

package ru.mypackage;


public interfaces MyAdapter {

	public int summa(int x, int y) throws IOException;
	
}

В классе-приложения создаем поле MyAdapter. Это поле инициализируется значением экземпляра адаптера в момент создания экземпляра класа-приложения при чтении конфигурационного файла.

package ru.mypackage;

import ...;
import ru.funsys.avalanche.sql.Adapter;

public class MyApplication extends Application {

	@CfgAttribute(description="Адаптер доступа к объекту MyFunction") 
	private MyAdapter myAdapter;

	...
	// Метод, использующий вызов класса MeFunction
	public void method1(параметры ...) {
		...
		int summa = myAdapter.summa(10, 17);
		...
	}
	
	...
		 
}

Пример секций конфигурации для локальных объектов MyFunction и MyApplication.

	<function class="ru.mydomain.MyFunction" name="myFunction" description="Функция MyFunction" </function>          

	<application class="ru.mypackage.MyApplication" name="myApp" 
	             description="WEB приложение">
	
		<adapter class="ru.mypackage.MyAdapter" name="myAdapter" uri="myFunction" />
	
	</application>