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
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.