Back to Home

Grape: not with single rails, part 2

ruby · grape · api

Grape: not with single rails, part 2

    In my first post about Grape, I quickly skipped over its main features, for which I was deservedly bombarded with reproaches for the excessive compression of the material. According to the results of the publication of the last post, I promised a practical example of the application of the framework, as well as comparative benchmarks.

    Today we will begin to create an example - we will develop an API that can:
    - register a user
    - activate it by email
    - update the data of an authorized user
    - return an authorized user profile




    TL; DR
    gem install grape-gen
    

    To be honest, I was a little cunning - we won’t write anything.
    After that post, I thought for a while - what should I do? I just didn’t really want to spend time writing a spherical example in a vacuum - I wanted, as they say, "to eat a fish and not to choke on a bone."
    So I spent a little time and put together the gem generator of a full-featured API application on grape, naming it plainly - grape-gen .

    "Skeleton" applications, batteries included


    I will not torment you with long stories about what a modern web application needs, except for the framework itself.
    The out of the box generator offers:
    - ORM (so far only mongoid)
    - authorization (Warden + CanCanCan)
    - background tasks (Sidekiq)
    - real-time messages (Faye)
    - integration with ElasticSearch (Tire)
    - file downloads (Carrierwave)
    - sending email via Mandrill (MandrillMailer)
    - a customized test environment with guard + spork and a gem for documenting APIs using tests.

    All of this is glued together and ready to go.
    Redis is highly recommended, at least for faye and sidekiq.

    After gem install grape-gen in the shell, the eponymous command will become available. To generate the application, we perform:
    $ grape-gen app your_app_name
    

    The generator provides the ability to disable certain components, here is a help to the command
    $ 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
    

    About grape-rackbuilder, turned on by default - this is my crutch for autoload files and reload code in a dev environment. Not everything is smooth in it, and the code there is terrible, but it copes with most of the functions assigned to it, in the near future I will patch it.

    About EventMachine


    In the comments on the first post, stalkerg expressed the idea that “without asynchronous programming, such a framework makes little sense.”
    I believe that there is some truth in this, so out of the box the application is designed to run under an EventMachine-based server - Thin or Goliath.
    This means that all the “batteries” used in the application during preparation are prepared to replace the blocking IO with asynchronous analogues from EventMachine, and for Carrierwave image processing is sent to the thread-pool via EventMachine.defer (yes, we have GIL, but even this In the same amount of time, we process twice as many images while giving the event loop “sigh” twice as often - I checked with tests).
    Well, em-synchrony, of course. Each request is executed in our own fiber via Rack :: FiberPool, so there is no callback-hell.

    API provided by the application


    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
    

    The API gives us JSON, which is generated using JBuilder and serialized through MultiJson + oj.

    Application configuration


    Connections to redis, elasticsearch, third-party API keys are configured in the config / application.yml file.
    Database connections are configured in config / database.yml
    . Logging
    settings in config / logging.yml. Sidekiq settings in config / sidekiq.yml

    Trial run


    After we made sure that our configs are correct, it is time to launch our newly made application:
    $ 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
    

    After the processes start successfully, a page with a primitive faye client subscribed to the / user / registered and / time channels will be available at http: // localhost: 9292 / faye. Messages to the / time channel are sent by the Sidekiq-task scheduled to start every 5 seconds. Thus, every 5 seconds a line with the server time will be added to the page. After user registration and confirmation of his email , a message with his display_name is added to the / user / registered channel , upon receipt of which the browser will add a line with a proposal to greet the new user.





    Test environment


    The test environment is based on RSpec 3 gems, rspec_api_documentation. In the kit many FactoryGirl, DatabaseCleaner and Faker are loved by many.

    It is launched under Guard + Spork, plus in the test environment, as in development, code reloading is used, which allows you to run tests quickly enough.

    We should also say about the rspec_api_documentation gem - it allows you to combine the process of writing tests and generating API documentation.
    I used Swagger before, but unfortunately it is more suitable for canonical REST APIs. If your API is more JSON-style RPC, then it will be difficult for you to fit your API into a Swagger descriptive structure, and documentation of the API response structure is available only for grape-entity. The above gem using its DSL on top of RSpec allows you to document the API using examples: you describe a test example (for example, valid user registration), when you run this example, it remembers the request sent to the server, the response received, the request url and generates documentation from this information. It is also possible to specify a description of the parameters and. etc.
    Here is an example:
    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 => "[email protected]")
          status.should == 201
        end
        example "Creating an account - errors", :document => [:private, :developers] do
          do_request
          status.should == 422
        end
      end
    end
    

    What's next?

    I think that I provided the basic information needed to quickly create my API application in this post.
    The gems used in the project are well documented. If you think that I missed something important - write in the comments or PM, I will definitely add.
    For the next post I will try to prepare more or less objective benchmarks of this application.
    So far I can say that on the i5 2500K one application instance (one thread) processes ~ 700 requests per second to POST / api / auth / register with the data of an existing user.
    Also, plans are to add JRuby support on the Goliath server (JIT is really good there) and http and in-app caching.

    Read Next