Rebuilding the company website using microservices and layer-7 load balancing

来源:互联网 时间:1970-01-01

A few weeks ago we released a brand new version with our new branding, an updated look and clearer messaging. We also added the ability to book a Shutl delivery right from the homepage as we work to consolidate our various web offerings.

We decided to follow a microservices pattern, instead of building one monolithic application, meaning that although visitors would see one site on a single domain with a consistent look and feel, behind the curtain there are serveral applications running.

One service handlesthe core functionalities of the website, like the overview, the retailers and carriers sections, “the contact us” section etc. For feedback and ratings pages, the HTML is served by a different service (Feedback service), and the same for the tracking pages which are provided by Tracking service.All services are Rails applications that are configured to use the Unicorn application server, withNginx acting asa layer 7 load balancer.

When you have a website structured like ours,the way you render pages might need a little bit of more work and thinking compared to a monolithic application. In the next paragraphs I’ll talk about two approaches to handlepage rendering in a microservices based website.

Achieve 100% decoupling

Althoughmany services means having many pages spread across different applications, it’s fundamental to keep the users under the same domainwhen they access different website pages.We want to basicallygive the users theillusion of accessing a single web application, while keeping all the single services completely decoupled.

A common way to achieve that, which we also applyat Shutl, is to leverage Nginx as a reverse proxy to forward the request to the right service, based on the request url.

The configuration belowshows how Nginx can be configured to achieve what I just described. That configuration is very close to what weuse in production to render to reviews page (

Nginx configuration


upstream frontend_app { server unix:/opt/shutl/unicorn/.unicorn.sock fail_timeout=30;}upstream feedback_service { server fail_timeout=300;}server { listen 80 default_server; server_name; root /opt/frontend_app/public; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Host $host; location /reviews { proxy_pass http://feedback_service; } location @app { proxy_pass http://frontend_app; }} upstreamfrontend_app{ serverunix:/opt/shutl/unicorn/.unicorn.sockfail_timeout=30;} upstreamfeedback_service{;} server{ listen80default_server;; root/opt/frontend_app/public; proxy_set_headerX-Forwarded-For$remote_addr; proxy_set_headerHost$host; location/reviews{ proxy_passhttp://feedback_service; } [email protected]{ proxy_passhttp://frontend_app; }}

The lines 17-19are the most relevant ones, needed to instruct Nginx so that if a request url is‘’,the request is forwarded to the URL given by the proxy_pass directive, that in our case is the feedback_service upstream (lines 5-7). That means that the Nginx listening to the port 80 of the server will receive a request like the following: feedback_service:80/reviews. Feedback service will then handle the requestand respond withthe html pagecontaining the reviews that the user willeventually enjoy at


Some pages are composed with content from several services. For example, our homepage shows customer reviews, streamed live and unedited. In this case, one application is serving the overall page, but it fetches HTML snippets from another – Feedback service.To create this section, weadded an endpoint to the Feedback service API that returns a HTML fragment containing the latest reviews. That snippet will be fetched and renderedwhenever the overview page is showed to the user.

Let’s dig into some code and see how that has been implemented.

Here’s the Rails route file:



Rails.application.routes.draw do root 'site_pages#show', file_name: 'overview' get ':file_name', to: 'site_pages#show'end Rails.application.routes.drawdo root'site_pages#show',file_name:'overview' get':file_name',to:'site_pages#show'end

The routes file shows that when arequest to the root page is received, the file_name parameter will beset to ‘overview’, and the request will behandled by the show action of a controller called SitePagesController, that will look like the one below:



class SitePagesController < ApplicationController helper :reviews def show template = File.join(params[:controller], params[:file_name]) render template endend classSitePagesController<ApplicationController helper:reviews defshow template=File.join(params[:controller],params[:file_name]) rendertemplate endend

It’s a very concise and simple controller. It assigns the template’s file path (site_pages/overview) to the template variable, which it’ll be used to render the desired Haml template.

The template will contain, among other information, the recent reviews which willbe fetched from the Feedback service by a helper, so not to clutter the controller too much and keep a nice separation of concerns.

The helper will look something like:



module ReviewsHelper def render_latest_reviews response = fetch_from_feedback_service('/latest_reviews') response.body.force_encoding('UTF-8').html_safe end private def fetch_from_feedback_service(url) connection.get do |req| req.url url end end def connection '') endend moduleReviewsHelper defrender_latest_reviews response=fetch_from_feedback_service('/latest_reviews') response.body.force_encoding('UTF-8').html_safe end private deffetch_from_feedback_service(url) connection.getdo|req| req.urlurl end end defconnection'') endend

This example is simplified – in reality we also want to handle error responses, timeouts and other exceptions.What the helper does should be quite clear to all of you. The method render_latest_reviews will call the fetch_from_feedback_service method to fetch the HTML snippet containing the latest reviews, that will be embedded in the Overview page.

The only missing piece is the overviewtemplate that will just call the render_latest_feedbacks method (available inthe ReviewsHelper) toshow the latest reviews:



%div %span What our customers are saying%div = render_latest_feedbacks %div %spanWhatourcustomersaresaying %div =render_latest_feedbacks

The approach just described can be appliedwhenever you need to embed a chunk of information (coming from a different service), in different pages of the same website,across several applications, or in theemails that you will send to the customers as part of a marketing campaign.

In the past we have used a more traditional approach – receive the data needed from the external service as JSON, and render that into HTML in the calling application. We prefer receiving the HTML from the external service as it keeps the display logic for the application within that application, meaning it can be easily changed. This is preferable to having duplicate logic in several places, in different applications, for rendering the same data.We share assets between applications to keep the styling and user experience consistent.

We’re proud of the new site and had a good time building it; we gave ourselves a deadlineand were ruthless with scope to make that happen. We’re happy with the result – a great looking site that allows us to iterate and test new ideas quickly. Let us know what you think!