Веб сервис «Афоризмы» и Desktop клиент к нему

30 Ноя
2011

Мне всегда нравилось читать афоризмы. В них кратко заключены истины, которые порой забываются в суете или просто неочевидны. Интересно, что многие эти фразы получаются как раз к месту в текущий момент, а сказаны были еще до нашей эры.

Но обычно бывает, что после прочтения хорошего высказывания оно вылетает из головы. Хотя хочется помнить его, особенно в ситуации, про которую идет речь. Иногда я даже вешал себе на компьютер листочки с любимыми афоризмами, которые потом куда-то терялись. Один из способов, чтобы цитата была всегда в памяти — это периодически читать ее снова. У меня потихоньку зарождалась идея создать какое-нибудь приложение, которое бы само показывало цитаты. Тут еще я начал изучать Java и подошел к моменту, когда уже скучно делать задачки из книги и захотелось поработать над чем-то интересным. Скриншоты, ссылки на код и приложение – в конце статьи.
В интернете много хороших сайтов с афоризмами, однако опять же в них нужно лезть, чтобы почитать цитаты. Обычно мне это лень или я просто забываю. Возможно, уже есть какое-либо реализованное десктоп приложение, которое само показывает афоризмы (найти не удалось, но искал я недолго). Мне хотелось самому попробовать это сделать и заодно освоится в Java.

Суть проекта


Есть десктоп приложение, которое показывает афоризмы в маленьком окне с определенным интервалом. Афоризмы эти оно получает с веб сервиса. Если афоризм понравился — то его можно пометить как «любимый». Пользователь всегда может посмотреть список своих любимых афоризмов. Так же у цитат есть рейтинг, который равен количеству людей, у которых она любимая.

Сервер


Первым делом я стал искать бесплатный хостинг для Java servlet’ов, на котором буду пробовать свой веб сервис. Это оказалось проблематично, т.к. почти все хостинги с поддержкой Java — платные. Немного погуглив, я остановился на Google App Engine (GAE), там и Java поддерживается, и есть бесплатные квоты для использования вычислительных мощностей сервера.

Архитектура

Далее надо было решить, по каким правилам обмениваться данными с сервером. В начале хотел сделать сервис с протоколом SOAP, к тому же есть подробное описание как настроить SOAP на GAE. Но он слишком громоздкий, а архитектура REST видится более логичной и подходящей в моем случае.
В качестве реализации Java интерфейса JAX-RS для REST веб сервисов я выбрал Jersey, формат данных — XML.

База данных

В Google App Engine для Java реализованы два интерфейса для работы с базами данных — Java Data Objects (JDO) и Java Persistence API (JPA), мне больше понравился JDO.

Самое трудное оказалось создать ресурс, который бы возвращал случайную цитату. Я хотел, чтобы десктоп клиент показывал именно случайные цитаты по умолчанию. Механизм работы с базой данной на GAE не поддерживает операцию «получить случайную запись». Популярное решение для реализации этой возможности — добавить к каждой записи поле, которому присваивается случайное значение при создании. Чтобы получить объект мы опять генерим случайное значение и делаем запрос к базе данных: «получить все записи, у которых поле со случайным значением больше сгенерированного» и берем первую запись из полученного списка. Подход хороший, однако случайность получается не совсем случайной: одни записи генерятся заметно чаще других. Оно и понятно, ведь при создании значения присваиваются не равномерно, т.е. может быть скопление в одном месте диапазона и разреженность в другом.
Поэтому я решил присваивать дополнительному полю значения по порядку, начиная с 0. Для получения случайного афоризма делается такой же запрос, но случайное значение генерится в диапазоне от 0 до последнего присвоенного индекса. При таком подходе придется иметь одну глобальную запись в базе данных, в которой будет храниться последний присвоенный индекс. Постоянно обновлять эту запись нужно только при добавлении афоризмов, а при их получении это значение можно брать из кэша. У моего сервиса добавление афоризмов — операция редкая по сравнению с получением, так что такой вариант подходит.

Регистрация

Следующая задача, которая необходима на сервисе — выяснить, является ли пользователь зарегистрированным. Регистрацию на сервисе я сделал на основе протокола Oauth. Это удобно, т.к. не надо заморачиваться с реализацией — регистрация управляется на другом, не зависящим от меня ресурсе (Google). Пользователь просто разрешает моему приложению получить доступ к некоторым сведениям его аккаунта, например узнать электронную почту.

WADL

Еще один момент: генерация Web Application Description Language (WADL). WADL — это язык описания REST веб сервиса, аналог WSDL для SOAP. Jersey по умолчанию генерит его в сильно упрощенном формате, по нему нельзя определить какие точные запросы принимает сервер и как отвечает. Для того, чтобы Jersey генерил WADL в расширенном формате (с указанием XML schema, комментариями), необходимо настроить веб сервис так, как написано здесь, что я и сделал. Результат тут.

Биллинг и оптимизация

К сожалению, 7 ноября Google обновил биллинговую политику, и теперь на их хостинге идет учет потребления не CPU-часов, а неких instance-часов. Instance — это VM с некоторым объемом выделенной памяти и CPU, которая обслуживает запросы. Если одной instance не хватает, то appengine создает вторую, третью и т.д. Мое приложение по старой схеме расходовало ~ 1% бесплатных ресурсов в день, а по новой схеме уже 10-15%, при том, что пользовался сервером в основном я один (но интенсивно). Это конечно не прибавило энтузиазма, но все-таки я начал искать возможности исправить положение.

С помощью stackoverflow.com, а так же отличного поста от Andrei Bodnarescu здесь, удалось снизить потребление квот. Основной вклад внесло кэширование (memCache), которое я добавил где смог. С его помощью удалось уменьшить затраты instance-часов в ~10 раз для некоторых REST-запросов. Так же добавил опцию true в appengine-web.xml, которая разрешает создавать новые потоки для обработки запросов, вместо новых процессов.

Eсть один нюанс на хостинге GAE: если к сервису не поступает запросов в течение какого-то времени, то инстанс высвобождает занятые ей ресурсы. При новом запросе она должна быть проинициализирована, что потребляет много ресурсов. В итоге получается такая картина, что при интенсивных запросах квоты расходуются медленнее, а при редких запросах и долгих простоях — быстрее. Можно в опциях заказать интансы, которые будут жить постоянно, но это не помогает снизить затраты. Почему я точно не понял, скорее всего квоты расходуются и на то, что инстанс постоянно загружена в память.

Такие основные вопросы возникали у меня в процессе создания сервера. Теперь расскажу немного про клиент.

Клиент


GUI

Самое главное, что должно делать мое десктоп приложение — показывать цитаты. Показывать во всплывающем окне, как какой-либо мессенджер делает.
Для Java стандартная библиотека для построения окон — Swing, ее я и использовал. Поискав в интернете готовые решения для всплывающих окошек, мне понравился jtoaster. В основе взял его код плюс много чего доработал.

В целом, в части графического интерфейса я использовал стандартные возможности Swing. Создал главное окно, в котором можно посмотреть разные списки цитат: последние показанные, любимые, самые популярные. Так же окно настроек, окно регистрации и окно About.

Связь с сервером

Для взаимодействия с сервером использовал клиентскую часть библиотеки Jersey, с помощью которой удобно посылать REST запросы и принимать ответы.

Регистрация

Как я уже упоминал, регистрация осуществляется по протоколу OAuth и поддерживаются Google аккаунты. Шаги, необходимые для регистрации пользователя на веб-приложении клиенте, хорошо показаны здесь.

Для десктоп приложения в принципе все тоже самое. С позиции пользователя это выглядит так:
  1. Пользователю дается ссылка, по которой следует пройти для начала регистрации.
  2. Он проходит по ссылке и попадает на сайт ресурса, который управляет регистрацией (Google accounts). Там он вводит свои логин, пароль.
  3. Далее у него запрашивается одобрение на доступ к данным его аккаунта из моего веб-сервиса. Он разрешает.
  4. Пользователь нажимает «завершить регистрацию» в десктоп приложении.

Регистрация окончена.

Вот в прицепе и все. Самые важные моменты создания приложения я описал. Остальное в основном — создание окошек и их взаимодействие, что заняло кстати больше всего времени :).

Обобщу возможности десктоп-клиента:
  • Программа «сидит» в трее. Работает на Windows, некоторых Linux дистрибутивах (проверял на Kubuntu).
  • Программа сама показывает афоризм с заданным интервалом (по умолчанию 15 минут). Так же можно принудительно показать афоризм нажав кнопку из трей меню.
  • Зарегистрированные пользователи могут отмечать понравившееся афоризмы как любимые. Если один пользователь отметил афоризм как любимый, то рейтинг этого афоризма увеличивается на 1. Соответственно, если афоризм перестает быть любимым, рейтинг уменьшается на 1.
  • Помимо небольших всплывающих окон, в которых отображаются цитаты, у программы есть главное окно. В нем можно увидеть список последних показанных цитат, список любимых цитат, список самых популярных цитат. Так же в каждом списке возможен поиск.
  • Имеется окно настроек, в котором можно задать нужные параметры, такие как прокси сервер, интервал отображения афоризмов и т.д.
  • Во всплывающем окне вместе с афоризмом отображается имя автора. При нажатии на автора появляется еще одно окошко с его краткой биографией и ссылкой на более подробную информацию. Описание автора так же появляется при двойном нажатии на автора в списках главного окна.

Получилось как-то так:

трей меню

афоризм с прозрачностью

афоризм и биография автора

весь экран

Ссылки:
http://sourceforge.net/projects/bwtclient/ — десктоп клиент (приложение и код)
http://sourceforge.net/projects/bwtserver/ — сервер (код)
Cпецификация ресурсов веб-сервиса:
http://bestwisethoughts.appspot.com/index.html — «читабельная»
http://bestwisethoughts.appspot.com/application.wadl — WADL
На сервере сейчас около 800 афоризмов.
Спасибо за внимание, рад буду услышать ваши замечания и предложения.
По материалам Хабрахабр.



загрузка...

Комментарии:

Наверх