Content posted here with the permission of the author Shweta Kale who is currently employed at Josh Software. Original post available here.
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.
RestClientand 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'] end env end end end
And we inserted our middleware just after
Warden (Devise uses this internally for authentication)
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
env['rack.session']['warden.user.user.key'] 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.
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 🙂