Grape: не рельсами едиными, ч. 2

a6cf9ef6357c40d698dbd9c8627b622f.pngВ первом своем посте про Grape я быстренько проскакал по основным его возможностям, за что заслуженно был засыпан упреками в излишней сжатости подачи материала. По результатам публикации прошлого поста я пообещал практический пример применения фреймворка, а также сравнительные бенчмарки.Сегодня мы приступим к созданию примера — разработаем API, которое умеет: — регистрировать пользователя— активировать его по email— обновлять данные авторизованного пользователя— возвращать профиль авторизованного пользователя

TL; DR gem install grape-gen Если честно, я немного слукавил — писать мы ничего не будем.После того поста я некоторое время думал — как же мне поступить? Просто так тратить время на написание сферического примера в вакууме мне не очень хотелось — хотелось, как говорится, «и рыбку съесть и косточкой не подавиться».Поэтому я потратил немного времени и собрал гем-генератор полнофункционального приложения API на grape, назвав его незамысловато — grape-gen.«Скелет» приложения, батарейки в комплекте Не буду томить вас долгими рассказами о том, что нужно современному веб-приложению, кроме самого фреймворка.Генератор «из коробки» предлагает: — ORM (пока только mongoid)— авторизацию (Warden + CanCanCan)— фоновые задачи (Sidekiq)— сообщения в реальном времени (Faye)— интеграцию с ElasticSearch (Tire)— загрузку файлов (Carrierwave)— отправку email через Mandrill (MandrillMailer)— настроенное тестовое окружение с guard+spork и гемом для документирования API с помощью тестов.Все это склеено вместе и готово к работе.Настоятельно рекомендуется redis, нужен как минимум для faye и sidekiq.

После gem install grape-gen в шелле станет доступна одноименная команда. Для генерации приложения выполняем:

$ grape-gen app your_app_name В генераторе предусмотрена возможность отключать те или иные компоненты, вот хелп к команде $ grape-gen help app

Usage: grape-gen app APP_NAME

Options: [--path=PATH] [--orm=ORM] # Default: mongoid [--batteries=one two three] # Batteries to include # Default: [«sidekiq», «carrierwave», «mandrill», «es», «faye»] [--use-grape-rackbuilder], [--no-use-grape-rackbuilder] # Default: true

Про grape-rackbuilder, включенный по-умолчанию — это мой костыль для автозагрузки файлов и перезагрузки кода в dev-окружении. В нем еще не все гладко, да и код там ужасный, но с большинством возложенных на него функций он справляется, в ближайшее время буду его латать.Про EventMachine В комментариях к первому посту пользователь stalkerg выразил мысль, что «без асинхронного программирования такой фреймворк имеет мало смысла».Я считаю, что в этом есть доля правды, поэтому «из коробки» приложение предназначено к запуску под EventMachine-based сервером — Thin или Goliath.Это означает, что все используемые в приложении «батарейки» при загрузке препарируются на предмет замены блокирующего IO на асинхронные аналоги из EventMachine, а для Carrierwave обработка изображений выносится в thread-pool через EventMachine.defer (да, у нас GIL, но даже в этом случае за одинаковое количество времени мы обрабатываем в два раза больше изображений при этом давая event-loop «вздыхать» в два раза чаще — я проверял тестами).Ну и em-synchrony, конечно. Каждый запрос у нас выполняется в собственном fiber через Rack: FiberPool, так что никакого callback-hell.API, предоставляемое приложением POST /api/auth/register email password display_name POST /api/auth/approve_email email email_approvement_code PUT /api/profile display_name avatar remove_avatar GET /api/profile API у нас отдает JSON, который генерируется с помощью JBuilder и сериализуется через MultiJson+oj.Конфигурирование приложения Подключения к redis, elasticsearch, ключи сторонних API настраиваются в файле config/application.ymlПодключения к базе данных настраивается в config/database.ymlНастройки логгирования в config/logging.ymlНастройки sidekiq в config/sidekiq.ymlПробный запуск После того, как мы удостоверились в правильности наших конфигов, настало время запустить наше новоиспеченное приложение: $ RACK_ENV=production thin start -p 9292 # Запуск сервера API $ thin start -p 9393 -e production -R faye.ru # Запуск сервера faye $ sidekiq -C config/sidekiq.yml -r ./config/boot_sidekiq.rb -e production # Запуск демона Sidekiq После того, как процессы успешно стартанут, по адресу http://localhost:9292/faye будет доступна страница с примитивным faye-клиентом, подписанным на каналы /user/registered и /timeСообщения в канал /time отправляются Sidekiq-задачей, запланированной запускаться раз в 5 секунд.Таким образом каждые 5 секунд на страницу будет добавляться строка с временем сервера.После регистрации пользователя и подтверждения его email в канал /user/registered добавляется сообщение с его display_name, получив которое браузер добавит строку с предложением поприветствовать нового пользователя.

Тестовое окружение Тестовое окружение базируется на гемах RSpec 3, rspec_api_documentation. В комплекте полюбившиеся многим FactoryGirl, DatabaseCleaner и FakerЗапускается все это под Guard+Spork, плюс в тестовом окружении, как и в development, используется перезагрузка кода, что позволяет прогонять тесты достаточно быстро.

Отдельно стоит сказать про гем rspec_api_documentation — он позволяет совместить процесс написания тестов и формирования документации API.До этого я использовал Swagger, но к сожалению он в большей степени подходит для каноничных REST API. Если у вас API больше в стиле JSON RPC, то вам будет непросто вместить свое API в описательную структуру Swagger, при этом документирование структуры ответов API доступно только для grape-entity.Вышеупомянутый гем с помощью своего DSL поверх RSpec позволяет документировать API с помощью примеров: вы описываете тестовый пример (например, валидную регистрацию пользователя), при запуске этого примера он запоминает посланный на сервер запрос, полученный ответ, url запроса и из этой информации генерирует документацию. Также есть возможность задать описание параметров и. т. д. Вот пример:

resource «Account» do get »/accounts» do parameter: page, «Page to view»

# default: document is: all example «Get a list of all accounts» do do_request status.should == 200 end

# Don’t actually document this example, purely for testing purposes example «Get a list on page 2», : document => false do do_request (: page => 2) status.should == 404 end

# With example_request, you can’t change the: document example_request «Get a list on page 3», : page => 3 do status.should == 404 end end

post »/accounts» do parameter: email, «User email»

example «Creating an account», : document =>: private do do_request (: email => «eric@example.com») status.should == 201 end

example «Creating an account — errors», : document => [: private, : developers] do do_request status.should == 422 end end end Что дальше? Думаю, что основную информацию, необходимую для быстрого создания своего API-приложения в этом посте я предоставил.Гемы, использованные в проекте, достаточно хорошо документированы. Если вы считаете, что я упустил что-то важное — пишите в комментариях или ЛС, я обязательно добавлю.К следующему посту я постараюсь подготовить более-менее объективные бенчмарки данного приложения.Пока могу сказать, что на i5 2500K один инстанс приложения (один thread) обрабатывает ~700 запросов в секунду к POST /api/auth/register с данными существующего пользователя.Также в планах есть добавление поддержки JRuby на Goliath-сервере (уж больно там JIT хорош) и http и in-app кеширования.

© Habrahabr.ru