GoLang with Rails

GoLang with Rails? Wondering why one would use GoLang with Rails?

Read on to find out!!

This is purely based on our requirement but can surely benefit others looking forward to similar use-case. We had a web app written in Rails but facing performance bottleneck while processing large chunk of data. The natural choice seem to use power of GoLang concurrency.

In order to use GoLang with our Rails app few approaches came to my mind. But I found one or the other flaw:

  • Write api’s in GoLang APP and route request from nginx based on request URL. Simple but for using this approach we would also need to add authentication in GoLang app. So authentication will be Rails as well as in GoLang – This doesn’t seem correct, because if I had to change authentication mechanism, would need to make changes in two apps.

  • Use RestClient and call GoLang apis from Rails application. So request will be routed to Rails app and it will call api from GoLang app and serve response. Here I will achieve some level of performance but again my Rails app will have to serve request which GoLang app can directly serve and the response has to wait for response from GoLang app.

  • Use FFI. Using FFI we can call GoLang binary directly. You can watch this video to see how it can be done. This seems fine at first, but what if I had to load balance moving GoLang app to other server?

So which approach did I follow?

We went with NONE of the above, but a 4th idea using rack_proxy gem.

Here is sample code for middleware we wrote

class EventServiceProxy < Rack::Proxy
  def initialize(app)
    @app = app
  end

  def call(env)
    original_host = env["HTTP_HOST"]
    rewrite_env(env)
    if env["HTTP_HOST"] != original_host
      perform_request(env)
    else
      @app.call(env)
    end
  end

  def rewrite_env(env)
    request = Rack::Request.new(env)

    if request.path.match('/events')
      if env['warden'].authenticated?
        env["HTTP_HOST"] = "localhost:8000"
        env['HTTP_AUTHORIZATION'] = env['rack.session']['warden.user.user.key'][0]
      end

      env
    end
  end
end


And we inserted our middleware just after Warden (Devise uses this internally for authentication)

config.middleware.insert_after(Warden::Manager, EventServiceProxy)

In above code snippet we are just proxing our request to localhost:8000 where GoLang App is running and setting up user_id in header. Warden adds authenticated user_id in env['rack.session']['warden.user.user.key'][0] so now we know who is logged in at GoLang App from header.

We added middleware in GoLang which extracts user_id from header and sets curretUser details in context.

Important Note
Our GoLang application is exposed only to Rails application and not to the whole world so we are sending user_id in header.

The main advantages we saw using this approach are:

  • We could use existing authentication mechanism used in Rails application
  • If needed we can add load balancer to our Rails and/or GoLang application which is micro service.
  • If we have used FFI we had to put binary on same machine but here we can have application and GoLang service on different machines.
  • As request will be rewritten from Rack it saved redirect and going through entire stack of rails app.

This could be used with any framework similar to Rails.

By using above approach now we can use power of GoLang when needed and development speed of Rails 🙂

Advertisements

One thought on “GoLang with Rails

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s