Grape: не рельсами едиными


    В этом посте я хотел бы познакомить вас с Grape — веб-фреймворком, написанным на ruby, предназначенным для быстрой и удобной разработки API, а также немного порассуждать о судьбе Rails в свете последних тенденций в веб-разработке.



    Ruby = Ruby On Rails


    Как-то так сложилось, что при упоминании ruby в качестве средства веб-разработки (да и просто в качестве скриптового ЯП) передо многими людьми, имеющими некоторое отношение к этой самой веб-разработке, в голове возникает если не логотип с пресловутыми белыми рельсами на красном фоне, то магическое словосочетание Ruby On Rails уж точно.
    Я не берусь спорить, хорошо это или плохо — эта статья не для спора. Одно могу сказать с уверенностью — RoR оказал огромное влияние на развитие веб-фреймворков в целом, а этот вклад переоценить крайне трудно.

    НО


    Но жизнь не стоит на месте.
    Веб становится динамичным, все большее и большее значение приобретают мобильные приложения, пользователю нужно потреблять контент «не отходя от кассы», то есть с экрана своего айфона, гугл нексуса, хуайвэя, впиши_название_своего_телефона.
    Да и сами сайты нуждаются в качественно новых подходах к организации пользовательских взаимодействий и подаче контента.
    AngularJS, Ember, Meteor.js, Derby.js — технологии, предвосхищающие очередной прорыв в сайтостроении, который можно сравнить с «изобретением» AJAX в старые добрые времена.
    Ruby-разработчикам нужно мощное и в то же время легкое для освоения средство для разработки API, которым когда-то стали RoR для обычных сайтов.

    Давайте уже к делу!


    Действительно, хватит рассуждений. Встречайте — Grape
    Это фреймворк, заточенный под разработку API, никаких швейцарских ножей.
    Но надо отдать должное, он умеет делать API очень неплохо.
    Попробую перечислить основные его достоинства:
    1. DSL, заточенный под описание API
    2. версионирование API из коробки
    3. параметризация методов со встроенной валидацией
    4. автоматическая генерация OPTIONS (кто встречался с CORS — оценит)
    5. прозрачная работа с форматами API
    6. встроенный DSL для документирования

    вот далеко не полный перечень инструментов, которые облегчают жизнь разработчика API, когда он использует Grape.

    Code time


    Для начала приведу пример простого приложения, которое по адресу /hello/world.json вернет нам {hello: 'world'}

    Gemfile
    source 'https://rubygems.org'
    
    gem 'grape', github: 'intridea/grape'
    gem 'rack', '~> 1.5.2'
    gem 'thin', '~> 1.6.2'
    


    hello_world.rb
    require 'grape'
    
    class HelloWorld < Grape::API
      format :json
      namespace :hello do
        get :world do
          {hello: 'world'}
        end
      end
    end
    


    config.ru
    require_relative 'hello_world'
    
    run HelloWorld
    


    На моем i5 c 16 Гб памяти и HDD это приложение стартует где-то за 400-700 мс. Вот список используемых гемов:
    Using i18n 0.6.11
    Using json 1.8.1
    Using minitest 5.4.1
    Using thread_safe 0.3.4
    Using tzinfo 1.2.2
    Using activesupport 4.1.6
    Using descendants_tracker 0.0.4
    Using ice_nine 0.11.0
    Using axiom-types 0.1.1
    Using builder 3.2.2
    Using coercible 1.0.0
    Using daemons 1.1.9
    Using equalizer 0.0.9
    Using eventmachine 1.0.3
    Using hashie 3.3.1
    Using multi_json 1.10.1
    Using multi_xml 0.5.5
    Using rack 1.5.2
    Using rack-accept 0.4.5
    Using rack-mount 0.8.3
    Using virtus 1.0.3
    Using grape 0.9.1 from git://github.com/intridea/grape.git (at master)
    Using thin 1.6.2
    

    Как вы могли заметить, в Grape есть чудесная штука, которая называется namespace. Она же group, resource, resources, segment — все для удобства чтения кода.
    При этом она может использоваться без параметров. Казалось бы, зачем? А вот вам пример:
    namespace :authorized do
        before { authorize! }
        get :some_secret_data ...
    end
    group do
        before { authorize! }
        get :some_secret_data ...
    end
    

    Это как в фильме — «все что случилось в Лас-Вегасе — остается в Лас-Вегасе».
    Внутри групп, равно как нэймспейсов, вы можете определять before и after блоки, которые будут выполняться только для роутов, указанных в данных группах (и глубже).

    Вот пример, демонстрирующий использование параметров:
    params do
        requires :first_name, type: String
        requires :last_name, type: String
        optional :birth_date, type: DateTime 
    end
    post :register do
    ...
    end
    

    Как по мне, так понятно без слов. Управление в роут даже не попадет, если с запросом не будут переданы параметры, которые удовлетворяют описанным условиям. Самое чудесное, что это все с минимальными модификациями можно использовать для документирования API. Например:
    desc 'User signup'
    params do
        requires :first_name, type: String, desc: 'First name'
        requires :last_name, type: String, desc: 'Last name' 
        optional :birth_date, type: DateTime, desc: 'Date of birth'
    end
    post :register do
    ...
    end
    

    Убиваем сразу целую охапку зайцев — код документирован на месте, подключив grape-swagger получаем swagger-совместимую документацию. Изменили код — изменилась документация.

    Одной из многих чудесных штук, которые меня покорили в Grape, является mount. Позволяет примонтировать ранее описанный API в новое место:

    mount.rb
    class Mount < Grape::API
      get :mounted do
        {mounted: true}
      end
    end
    

    mount.rb
    require 'grape'
    require_relative 'mount'
    class HelloWorld < Grape::API
      format :json
      namespace :hello do
        mount Mount
        get :world do
          {hello: 'world'}
        end
      end
    end
    

    Как мы все уже поняли, наш роут из класса Mount станет доступен по адресу /hello/mounted.json

    «Меня терзают смутные сомнения...»


    Само собой, объема среднестатистической статьи, которая не вызовет у хабражителя стойкого зевотного рефлекса, вряд ли хватит, чтобы рассказать о всех плюсах и минусах фреймворка. Моей задачей в первую очередь было вызвать в вас интерес — документация у проекта неплохая, трудностей с дальнейшим изучением возникнуть не должно.
    Также на гитхаб-страничке можно найти перечень гемов, которые можно использовать совместно с grape.

    Эпилог


    До недавнего времени у проекта была небольшая проблема, связанная с автоматической перезагрузкой измененного кода в dev-режиме. Все Rails-разработчики к этому привыкли и на мой взгляд это must have feature. В issues на гитхабе эта проблема была озвучена несколько раз и вроде предлагались какие-то решения на Rack::Reloader и для случаев использования совместно с Rails.
    Я позволю себе упомянуть свое собственное решение, которое увидело свет буквально пару недель назад, а именно гем grape-reload, предназначенный для использования в plain-rack стеках.
    Для grape версии 0.9.0 и ранее можно использовать версию гема 0.0.3, для более поздних и master-ветки фреймворка используйте master-ветку репозитория гема.

    Если вам будут интересны дальнейшие статьи, посвященные данному фреймворку — не забудьте упомянуть об этом в комемнтариях. Всем ruby, посоны!

    P.S. В ближайшее время напишу статью по созданию API для todo-листов, с использованием БД. Допускаю, что из данной вводной статьи непонятно, в чем отличие этого фреймворка от Rails и почему я не сравниваю его с Sinatra.
    Также постараюсь в следующей статье сделать бенчмарк приложения на ActiveSupport::Metal и Grape.
    Поделиться публикацией

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

      +6
      Из вашей статьи не понятно, чем это хозяйство лучше rails с гемами для построения API. Сделали бы какое-то сравнение, какие плюсы, какие минусы. А пока похоже на перевод туториала, что не так интересно.
        +1
        Да собственно лучше только тем, что нет кучи middleware из коробки. Настолько я помню Grape появился раньше чем ActiveController::Metal. Ну и в сферическом вакуме запрос быстрее проходит через приложение.
          +4
          Странно, что автор сравнивает Grape с Ruby on Rails, а не с Sinatra, который как раз используют когда надо пострелять по воробьям. Я не уверен, что если производительность критична, Grape – хороший выбор. Я, например, в таком случае писал бы на Go, ну или на NodeJS.
            –2
            На Grape быстрее чем на Go и NodeJS написать законченный проект.

            С Sinatra сранивать глупо потому, что синатра совсем уж голая относительно Grape. Grape все же удобный фрэимворк для API, легко можно замаунтить к приложение с рельсой если хочется.
              +1
              Sinatra точно так же легко можно замаунтить в рельсы. Grape кроме этого существенно тормознее чем Sinatra
                +1
                Так любое Rack приложение можно замаунтить в любом другом Rack приложении.
        0
        Пока основное отличие которое вижу — больший упор на конфигурирование. И да, думаю grape лучше сравнивать с sinatra или почившем espresso.
          +8
          На hello worldах все фреймворки хороши.
            +1
            А производительность то какая будет? Over 5000000000...e hello world'ов в секунду…
            0
            Основная фишка — заточенность и DSL? А как на тему реюза имеющегося кода?

            Если в том же RoR'е есть набор моделек — оно оттуда без б подключается?

            ps: Я не силён в деталях ruby-стэка
              0
              подключаете ActiveRecord и все
                0
                Не совсем понял, что вы имеете в виду под реюзом кода. Случай, когда Grape подключается к Rails и использует его модели? Если да, то это без проблем.
                Я в первую очередь хотел показать, как можно жить без Rails и не отказывать себе ни в чем, хотя бы а рамках написания API.
                0
                Мне кажется без асинхронного программирования такой фреймворк имеет мало смысла.
                  0
                  Это вы зря, конечно. Но если вам хочется полной асинхронности, то я постараюсь в следующем посте сделать пример с асинхронным sleep() внутри роутов.
                    0
                    А к БД доступ то же будет асинхронный?
                    Просто красиво отдать данные это пол дела… их ещё ведь надо откуда то забрать.
                      0
                      Да, например mysql2, да и любой, поддерживающий eventmachine. Из gemfile видно, что у автора поста thin в ходу.
                      0
                      Это не такая тривиальная задача. Обычно sleep останавливает всю EM. Вроде как реализация sleep на fiber'ах есть в em-synchrony, но работает ли она из коробки не помню.
                        0
                        Работает :)
                          0
                          Работает, но как? Если поставить sleep на десять секунд, сделать вызов, и ещё один через пять секунд, ответ на второй приходит через пять или через десять секунд после первого?

                          P.S. Не совсем понимаю смысл вашего смайлика. Вы смщённо улыбаетесь, шутите, нервно улыбаетесь, троллите, или просто по жизни весёлый человек?
                    0
                    Если я правильно понял, решить проблему перезагрузки можно с помощью shotgun и ActiveSupport::Dependencies.autoload_paths?
                      0
                      Зависит от того, насколько вы терпеливы. Shotgun на каждый запрос будет заново загружать файлы проекта — если их у вас не 2 а 20, то это примерно 3 секунды.
                      Из-за специфики кода Grape его нелья перезагружать просто выгрузкой объявленных констант и повторным require изменившегося файла.
                      Когда я начал свои изыскания в этом вопросе, я первым делом прицепил Grape к Padrino, у которого достаточно неплохой релоадер. Это сработает в определенных пределах — простое приложение типа этого HelloWorld будет перезагружаться, но начнут задваиваться роуты — это можно увидеть подключив grape-swagger. Потом я попытался пропатчить Grape::API, чтобы заставить его нормально работать с Padrino::Reloader — то, что получилось в итоге, по времени релоада мало отличалось от shotgun.
                      Поэтому я написал отдельный гем, который делает это с учетом специфики Grape — он делает кучу манки-патча Grape::API класса для dev-окружения.
                        0
                        Можете создать тикет с задваиванием роутов на github.com/padrino/padrino-framework и выложить пример сломанного проекта? Я разработчик Padrino::Reloader.
                          0
                          Может быть вы меня не совсем правильно поняли — задваиваются роуты именно в случае релоада классов, унаследованных от Grape. Это не ошибка Padrino::Reloader, так Grape устроен.
                      0
                      Для view, часто используют джем rabl в связке с Grape. Я как-то делал тестовый проект, с описанием api для puzzless.com
                      Использовал grape, rack, rabl, AR. [source]
                        0
                        Хорошее решение. Тоже были мысли использовать grape без рельсов. Воспользуюсь вашими наработками, если вы не против.)
                          0
                          Да, на то он и open source (:

                          А вообще, если есть уже готовое рельсовое приложение и нужно к нему написать API, то я бы не стал заморачиваться с Grape, Sinatra или еще чем-то, а делал бы этот API частью приложение.
                        –1
                        юзаю mcavage.me/node-restify/ — на nodejs
                          0
                          В рамках большого проекта restify очень уж неудобен. Express убивает всех зайцев, благо сейчас модулей выше крыши, от тех же strongloop. И если посмотреть исходники restify — там хаос, чего не встретишь в express. И все выше перечисленные плюсы в Grape из коробки есть в express: mounts, namespaces, middlewares, params!(Киллер фича express, теперь можно спокойно вешать на каждый параметр в роуте, свой middleware /hello/:_world). «Но это уже совсем другая история»©

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

                        Самое читаемое