Слайд 2Задача
Возможность изменять бизнес-логику server-side Java EE приложения «на лету»:
С минимальными обращениями к
![Задача Возможность изменять бизнес-логику server-side Java EE приложения «на лету»: С минимальными](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-1.jpg)
разработчикам системы
Без перекомпиляции
Без shutdown/redeploy системы на сервере
С защитой от синтаксических и семантических ошибок
Слайд 3Применение
Биллинговые системы:
Операторы связи
Такси
Генерация разнообразных отчетов
Пример:
«Клиенту, сделавшему 3 заказа в прошлом месяце и
![Применение Биллинговые системы: Операторы связи Такси Генерация разнообразных отчетов Пример: «Клиенту, сделавшему](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-2.jpg)
с днем рождения на этой неделе, сделать скидку в 10% после 15-й минуты поездки»
Слайд 4Типичные решения
Фиксированные параметры и настройки логики – недостаточно гибко
Скриптинг:
JavaScript (Mozilla Rhino, http://www.mozilla.org/rhino/)
Groovy
![Типичные решения Фиксированные параметры и настройки логики – недостаточно гибко Скриптинг: JavaScript](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-3.jpg)
(http://groovy.codehaus.org/)
Слайд 5Groovy
Dynamic language for the Java Virtual Machine:
Динамическая типизация
Удобный и краткий синтаксис работы
![Groovy Dynamic language for the Java Virtual Machine: Динамическая типизация Удобный и](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-4.jpg)
с коллекциями, картами, массивами, строками
Возможность runtime-компиляции в JVM байт-код и работы с другим Java кодом и библиотеками
Слайд 6Архитектура
Java EE – JBoss Application Server
ORM – EJB JPA Persistence (Stateless &
![Архитектура Java EE – JBoss Application Server ORM – EJB JPA Persistence](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-5.jpg)
Entity Beans)
Service MBeans
HTTP/SOAP Client Connectors
Слайд 7Сервис команд
Service MBean:
Invoker:
Object invoke(String mapping, Object[] args)
Commands:
Object invoke(Object[] args)
![Сервис команд Service MBean: Invoker: Object invoke(String mapping, Object[] args) Commands: Object invoke(Object[] args)](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-6.jpg)
Слайд 8Оформление команд
Команда: Groovy Script (класс)
Runtime компиляция в JVM байт-код, создание объектов и
![Оформление команд Команда: Groovy Script (класс) Runtime компиляция в JVM байт-код, создание](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-7.jpg)
хранение в памяти:
GroovyClassLoader loader = new GroovyClassLoader();
Class groovyClass = loader.parseClass(content);
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
Файлы исходников команд расположены вне EAR/WAR/SAR-архивов
Мониторинг изменений директории исходников через JBoss Deployer для runtime отслеживания изменений
Слайд 9Класс команды
Аннотация на класс – mapping команды:
@ScriptMapping("/createOrder")
Имплементация Java интерфейса:
public interface GenericScript {
![Класс команды Аннотация на класс – mapping команды: @ScriptMapping("/createOrder") Имплементация Java интерфейса:](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-8.jpg)
void init(Object... args);
Object invoke(Object... args);
void onInterrupt(Object... args);
}
Хранение скомпилированных объектов в сервисе в виде ассоциативного массива [Mapping -> Object]
Выполнение прямым вызовом метода invoke без использования Reflections:
GenericScript s = scripts.get(mapping);
s.invoke(args);
Слайд 10Базовый контекст выполнения скрипта
Новое выполнение – новый объект (аналогично HttpServletRequest)
Утилитные методы:
Object getAttribute(String
![Базовый контекст выполнения скрипта Новое выполнение – новый объект (аналогично HttpServletRequest) Утилитные](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-9.jpg)
key);
void setAttribute(String key, Object value);
Object invoke(String mapping, Object[] args);
void log(String message);
Слайд 11Типы команд
Разделение контекстов выполнения команд:
Calculation (базовый): без доступа к Persistence
Read-only: с доступом
![Типы команд Разделение контекстов выполнения команд: Calculation (базовый): без доступа к Persistence](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-10.jpg)
к Persistence на чтение
Read/Write: с доступом к Persistence на чтение/обновление
Слайд 12Организация доступа к данным
EJB JPA Persistence:
Все сущности предметной области – @Entity
Утилитный Stateless
![Организация доступа к данным EJB JPA Persistence: Все сущности предметной области –](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-11.jpg)
Bean:
public interface BaseDAO {
List getAll(Class c);
List getEntitiesByKey(Class c, String key, Object value);
T getEntityById(Class c, Object id);
T createEntity(T entity);
void mergeEntity(Object entity);
void removeEntity(Class c, Object id);
}
Методы Stateless Bean доступны через контекст скрипта
Слайд 13Организация доступа к данным
Имплементация Stateless Bean, примеры:
T createEntity(T entity) {
entityManager.persist(entity);
return entity;
}
![Организация доступа к данным Имплементация Stateless Bean, примеры: T createEntity(T entity) {](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-12.jpg)
List getAll(Class c) {
Query query = entityManager.createQuery("select c from " + c.getName() + " c");
return query.getResultList();
}
Слайд 14Управление транзакцией
Работа с транзакцией в Stateless Bean:
@TransactionManagement(value = TransactionManagementType.CONTAINER)
@TransactionManagement(value = TransactionManagementType.BEAN)
При использовании
![Управление транзакцией Работа с транзакцией в Stateless Bean: @TransactionManagement(value = TransactionManagementType.CONTAINER) @TransactionManagement(value](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-13.jpg)
CMT – аннотации на методах:
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
…
sessionContext.setRollbackOnly();// откат
В обоих случаях, нельзя:
myStatelessBean.startTransaction();
doSomething();
myStatelessBean.commitTransaction();
Слайд 15Управление транзакцией
Решение:
Специальные методы-обертки в Stateless Bean:
//для Read/Write контекста
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Object wrapTransactionRequired(ScriptWrapper sw)
//для Read-Only
![Управление транзакцией Решение: Специальные методы-обертки в Stateless Bean: //для Read/Write контекста @TransactionAttribute(TransactionAttributeType.REQUIRED)](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-14.jpg)
контекста
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public Object wrapTransactionSupports(ScriptWrapper sw)
Вызов метода invoke скрипта-команды и связывание с Stateless Bean – внутри методов wrapTransactionRequired и wrapTransactionSupports
Слайд 16Многопоточное исполнение
Исполнение в очереди - ExecutorService:
singleThreadExecutor: один поток, контроль времени выполнения
multiThreadExecutor: несколько
![Многопоточное исполнение Исполнение в очереди - ExecutorService: singleThreadExecutor: один поток, контроль времени](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-15.jpg)
потоков, контроль времени выполнения
debugThreadExecutor: несколько потоков, без контроля времени выполнения
Определение типа команды и таймаута выполнения в аннотации к классу скрипта:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ScriptMapping {
//…
long runTimeout() default -1;
ScriptThreadingType type() default ScriptThreadingType.MULTI;
}
Слайд 17Контроль времени выполнения
Два вложенных Callable на выполнение команды:
1) Внутренний: запуск скрипта
2) Внешний:
![Контроль времени выполнения Два вложенных Callable на выполнение команды: 1) Внутренний: запуск](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-16.jpg)
контроль времени выполнения через FutureTask.get(timeout)
Внутренний ExecutorService на N+1 поток
Слайд 18Контроль времени выполнения
1) TimeoutException в FutureTask.get(timeout)
2) Вызов метода onInterrupt() у скрипта команды
![Контроль времени выполнения 1) TimeoutException в FutureTask.get(timeout) 2) Вызов метода onInterrupt() у](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-17.jpg)
для предупреждения о завершении
3) sleep(timeout)
4) stop() у потока
5) Запись в журнал ошибок
Слайд 19Асинхронный режим
Вызывающий клиент имплементирует Callback для взаимодействия с командой во время выполнения,
![Асинхронный режим Вызывающий клиент имплементирует Callback для взаимодействия с командой во время выполнения, а получает Future:](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-18.jpg)
а получает Future:
Слайд 20Контроль ошибок
1) Проверка синтаксиса при компиляции:
GroovyClassLoader loader = new GroovyClassLoader();
Class groovyClass =
![Контроль ошибок 1) Проверка синтаксиса при компиляции: GroovyClassLoader loader = new GroovyClassLoader();](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-19.jpg)
loader.parseClass(content);
- throws CompilationFailedException при синтаксическиой ошибке
2) Проверка времени исполнения по таймауту
3) При таймауте скрипта больше K раз – исключение из Invoker
Слайд 21Отладка
Поддержка синтаксиса Groovy в IDE
Удаленная отладка (JPDA) из IDE
Выполнение в отдельном потоке
![Отладка Поддержка синтаксиса Groovy в IDE Удаленная отладка (JPDA) из IDE Выполнение](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-20.jpg)
без контроля таймаута
Слайд 22Интерфейс администрирования
Create, Read, Update, Delete команд
Версионность для отката изменений
Мониторинг:
Количество команд в
![Интерфейс администрирования Create, Read, Update, Delete команд Версионность для отката изменений Мониторинг:](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-21.jpg)
очереди
Exceptions
Отключенные команды
Слайд 23Пример
@ScriptMapping(value = "/SetOrderToBoard", runTimeout = 10000L)
class SetOrderToBoard extends ReadWriteScript {
def invoke(context,
![Пример @ScriptMapping(value = "/SetOrderToBoard", runTimeout = 10000L) class SetOrderToBoard extends ReadWriteScript {](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-22.jpg)
orderUuid, boardUuid) {
def success = false;
def order = context.findByKey("Order", "uuid", orderUuid);
def boards = context.findAll("Board");
for (board in boards) {
if (board.status == "free") {
board.currentOrder = order;
order.board = board;
if (new Date().getTime() - order.creationTime > 10*60* 1000) {
order.discount += 10;
}
context.update(board);
context.update(order);
success = true;
break;
}
}
return success;
}
}
Слайд 24Другие платформы
Эквивалентное выполнение скриптов:
.NET - перенос в контекст отличающихся по синтаксису методов:
sqrt,
![Другие платформы Эквивалентное выполнение скриптов: .NET - перенос в контекст отличающихся по](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-23.jpg)
pow, round, equal, etc
Слайд 25Выводы
Разработанный сервис:
Глубокая настройка бизнес-логики приложения
Понятный юзерам язык и API
Работа с сущностями предметной
![Выводы Разработанный сервис: Глубокая настройка бизнес-логики приложения Понятный юзерам язык и API](/_ipx/f_webp&q_80&fit_contain&s_1440x1080/imagesDir/jpg/461901/slide-24.jpg)
области системы
Защита от ошибок
Возможность расширения на другие платформы