вторник, 29 декабря 2009 г.

Запуск "Наемники : Карты судьбы"

Не так давно наступил день, который я так долго ждал. 25 декабря был запущен проект для ВКонтакте - игра "Наемники : Карты судьбы", над которым я трудился последние 2,5 месяца. Серверная часть проекта была реализована полностью мной, что называется "своими руками". Давненько мечтал о таком маленьком проекте, в котором я смогу применить все свои знания и опыт в области разработки.

Использовались следующие java-технологии:

  • Spring
  • Spring security
  • Spring BlazeDS Integration
  • BlazeDS
  • Hibernate
  • JBoss Cache
  • Unitils
  • Jmockit
  • DBUnit
  • TestNG

Думаю, что некоторые из вас скажут, что с использованием Hibernate хорошей производительности не добьешься, но я с этим не соглашусь. Да, в процессе разработки в Hibernate нашлись некоторые баги и выявилось весьма "необычное" поведение, но все эти проблемы можно решить.

Теперь, когда проект запущен, я могу снова начинать писать статьи, освещая некоторые проблемы с которыми я столкнулся при разработке.

Отмечу также и тот факт, что более 80% кода было покрыто тестами. Хотя этот процесс и занимал у меня до 50% времени, я считаю, что именно благодаря тестам удалось выйти в релиз с минимальным кол-вом багов на серверной стороне. Без тестов был бы невозможен безболезненный рефакторинг и быстрое добавление новой функциональности.

среда, 28 октября 2009 г.

Динамическая модификация контекста в Spring

Существует механизм изменения контекста Spring, который запускается после сбора всей информации о нем (из xml и аннотаций) и срабатывает до создания самих bean-ов. Я говорю об интерфейсе org.springframework.beans.factory.config.BeanFactoryPostProcessor, реализуя который вы получаете именно такую возможность. Используя данный механизм очень удобно менять параметры уже объявленных где-то в контексте bean-ов. Объект реализующий этот интерфейс можно зарегистрировать как обычный bean в контексте, либо добавить его на этапе создания контекста, например так:

Только не забудьте указать в конструкторе контекста аргументу refresh значение false, иначе наш механизм будет вызываться лишь для новых bean-ов добавляемых уж после создания контекста (как это делать я писал тут). При вызове метода refresh произойдет инициализация контекста и наш обработчик будет вызван.

Описанный выше прием оказывается очень полезен в использовании при тестировании, когда нужно изменить какие-то из параметров bean-ов или просто выставить свои значения.

понедельник, 17 августа 2009 г.

Управление версиями в Maven

После очередного релиза проекта приходится менять его версию, проходясь при этом по всем pom-файлам. Процедура эта довольно утомительная и избавить от нее поможет versions-maven-plugin.

Достаточно выставить в головном pom-файле новую версию проекта и затем выполнить команду:

mvn versions:update-child-modules

Как вы наверное догадались, команда выполнит обновление всех версий проекта в pom-файлах дочерних модулей. Если же, по какой-то причине, вы остались недовольны проделанной работой плагина, то содержимое pom-файлов можно легко вернуть в исходное состояние командой:

mvn versions:revert

Чтобы удалить backup-ы pom-файлов выполните:

mvn versions:commit

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

mvn versions:display-dependency-updates

Список новых версий плагинов используемых в проекте:

mvn versions:display-plugin-updates

суббота, 8 августа 2009 г.

Параметры производительности nginx и tapestry 5

Думаю, что вам известно о HTTP-сервере nginx, а также возможности его использования в качестве front-end сервера для передачи разных ресурсов клиенту. Использование nginx позволяет разгрузить ваш сервер Java-приложений от лишней работы со стататическими ресурсами.

Отмечу лишь несколько основных, на мой взгляд параметров, влияющих на производительность системы использующей Nginx и веб-фреймворка Tapestry 5.

Параметры для nginx, выставляемые в nginx.conf:

  • worker_processes - кол-во процессов, обеспечивающих загрузку ресурсов с диска. Значение зависит от объема ресурсов и скорости носителя с которого производится чтение. Также играет роль число ядер/процессоров в вашем сервере, думаю, что значение должно быть не меньше чем их кол-во;

  • worker_connections - максимальное кол-во одновременных соединений с сервером. Следует выбрать оптимальное число, которое сможет держать сам сервер-приложений. Именно верный порог соединений предотвратит DDOS-атаки на ваш сервер и даст уверенность в том, что ваш сервер будет устойчиво работать.

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

  • tapestry.page-pool.hard-limit - максимальное значение страниц в пуле. Следует учесть, количество локалей поддерживаемых вашим приложением, т.к. для каждой локали создается отдельный экземпляр страницы (по-умолчанию значение 20);

  • tapestry.page-pool.soft-limit - кол-во страниц в пуле, после которого tapestry перейдет в ожидание освободившихся страниц. Пока число страниц в пуле не достигнет этого значения и свободных экземпляров не будет, они будут создаваться (по-умолчанию значение 5).

понедельник, 3 августа 2009 г.

Использование LiquiBase с Hibernate и Maven. Часть 2

Теперь о том, что касается изменений которые вносит hibernate в структуру БД, при добавлении/изменении сущностей, если в настройках указан параметр hibernate.hbm2ddl.auto=update. Как продолжить пользоваться этой возможностью, если теперь для всех изменений структуры БД мы должны использовать только liquibase? Было бы не плохо получать sql генерируемый hibernate, который можно легко поместить в changeset liquibase. Для этого необходимо подключить hibernate3-maven-plugin к модулю, в котором используется hibernate:

Необоходимым условием для работы плагина является наличие файла hibernate.hbm.xml, в нем должны содержаться следующие настройки:

Не забудьте также и о том, что теперь все маппинги классов тоже нужно указывать в этом файле, а не в каком-нибудь SessionFactoryBean, если используете Spring. Настройка SessionFactoryBean будет выглядеть примерно так:

Стоит отметить, что параметру hibernate.hbm2ddl.auto, в вашем проекте, нужно присвоить значение validate. Теперь при каждом выполнении команды:

mvn hibernate3:hbm2ddl -Dhibernate.create=true

вам будет выводится sql генерируемый hibernate, который можно вставить в changeset. Параметр -Dhibernate.create=true следует указывать, в том случае, когда вам требуется SQL всей схемы БД. В случае, когда требуется получить SQL-команды, выполняемые hibernate для обновления схемы БД, то вместо первого параметра используйте -Dhibernate.update=true.

И помните, что в случае обновления БД вам нужно будет следить SQL, генерируемым hibernate, т.к. не все изменения параметров объектов входят в SQL (к примеру, hibernate не будет создавать некоторые constraint-ы).

четверг, 9 июля 2009 г.

Использование LiquiBase с Hibernate и Maven. Часть 1

Когда-то я представлял вашему вниманию инструмент для управления изменениями в БД - liquibase. Теперь я решил описать как его можно использовать в проектах с maven-сборкой, и hibernate.

Для начала необходимо создать отдельный maven-артефакт, который будет содержать только ресурсы - xml-файлы с changeSet-ами от liquibase. Структура файлов в папке resources нашего артефакта следующая:

    /changelogs/версия_проекта/*-changelog.xml
/changelogs/версия_проекта/release-changelog.xml
/project-changelogs.xml

Файл project-changesets.xml включает в себя набор файлов release-changelog.xml всех версий проекта, вот пример его содержимого:

Таким же образом файлы release-changelog.xml включают в себя список файлов с измененями, находящихся в той же папке и касающихся лишь данной версии проекта. Сами изменения я предпочитаю описывать не пользуясь при это тэгами liquibase, а использую привычный SQL.

Благодаря наличию в liquibase такого понятия как "контекст", можно разделить все операции над БД на три контекста:

  • schemedata - контекст в котором будут отражены изменения структруры базы;

  • initdata - контекст определяющий данные для инициализации БД, например добавление новых пользовательских ролей;

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

Такая концепция позволит вам четко разделять виды изменений в БД. А также "накатывать" изменения лишь нужных контекстов. Пример:

Если в проекте предполагается использовать две базы - БД разработчика/продакшн и БД для тестов, то pom-файл, созданного нами артефакта, будет содержать следующие строки:

Параметры app.database.xxx и test.database.xxx можно определить в головном pom-файле проекта. Теперь накатывать изменения на обе БД можно одной командой:

mvn process-resources либо mvn clean install

Чтобы сделать rollback обеих баз на несколько changeset-ов назад к команде следует добавить параметры:

-Dliquibase.goal=rollback -Dliquibase.rollbackCount=1

liquibase.rollbackCount - определят кол-во changeset-ов которые следует откатить.

Порой может возникнуть ситуация, когда требуется внести изменение в существующий changeset и заново накатить все changeset-и. Здесь поможет параметр:

-Dliquibase.dropFirst=true

Если потребуется работать с каждой БД отдельно, то необходимо использовать уже команды самого плагина liquibase:

mvn liquibase:update либо mvn liquibase:rollback

Для тестовой БД добавляем параметр -Ptestdb в командной строке.

суббота, 2 мая 2009 г.

Javascript AOP

Наверное каждому известно что такое АОП или по-просту аспектно-ориентированное программирование. В java есть целый ряд фреймворков реализующих определенные возможности данного подхода.

Но если смотреть в сторону javascript-языка, то возможности АОП в нем реализовать довольно просто:

var AOPUtils = {
addBefore: function(object, methodName, invokedObject, invokedMethodName) {
var oldMethod = object[methodName];
object[methodName] = function() {
var args = $A(arguments);
try {
if (Object.isFunction(invokedObject)) {
invokedObject.call(invokedObject, args);
} else {
invokedObject[invokedMethodName].call(invokedObject, args);
}
} catch (e) {
alert("An exception occurred in method '" + methodName + "' Error: " + e.message);
}
try {
var result = oldMethod.apply(object, args);
} catch (e) {
alert("An exception occurred in method '" + methodName + "' Error: " + e.message);
}
return result;
};
}
};

Представленная функция AOPUtils.addBefore позволяет вам выполнить свою функцию перед функцией какого-либо объекта. Для реализации использовались стандартные javascript-функции, смысл которых состоит в следующем: apply - вызов функции с параметрами в виде массива, call - вызов функции с перечислением параметров через запятую. Первым аргументом обеих функций будет объект который в вызываемой функции будет представлять ссылку на this.

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

var SomeClass = Class.create({
simplemethod: function(url) {
alert(url);
}
});

var sobject = new SomeClass();
AOPUtils.addBefore(sobject, "simplemethod", function(args) {
var url = args[0];
url += "?param=test";
args[0] = url;
});

sobject.simplemethod("http://test.com");

Отмечу, что для использования данного механизма необоходма библиотека Prototype