Недавние статьи из серии Java development 2.0 были посвящены возможностям применения подхода PaaS (платформа как услуга) для разработки на языке Java. На этот раз я предлагаю вашему вниманию Heroku, еще одну популярную систему PaaS, теперь также поддерживающую приложения Java.
Heroku, ведущая свою родословную от Ruby, подходит к разработке и развертыванию приложений Java совершенно иначе, чем знакомые вам другие разновидности Java PaaS, такие как Amazon Elastic Beanstalk и Google App Engine (GAE). Прежде чем начать практическое знакомство с Heroku, полезно узнать, что общего она имеет с этими двумя платформами и в чем от них отличается.
GAE и Beanstalk: два разных тяжеловеса
Как мы уже узнали из предыдущих статей этой серии, Google App Engine и Amazon Elastic Beanstalk являются полными противоположностями в смысле гибкости. В то время как крайне строгий GAE требует, чтобы вы играли по его правилам или не играли вообще, Elastic Beanstalk допускает полную настройку: PaaS от Amazon позволяет исполнять все, что работает на JVM. GAE ограничивает выбор библиотек Java и контролирует способы масштабирования вашего приложения. На практике развертывание приложения в GAE напоминает игру в угадайку: вы не знаете, где живет ваше приложение и даже сколько экземпляров приложения одновременно существует. Положительный момент здесь в том, что Google выполняет за вас все масштабирование, и, само собой разумеется, что приложения GAE масштабируются очень хорошо. Передавая управление. GAE, вы можете практически ни о чем не беспокоиться, кроме написания максимально надежного кода.
Amazon's Elastic Beanstalk находится по другую сторону зеркала. Он не только предоставляет вам богатый набор инструментов, но и в большой степени позволяет контролировать способ масштабирования приложения. Недостаток этого подхода очевиден: без вашего личного вмешательства Amazon почти ничего делать не будет. И все же если вы хотите тонко настроить развертывание своего приложения и хотите иметь хорошо масштабируемую инфраструктуру, Amazon Elastic Beanstalk — очень хороший выбор.
Если бы мне предложили оценить простоту масштабирования (в смысле написания кода и развертывания) по десятибалльной шкале (10 — очень сложно, 0 — без усилий), я бы дал GAE 4 балла, а Elastic Beanstalk — 6. GAE позволяет без проблем выполнять скрипты и загружать файлы, но ограниченный диапазон совместимых средств разработки несколько противоречит моему стилю. В отличие от этого, Elastic Beanstalk позволяет использовать любые библиотеки, но путь к развертыванию оказывается дольше и, зачастую, утомительней; приходится жертвовать простотой ради большего контроля.
Подобно GAE и Elastic Beanstalk, Heroku рассчитана на горизонтальное масштабирование. Вы развертываете свой код в то, что в Heroku называется dyno; в сущности, это веб-контейнеры. Если вы хотите расширить систему, нужно просто добавить больше dyno, позволив Heroku обрабатывать больше одновременных веб-запросов. Эта концепция очень проста и предоставляет больше контроля, чем GAE, без конфигурационных требований Elastic Beanstalk.
В Heroku конвейером развертывания является Git — а не Ant и не Maven. Когда вы развертываете свое приложение, вы делаете это через Git push, что мы подробнее обсудим позже в этой статье.
Если сравнить Heroku с GAE и Elastic Beanstalk по легкости масштабирования, то я бы поставил ему 2, что означает, что он практически не требует усилий. В плане разнообразия инструментов Heroku предлагает большую гибкость, и я могу выбрать наиболее эффективный инструмент для любой конкретной работы. В плане конфигурирования Heroku дает больше контроля, чем GAE, но меньше, чем Elastic Beanstalk — и иногда это именно то, что нужно. К тому же приложение, построенное на Heroku, можно легко перенести на Elastic Beanstalk: если вдруг мне понадобится более тонкое управление масштабированием приложения, я знаю, что всегда могу это сделать.
Чтобы начать работу с Heroku, нужно установить и настроить следующие компоненты:
- Ruby и RubyGems (оболочка командной строки Heroku написана на Ruby, а установка производится через RubyGems)
- Завести учетную запись Heroku, что делается бесплатно
- Git
- Maven
Заметьте, что Maven для Heroku не нужен, просто я использую его как средство сборки. Документация Java в Heroku тоже опирается на Maven.
Установив все необходимые компоненты, выберите директорию, в которой будете работать, и запустите следующую команду Maven (код разбит на строки в соответствии с шириной страницы):
mvn archetype:generate -DarchetypeGroupId=org.mortbay.jetty.archetype -DarchetypeArtifactId=jetty-archetype-assembler -DarchetypeVersion=7.5.0.RC0 |
Maven предложит вам ввести идентификаторы groupId и artifactId. Обычно для groupId я использую имя своего пакета, а для artifactId — имя своего проекта. Затем Maven создаст структуру проекта, позволяющую собирать и запускать веб-приложения поверх Jetty. Таким образом, за один шаг вы создаете костяк веб-приложения, готовый к развертыванию в инфраструктуру облака Heroku. При этом, хотя Jetty является прототипом Maven, Heroku не поддерживает Jetty на стороне веб-сервера. На самом деле Heroku даже не знает о Jetty — да и ему все равно.
Предыдущая команда Maven создает очередное простейшее приложение типа "Hello World". Если войти в директорию src этого приложения и затем в main—>webapp, можно увидеть файл index.html. Запустите приложение, и этот файл (как вы, наверно, уже догадались) напечатает текст "hello world".
Запустить приложение тоже просто. Если подать команду mvn install в директории вновь созданного проекта (который носит имя, присвоенное идентификатору artifactId), будет создан скрипт командного процессора для нужной вам операционной системы (в моем случае это OSX). Просто наберите $>sh target/bin/webapp, затем перейдите по адресу http://localhost:8080 в своем любимом браузере и получите приветствие.
С помощью Heroku можно развернуть любую нужную вам библиотеку Java. Для примера (и по традиции — по крайней мере для тех, кто читает эту серию), я собираюсь создать еще одну инкарнацию моего мобильного веб-сервиса Magnus, собирающего информацию о местоположениях, который дебютировал в моем введении в Amazon Elastic Beanstalk (см. Ресурсы). В качестве библиотеки RESTful я собираюсь использовать Apache Wink, который представляет собой реализацию спецификации JAX-RS. Моя реализация веб-сервиса предоставит конечную точку PUT, которая принимает JSON и вставляет полученные из документа соответствующие данные в экземпляр MongoDB. Он, в свою очередь, будет размещен в MongoHQ с помощью Morphia (см. Ресурсы).
Первое, что нужно сделать, — это добавить в Maven-файл pom.xml в Magnus новые зависимости для Wink и Morphia, как показано в листинге 1:
Листинг 1. Добавление Wink и Morphia в Maven POM
<dependency> <groupId>org.apache.wink</groupId> <artifactId>wink-server</artifactId> <version>1.1.3-incubating</version> </dependency> <dependency> <groupId>org.apache.wink</groupId> <artifactId>wink-json-provider</artifactId> <version>1.1.3-incubating</version> </dependency> <dependency> <groupId>com.google.code.morphia</groupId> <artifactId>morphia</artifactId> <version>0.99</version> </dependency> |
Заметьте, что я также изменил свой файл POM в хранилище Morphia Maven и получил версию 0.99:
Листинг 2. Добавление нового хранилища в Maven POM
<repositories> <repository> <id>morphia repository</id> <url>http://morphia.googlecode.com/svn/mavenrepo/</url> </repository> </repositories> |
Затем я создаю ресурс местоположений — конечную точку Wink, которая будет представлять местоположения пользователя:
Листинг 3. Создание ресурса местоположений Wink
@Path("/location")
public class LocationResource {
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Path("{id}")
public String updateAccountLocation(@PathParam("id") int accountId, JSONObject
requestJSON) {
try{
double latitude = Double.parseDouble(requestJSON.get("latitude").toString());
double longitude = Double.parseDouble(requestJSON.get("longitude").toString());
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy HH:mm");
Date dt = formatter.parse(requestJSON.get("timestamp").toString());
new Location(accountId, dt, latitude, longitude).save();
return new JSONObject().put("status", "success").toString();
}catch(Exception e){
return "failure with " + requestJSON;
}
}
}
|
Wink позволяет описать мой сервис RESTful посредством трех аннотаций: одной для метода HTTP (PUT), другой для предполагаемого типа запроса (JSON) и последней для указания того, что конечная точка приняла параметр (в данном случае location/:accountId).
Этот класс Location является тем же поддерживаемым в Morphia объектом, который был представлен в моем введении в Elastic Beanstalk. Он просто создает в MongoHQ документ, представляющий местоположение для данной учетной записи. Это местоположение (теоретически полученное от мобильного устройства) представлено параметром конечной точки RESTful.
Теперь я хочу связать Wink с Jetty. Для этого мне нужно проделать две вещи: создать класс Application и настроить файл web.xml.
Класс Wink Application (показанный в листинге 4) предназначен для загрузки соответствующих классов ресурсов:
Листинг 4. Класс Application в Wink
public class MarmarisApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<Class<?>>();
classes.add(LocationResource.class);
return classes;
}
}
|
В листинге 5 я изменил файл web.xml своего приложения, добавив специфические для Wink атрибуты, такие как указатель на мой класс Application и нужный мне URL, который в данном случае такой /service/resource:
Листинг 5. Подключение Wink через web.xml
<servlet> <servlet-name>MarmarisApp</servlet-name> <servlet-class>org.apache.wink.server.internal.servlet.RestServlet</servlet-class> <init-param> <param-name>javax.ws.rs.Application</param-name> <param-value>com.b50.marmaris.MarmarisApplication</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>MarmarisApp</servlet-name> <url-pattern>/service/*</url-pattern> </servlet-mapping> |
Для проверки попробуйте еще раз выполнить команду mvn install и запустить приложение (в данном случае Magnus) локально. При этом вы должны суметь передать документ JSON (показанный в листинге 6) в конечную точку http://localhost:8080/service/location/{account_id}, где account_id является числом.
Листинг 6. Документ JSON, представляющий местоположение
{
"name":"location payload",
"latitude":"46.49",
"longitude":"71.11",
"timestamp":"09-02-2011 14:43"
}
|
Таким образом, мы без труда написали приложение, и теперь готовы перейти к еще более простой части: развернуть его в облаке Heroku!
Развертывание в Heroku выполняется через Git, что потребует от вас некоторой адаптации, если вы не знакомы с распределенными системами управления версиями. Развертывание в Heroku (через Git) подобно фиксации в Subversion в другую ветвь, отличную от основной линии разработки. Но в данном случае Heroku не является основным репозиторием кода; она лишь выступает в роли альтернативного удаленного хранилища. Для развертывания приложения вы выполняете операцию push для его исходного кода через Git.
Заметьте, что Heroku развертывает проект не через файлы WAR, а «как есть». Как вы увидите, когда мы начнем создавать Procfile, для нашего приложения, Heroku – это просто JVM, ищущая код, который можно исполнить. (Чтобы убедиться в этом прямо сейчас, загляните в файл Main.java, созданный в структуре пакета вашего проекта, и сопоставьте его содержимое со своим POM.)
Хотя модель развертывания Heroku может показаться неочевидной, она становится понятной, как только вы начнете ею пользоваться. Более того, тесная интеграция Heroku с Git позволяет быстро и без проблем перемещать разные ветви в разные среды.
Развертывание выполняется в два этапа. Сначала вам нужно создать свой код и передать его в локальный репозиторий Git. Это можно сделать в корневой директории вашего проекта, набрав в терминале команды, приведенные в листинге 7:
Листинг 7. Инициализация и передача в Git
$> git init $> git add . $> git commit -m "начальная передача до развертывания в Heroku" |
Теперь в локальном хранилище Git находится снимок вашего кода (т.е. создана его версия).
Затем нужно создать приложение в Heroku. Я сделал это в листинге 8 через клиент командной строки heroku (вот здесь важно, чтобы у вас были установлены Ruby и RubyGems):
Листинг 8. Создание приложения Heroku
$> heroku create marmaris --stack cedar |
Команда heroku create в листинге 8 создает приложение с именем "marmaris" на стеке Cedar. Заметьте, что вам придется выбрать для приложения другое имя, поскольку это уже занято. Вы также можете предоставить выбор имени Heroku, и она сама создаст для вас уникальное имя.
Heroku имеет несколько стеков. Cedar поддерживает Java и Node.js, тогда как другие (например, Bamboo) поддерживают новые версии Ruby. Когда вы исполните команду heroku create, она обновит вашу конфигурацию Git и добавит дубликат удаленного хранилища heroku.
Перед развертыванием своего кода в Heroku вы должны сообщить ей, как следует исполнять ваше приложение. Это легко делается с помощью Procfile, который является простым текстовым файлом с командой. Показанный ниже мой вариант Procfile указывает на скрипт командного процессора webapp в моей директории target.
Листинг 9. Создание Procfile
$> echo 'web: sh target/bin/webapp' > Procfile |
После создания нового файла важно уведомить об этом Git; в противном случае Heroku не будет знать о его существовании при развертывании приложения через Git push.
Листинг 10. Уведомление Git
$> git add Profile $> git commit -m "включение моего Profile" |
И, наконец, чтобы развернуть приложение, вы просто подаете команду Git push в удаленный репозиторий heroku, как показано в листинге 11:
Листинг 11. Развертывание в Heroku
$> git push heroku |
Вы должны увидеть несколько сообщений, поступивших от heroku; обратите внимание на то из них, которое имеет вид:
http://your_app_name.herokuapp.com deployed to Heroku |
Введите этот адрес в свой любимый браузер (при условии, что вы оставили в директории проекта стандартный файл index.html), и вы увидите сообщение "Hello World!".
Конечно, всегда интересно читать сообщение "Hello World!", но цель моего приложения — принимать информацию о местоположении через HTTP PUT. Поэтому, используя RESTClient от WizTools.org, я могу подать команду HTTP PUT в мою конечную точку RESTful, создав документ JSON, и — вуаля! — я послал замечательный ответ JSON с прекрасным словом success (успех).
Масштабирование и обслуживание Heroku
По умолчанию Heroku запускает ваше приложение в одном контейнере (dyno). Этот бесплатный dyno отключается при отсутствии активности и включается снова при поступлении запроса. Если вам нужно расширить свое приложение, вы добавляете больше dyno, что можно сделать командой heroku scale:
Листинг 12. Масштабирование приложения
$> heroku scale web=2 |
При необходимости вы можете масштабировать приложение обратно, уменьшив число запрошенных dyno. Например, в данном случае я масштабируюсь обратно к размеру web=1. Обе эти операции можно выполнить и через веб-интерфейс Heroku.
Кроме того, в Heroku можно просматривать журналы с помощью команды heroku logs, как показано в листинге 13:
Листинг 13. Просмотр журналов в Heroku в реальном времени
$> heroku logs -t |
Клиент командной строи heroku поддерживает множество функций масштабирования, мониторинга и управления вашим приложением; подробная документация приведена в Ресурсах . Кроме того, помимо стандартного хранилища Heroku поддерживает многие дополнительные компоненты сторонних поставщиков. Рекомендую ознакомиться с поддержкой Heroku для MongoHQ и PostgreSQL.
Тесная интеграция Heroku с Git открывает новые пути развертывания и масштабирования приложений Java в облачной среде и при этом делает этот процесс чрезвычайно легким и эффективным. Лично меня привлекает свобода использования любых библиотек Java и развертывание созданных приложений практически без усилий.
Научиться
- Оригинал статьи: Java development 2.0: Git-commit your Java apps with Heroku's PaaS.
- Серия статей Java development 2.0 посвящена технологиям, меняющим принципы разработки на языке Java. Среди тем статей —
Amazon's Elastic Beanstalk (февраль 2011 г.); Google App Engine (август 2009 г.); RESTClient (ноябрь 2009 г.); Gretty (август 2011 г.); MongoDB (сентябрь 2010 г.).
- "Git меняет правила распределенной веб-разработки" (Уильям фон Хаген (William von Hagen), developerWorks, август 2009 г.): распределенные системы управления версиями сами по себе новость, но уникальная поддержка в Git коллективной работы и взаимодействия разработчиков действительно меняет правила игры.
- "Введение в Maven 2" (Син Ли (Sing Li), developerWorks, декабрь 2006 г.): если вы хотите узнать больше о Maven, это учебное пособие объяснит вам все от "А" до "Я".
- "Применение Apache Wink, Eclipse и Maven для разработки веб-сервисов RESTful" (Габриэл Матееску (Gabriel Mateescu), developerWorks, февраль 2011 г.): узнайте больше о совместном применении Wink и Maven для разработки приложений RESTful.
- Путь обучения: введение в PaaS (платформа как услуга) (март 2011 г.): руководство по изучению PaaS.
- "Облако и отраслевые приложени я. Часть 1: передовой опыт и шаблоны PaaS" (Юй Чэнь Чжоу (Yu Chen Zhou) с соавторами, developerWorks, декабрь 2010 г.): ознакомьтесь с концепциями, примерами и моделями отраслевых облачных решений.
- Модели облачных вычислений. Часть 2: платформа как услуга (Дэн Орландо (Dan Orlando), developerWorks, январь 2011 г.).
- "Турнир Java PaaS-систем" (Майкл Юань (Michael Yuan), developerWorks, апрель 2011 г.): техническое сравнение Google App Engine, Amazon Elastic Beanstalk и CloudBees RUN@Cloud.
Получить продукты и технологии
- Введение в Heroku.
- Загрузите Ruby и RubyGems.
- Play, Gretty и Wink – три популярных инструмента для сборки приложений RESTful.

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