© Cloves Carneiro Jr. and Tim Schmelmer 2016

Cloves Carneiro Jr. and Tim Schmelmer, Microservices From Day One, 10.1007/978-1-4842-1937-9_12

12. Single-Client Application—Mobile and Web

Cloves CarneiroJr. and Tim Schmelmer2

(1)Hollywood, Florida, USA

(2)Broomfield, Colorado, USA

Your microservice-powered application will ultimately be displayed to actual people using a web browser or mobile devices; we call those customer facing applications. In order to function well, those applications need to make calling each service they depend on a trivial task. We know that many developers have been exposed only to the ideas and concepts behind the microservices architecture, and still have very little actual exposure to how all the pieces fit together in practice.

In this chapter, we share our book sample project; it has working code that resembles a real-life scenario, so that you can run all the services in this project and see a web application running. Our project implementation is far from complete, as that's not the goal of this book; we only have enough pieces in place to show how everything connects. We will focus on explaining the main building blocks of a few pages of the sample project, and explain the components that make each part of some pages work. We will show code in Ruby and Rust, which are the two languages we are comfortable with and can fairly easily use to showcase interaction between a web application and a few services. Even if you are not familiar with those languages, we recommend you follow along with this chapter as it will put into practice a lot of the concepts we have described in the book so far.

Application Overview

Before we get started, take a look at the architecture of what we are building in this chapter. We'll be implementing much of the application example described throughout the book in the following services:

The diagram in Figure 12-1 makes it easy to visualize that our customers will be using their browsers to point to the web server that is running the showcase app, which will then make requests to books-service, purchases-service and reviews-service in order to render web pages to our users.

A394450_1_En_12_Fig1_HTML.jpg
Figure 12-1. Architecture of the sample project
Note

You can—and should—follow along by loading the code available from the URLs shown. Each service has a Readme file with details on how to set up each application, and we also provide a repository with more documentation at https://github.com/microservices-from-day-one/docs .

It is time to put all that we've talked about in this book together, and demonstrate a working web application in a microservices environment. The sample application for this book is a bookstore, which has a set of fairly standard features that we will describe and dissect in this chapter. We'll talk about each of the application 's pages, and will explain all service calls that happen in order to get each page rendered. The sample application is not fully functional, as it would probably require a team of full-time developers to implement and maintain such an application; however, we decided to implement enough functionality that will showcase a lot of the topics covered in this book.

Home Page

The home page for the bookstore is a fairly standard e-commerce landing page, as you can see in Figure 12-2.

A394450_1_En_12_Fig2_HTML.jpg
Figure 12-2. Home page of our bookstore

Before we go into the details of a few elements of the home page, we want to bring to your attention the client library we are using to connect to our services. You probably remember from Chapters 5 and 6 that we decided to use the JSON API standard to describe all our responses, which would keep our services interfaces and responses similar across the whole system. We build a client library in Ruby—known as a Ruby gem—to interface with our services. The payoff for using a standard starts to appear, as we were able to build our own library on top of an open source JSON API compliant library called json_api_client( https://github.com/chingor13/json_api_client ).

In our client library, named api_client and available at https://github.com/microservices-from-day-one/api-client , you can see that we have defined one module per service, so we'll have a module called ApiClient::Books for books-service, another called ApiClient::Purchases for purchases-service, and so on. Each module is very simple and leverages the json_api_client gem; they also define a Base class, which is responsible for having code that will be shared by all models. The Base class for ApiClient::Books is listed here:

module ApiClient
  module Books


    class Base < JsonApiClient::Resource
      def self.site
        @site ||= "#{ServiceDiscovery.url_for(:books)}/v1"
      end
    end


  end
end


require 'api_client/books/book'
require 'api_client/books/category'

That class is responsible for mainly two things, defining where the endpoint for books-service is to be found, as implemented in the site class method, and defining a base resource, which inherits from JsonApiClient::Resource; therefore all the subclasses of this class don't really need to know that detail of the implementation.

With the Base class in the client library explained, we'll now show how a specific resource is defined. Our example is the ApiClient::Books::Book class, which represents the book model as served by books-service; the class is defined here:

module ApiClient
  module Books


    class Book < Base
    end


  end
end

As you can see, there is really no code associated with this class; we are able to define a standard resource, which has a set of well-known method available that match to a RESTful set of CRUD endpoints—again, one of the advantages of following a standard. So our client library is, as we expected, a very thin layer that allows client applications to reach the service endpoints we wrote.

Now it's time to look at how some of the elements of this page have come together. An element that is a constant feature in most e-commerce applications is our shopping cart widget, which shows us the current value of the cart, as well as the number of elements in it. All details related to purchases are owned by purchases-service, which also owns all cart details. When any user accesses the bookstore, this service tries to find a cart associated with the user, which is saved in the session:

def current_cart_id
  if session[:cart_id].nil?
    cart = ApiClient::Purchases::Cart.create
    session[:cart_id] = cart.cart_id
  end
  session[:cart_id]
end
helper_method :current_cart_id

As you can see in this code, the application will load the card_id associated with the user from the session. For new users that do not have an associated cart_id, a new cart will be generated in purchases-service via the ApiClient::Purchases::Cart.create call. Again, we will take a look at the implementation in purchases-service, available at https://github.com/microservices-from-day-one/purchases , which is a Ruby service. In Ruby, we have an open-source gem called jsonapi-resources ( https://github.com/cerebris/jsonapi-resources ), which is described as “a resource-focused Rails library for developing JSON API compliant servers.” By following the JSON API standard, and defining a set of resources that define the models to be exposed in the API, this gem makes it very easy to create JSON API-complaint services. In purchases-service, the Cart resource is defined as follows:

module V1
  class CartResource < JSONAPI::Resource
    attributes :cart_id, :expires_at, :total, :items_count
  end
end

The cart’s endpoints are defined in the V1::CartsController class, defined as:

namespace :v1 do
  jsonapi_resources :carts
end

in routes.rb and implemented as:

module V1
  class CartsController < JSONAPI::ResourceController
  end
end

That is all that we need in order to render the shopping cart widget. It currently shows the number of items in the cart and its value as 0; however, that will change when we implement the endpoints and code to add books to the cart.

Now it's time to look into how some of the elements of this page have come together. The main block in the home page displays four featured books that are presented to the user. We will start with a very simple implementation, which is a service call that will return the six most recent books in books-service. The code that makes that service call is

def home_products(limit: 8)
  @home_products ||= ApiClient::Books::Book.paginate(page: 1, per_page: limit).to_a
end

When this code is executed, the following request comes to books-service:

GET http://localhost:5000/v1/books?page%5Bnumber%5D=1&page%5Bsize%5D=6`

It returns

{
  "data": [
    {
      "attributes": {
        "author": "Mo Willems",
        "cover_image": "https://images-na.ssl-images-amazon.com/images/I/61jurA6wsFL._SX258_BO1,204,203,200_.jpg",
        "description": "Diva, a small yet brave dog, and Flea, a curious streetwise cat, develop an unexpected friendship in this unforgettable tale of discovery.",
        "isbn": "978-1484722848",
        "pages": 80,
        "price": 987,
        "slug": "the-story-of-diva-and-flea",
        "title": "The Story of Diva and Flea"
      },
      "id": "2497fe7d-4797-4506-a3cd-85e42d90415b",
      "type": "books"
    },
    {
      "attributes": {
        "author": "Charles C. Mann",
        "cover_image": "https://upload.wikimedia.org/wikipedia/en/b/b7/1491-cover.jpg",
        "description": "In this groundbreaking work of science, history, and archaeology, Charles C. Mann radically alters our understanding of the Americas before the arrival of Columbus in 1492.",
        "isbn": "978-1400032051",
        "pages": 541,
        "price": 1299,
        "slug": "1491-new-revelations",
        "title": "1491: New Revelations of the Americas Before Columbus"
      },
      "id": "2d831aba-75bb-48b6-bae6-9f1bb613d29b",
      "type": "books"
    },
    {
      "attributes": {
        "author": "Jane Austen",
        "cover_image": "https://themodernmanuscript.files.wordpress.com/2013/01/pride-and-prejudice-1946.jpg",
        "description": "Pride and Prejudice is a novel of manners by Jane Austen, first published in 1813. The story follows the main character, Elizabeth Bennet, as she deals with issues of manners, upbringing, morality and education.",
        "isbn": "978-1484110980",
        "pages": 279,
        "price": 951,
        "slug": "pride-and-prejudice",
        "title": "Pride and Prejudice"
      },
      "id": "12892302-7ef4-4023-9999-2b3560a2a4d6",
      "type": "books"
    },
    {
      "attributes": {
        "author": "James Dean ",
        "cover_image": "https://i.harperapps.com/covers/9780062303899/x300.png",
        "description": "Pete the Cat is going scuba diving! Before he hits the water, Captain Joe tells him about all the sea creatures he can encounter, and Pete is super excited to see a seahorse.",
        "isbn": "978-0062303882",
        "price": 319,
        "slug": "pete-the-cat-scuba-cat",
        "title": "Pete the Cat: Scuba-Cat"
      },
      "id": "8217e21a-97db-4be9-9feb-4cea97a64948",
      "type": "books"
    },
    {
      "attributes": {
        "author": "Eric Litwin",
        "cover_image": "https://i.harperapps.com/covers/9780061906220/x300.png",
        "description": "Pete the Cat goes walking down the street wearing his brand new white shoes. Along the way , his shoes change from white to red to blue to brown to WET as he steps in piles of strawberries, blueberries, and other big messes.",
        "isbn": "978-0061906237",
        "pages": 40,
        "price": 1139,
        "slug": "pete-the-cat-i-love-my-white-shoes",
        "title": "Pete the Cat: I Love My White Shoes"
      },
      "id": "008672a6-c48c-49b2-98e8-b78914f2fce4",
      "type": "books"
    },
    {
      "attributes": {
        "author": "Eric Litwin",
        "cover_image": "https://i.harperapps.com/covers/9780062110589/x300.png",
        "description": "Pete the Cat is wearing his favorite shirt—the one with the four totally groovy buttons. But when one falls off, does Pete cry? Goodness, no! He just keeps on singing his song—after all, what could be groovier than three groovy buttons?",
        "isbn": "978-0062110589",
        "pages": 40,
        "price": 1175,
        "slug": "pete-the-cat-and-his-four-groovy-buttons",
        "title": "Pete the Cat and His Four Groovy Buttons"
      },
      "id": "20ee1ef4-6000-460a-b715-40da50979fc7",
      "type": "books"
    },
    {
      "attributes": {
        "author": "James Dean",
        "cover_image": "https://i.harperapps.com/covers/9780062304186/x300.png",
        "description": "Pete the Cat takes on the classic favorite children's song "Five Little Pumpkins" in New York Times bestselling author James Dean's Pete the Cat: Five Little Pumpkins. Join Pete as he rocks out to this cool adaptation of the classic Halloween song!",
        "isbn": "978-0062304186",
        "pages": 32,
        "price": 791,
        "slug": "pete-the-cat-five-little-pumpkins",
        "title": "Pete the Cat: Five Little Pumpkins"
      },
      "id": "25f2db6f-966a-4c24-9047-1fdfeb43e465",
      "type": "books"
    }
  ]
}
```

With those products retrieve d, the code in the template that renders the page will pick four random books out of those six, so that users see different books in subsequent requests:

```
<% for product in home_products(limit: 6).shuffle.first(4) do %>
  <%= render partial: 'home/product', locals: {product: product} %>
<% end %>

We’ve started with a very simple service call; however, as the site develops a wider range of products and starts to collect user behavior data, this is an area that could benefit from the existence of user recommendations, which could be implemented in a recommendations service, and be based on an algorithm that takes into consideration the user's purchase habits, along with the purchase habits of similar users, to maximize sales opportunities.

Featured Categories

In the second half of the home page, we have the Featured Categories block, which is a section used to feature a couple of categories the business is interested in focusing on. In the showcase application, we make a call to the featured_categories endpoint in books-service, available at /v1/categories/featured, which is responsible for knowing the logic behind deciding how a category gets featured; in the browser, that call looks like Figure 12-3.

A394450_1_En_12_Fig3_HTML.jpg
Figure 12-3. Featured categories block in home page
def featured_categories
  @featured_categories ||= ApiClient::Books::Category.featured.first(2)
end

The Category resource in the service returns a JSON API-compliant resource, with a response that looks like this:

{
  "data": [
    {
      "attributes": {
        "description": "Something for the little ones.",
        "image": "http://sleepingshouldbeeasy.com/wp-content/uploads/2013/01/childrens-books-about-going-to-school-vertical.jpg",
        "name": "Childrens Books",
        "slug": "children-s-books"
      },
      "id": "20a05be7-3a9b-4ed0-a36b-ed36b0504557",
      "type": "categories"
    },
    {
      "attributes": {
        "description": "Learn about your past.",
        "image": "http://media1.s-nbcnews.com/ij.aspx?404;http://sys06-media.s-nbcnews.com:80/j/streams/2013/November/131120/2D9747505-Where_Were_You_book_jacket.blocks_desktop_vertical_tease.jpg",
        "name": "History",
        "slug": "history"
      },
      "id": "cc7f1963-23fc-485c-89ec-d1efb25966d1",
      "type": "categories"
    },
    {
      "attributes": {
        "description": "It is promise and hope. Titillations and excitations.",
        "image": "https://s-media-cache-ak0.pinimg.com/564x/29/bd/9e/29bd9eafd49a185874e706fb4f896ba0.jpg",
        "name": "Romance",
        "slug": "romance"
      },
      "id": "c85102c8-3ed2-4af9-be8b-f1008448f6cb",
      "type": "categories"
    }
  ]
}

In the service code, the featured endpoint has been implemented as a response that returns categories in random order, so that all categories are featured equally. Of course, in a production setup, we'd want to implement featured_categories based on business logic, probably based on featuring-best selling categories. In the described architecture, changes to the algorithm of how categories become featured are just an implementation detail of books-service and will have zero impact on the rest of the system, given that the API interface remains unchanged. Here's the current implementation of the featured_categories endpoint, implemented in Rust, and as we discussed in Chapter 11.

pub fn featured(req: &mut Request) -> IronResult<Response> {
    let pool = req.get::<Read<::AppDb>>().unwrap();
    let conn = pool.get().unwrap();


    let mut categories: Vec<Category> = Vec::new();
    for row in &conn.query("SELECT category_id, name, description, slug, image FROM categories ORDER BY random()", &[]).unwrap() {
        let category = Category::from_row(row);
        categories.push(category);
    }


    let response = jsonapi::json_collection_wrapped_in("data", categories);
    Ok(Response::with((status::Ok,
                       Header(jsonapi::jsonapi_content_type()),
                       response)))
}

One last thing to mention about this element is that it is probably the most “cacheable” element of the home page, meaning that it's very unlikely we will want featured categories to change frequently throughout the day; thus, we can use client-side caching to cache the featured categories block for a specific period of time (usually to be decided by the business), and not even have to make the service call on most requests, which also makes the application more performant and resilient to service failures. In this case, caching the response for 4 hours seems acceptable ; so we could surround the client-side code with these lines:

<% cache 'featured_categories', expires_in: 4.hours do %>
  <%= render partial: 'home/offers' %>
<% end %>

Users Should Never See Internal Errors

Now that you have seen some code, and we have the home page being rendered by loading data from a few services, we'll look into how we want to treat service errors in this user-facing application. We believe strongly that users should never see server errors; developers and designers should think about the user experience to be presented to the user when there's something wrong with the application. Based on the particular use case, treating errors will take a unique shape, as there are cases that you can easily recover from, and others not so much.

The rule of thumb is that you need to catch errors that happen when interacting with the server, give your users a fall-back, and try to degrade gracefully in all areas; but degrading means different things based on the situation. In some cases, you can make it look like there is no error at all, and in others, you still have to display a “something went wrong” message, which is still more user-friendly than a 500 server error.

We will start by looking at the shopping cart widget, which we described a few pages ago, and will see what happens to the application when that service is not available—down. To simulate purchases-service being down, we can just Ctrl-C that process and wait until we are sure it's no longer running. After doing that, just refresh your browser, and you will see that the home page greets you with an ugly ConnectionError error (see Figure 12-4).

A394450_1_En_12_Fig4_HTML.jpg
Figure 12-4. Error when loading the home page when dependencies are not available

The shopping cart widget gives us an insight into what is currently in the cart, and also works as a link to the cart page. In this case, we could treat that error to make sure that we still render the widget, without the number of items in the cart nor the current cost. That fall-back seems to be acceptable, since it should only happen during down times of that one service. We've changed the helper method that loads the shopping cart object by adding error-handling code, and we also made sure we logged the error that is happening and left a TODO comment inline to alert us that something is wrong. In Chapter 14, we talk about some options for alerting this type of issue. The code ends by returning a nil object:

def shopping_cart
  @shopping_cart ||= ApiClient::Purchases::Cart.find(current_cart_id).first
rescue JsonApiClient::Errors::ConnectionError => e
  Rails.logger.error("Error loading shopping_cart: e=#{e.message}")
  # TODO: alert that an error is happening
  nil
end

We can check for a nil object in the template that renders the shopping cart information:

<% if shopping_cart %>
  <div class="total">
    <span class="simpleCart_total"><%= render_price(shopping_cart.total) %></span>
    (<span id="quantity" class="quantity"><%= shopping_cart.items_count %></span>)
  </div>
<% end %>

Figures 12-5 and 12-6 are screenshots of the shopping cart widget first when it's working as expected and then under the error case, which may not even be noticeable to the user.

A394450_1_En_12_Fig5_HTML.jpg
Figure 12-5. Shopping cart widget in functional state
A394450_1_En_12_Fig6_HTML.jpg
Figure 12-6. Shopping cart widget when purchases-service is down

We demonstrated an error case that had a simple solution that didn't impact the page at lot; we could easily degrade gracefully. However, there are cases where that is not possible. For example, if the user were in the book detail page, and the books-service happened to be nonresponsive, you'd have to return a page with an error message that asks the user to “try again in a few minutes.”

Circuit Breakers

In most cases, the type of error handling code we’ve just explained is enough to help one application handle failure from a downstream dependency; however, in environments with a very high number of requests, it's possible that having a client-facing application keep making requests to a service when it knows it's down isn't a good solution, for two reasons:

  • The calling application will be slightly delayed, as it may have to wait for its timeout to realize that a dependency is unavailable, which may slow other systems.

  • The struggling server takes longer to respond, or come back to life, if it keeps receiving requests that it cannot handle, compounding the existing problem.

For that type of situation, the calling application should use the circuit breaker pattern we introduced in Chapter 6. It is a mechanism that will make the calling application try to request a resource from a service even when it notices that the service has not been responsive. Circuit breakers are usually set up by defining a failure threshold in the calling application that when met will cause the circuit to “open” for a predetermined period of time, causing the calling application to go into error handling mode without even calling the downstream dependency, because it already knows that it has been struggling to properly serve requests. This spares us from the two unwanted scenarios we described earlier and makes a system even more resilient in the face of an abnormal situation.

Book Detail Page

The next page we will look at is the book detail page, shown in Figure 12-7, which is probably the most important page in the application; it's where users will make a purchase decision. Here is some of the information a user would expect to see on the book page:

  • bibliographic information such as title, author, cover image, description, ISBN, price, page count and publisher name

  • reviews of books

    • other people's review,

    • ability to review yourself

  • add to a wish list

A394450_1_En_12_Fig7_HTML.jpg
Figure 12-7. Screenshot of book detail page

The initial set of attributes we need to load are what we could call book metadata, and are owned by books-service; the client helper will load data from that service, by using the book “slug,” which is a unique SEO-friendly string used in the book URL, such as http://localhost:5004/products/pete-the-cat-i-love-my-white-shoes.

def book_details
  @book ||= ApiClient::Books::Book.where(slug: params[:id]).first
end

The code above will make this service call:

GET http://localhost:5000/v1/books?filter%5Bslug%5D=pete-the-cat-i-love-my-white-shoes

The call in turn returns:

{
  "data": [
    {
      "attributes": {
        "author": "Eric Litwin",
        "cover_image": "https://i.harperapps.com/covers/9780061906220/x300.png",
        "description": "Pete the Cat goes walking down the street wearing his brand new white shoes. Along the way, his shoes change from white to red to blue to brown to WET as he steps in piles of strawberries, blueberries, and other big messes.",
        "isbn": "978-0061906237",
        "price": 1139,
        "slug": "pete-the-cat-i-love-my-white-shoes",
        "title": "Pete the Cat: I Love My White Shoes"
      },
      "id": "008672a6-c48c-49b2-98e8-b78914f2fce4",
      "type": "books"
    }
  ]
}

As mentioned previously, our ApiClient library knows the JSON API standard well, and transforms that response into an object we can easily use in the template code, such as this:

<div class="single-para ">
  <h4><%= book_details.title %></h4>
  <h5 class="item_price"><%= render_price(book_details.price) %></h5>
  <p class="para"><%= book_details.description %></p>
  <div class="prdt-info-grid">
    <ul>
      <li>- ISBN : <%= book_details.isbn %></li>
      <li>- Author : <%= book_details.author %></li>
    </ul>
  </div>

We hope this is starting to feel easier to follow; we'll follow exactly the same pattern to make service calls to load review information from reviews-service, which follows the same pattern of loading the book data.

The last element we'll talk about in the book detail page is the “related books” block, shown in Figure 12-8, which is a set of links to books that are somehow related to the book featured in this page. Much like the home page's Featured Books block, this is an element that will benefit from having its own endpoint, so that the business can tweak the rules for selecting those elements without that affecting any work on the front-end client application. The first step in implementing this is to define an endpoint in books-service that asks for books related to a specific book. We settle for an endpoint in the following format:

A394450_1_En_12_Fig8_HTML.jpg
Figure 12-8. The related books block in the book detail page
GET /v1/books/2d831aba-75bb-48b6-bae6-9f1bb613d29b/related

In this example, the UUID (2d831aba-75bb-48b6-bae6-9f1bb613d29b) is the ID of the book for which we want to receive “related books.” For the sake of this example, we decided on using a very straightforward implementation that loads five random books that are not the book_id passed in the URL. Here's the implementation in Rust:

pub fn related(req: &mut Request) -> IronResult<Response> {
    let book_id = iron_helpers::extract_param_from_path(req, "book_id");
    let book_uuid = Uuid::parse_str(&book_id).unwrap();


    let pool = req.get::<Read<::AppDb>>().unwrap();
    let conn = pool.get().unwrap();


    let sql = "SELECT book_id, title, author, description, isbn, price, pages,
        slug, cover_image FROM books WHERE book_id != $1 ORDER BY random() LIMIT 5";


    let mut books: Vec<Book> = Vec::new();
    for row in &conn.query(&sql, &[&book_uuid]).unwrap() {
        let book = Book::from_row(row);
        books.push(book);
    }


    let response = jsonapi::json_collection_wrapped_in("data", books);
    Ok(Response::with((status::Ok,
                       Header(jsonapi::jsonapi_content_type()),
                       response)))
}

When called, the preceding returns the following response:

{
  "data": [
    {
      "attributes": {
        "author": "James Dean",
        "cover_image": "https://i.harperapps.com/covers/9780062304186/x300.png",
        "description": "Pete the Cat takes on the classic favorite children's song "Five Little Pumpkins" in New York Times bestselling author James Dean's Pete the Cat: Five Little Pumpkins. Join Pete as he rocks out to this cool adaptation of the classic Halloween song!",
        "isbn": "978-0062304186",
        "pages": 100,
        "price": 791,
        "slug": "pete-the-cat-five-little-pumpkins",
        "title": "Pete the Cat: Five Little Pumpkins"
      },
      "id": "25f2db6f-966a-4c24-9047-1fdfeb43e465",
      "type": "books"
    },
    {
      "attributes": {
        "author": "Eric Litwin",
        "cover_image": "https://i.harperapps.com/covers/9780062110589/x300.png",
        "description": "Pete the Cat is wearing his favorite shirt—the one with the four totally groovy buttons. But when one falls off, does Pete cry? Goodness, no! He just keeps on singing his song—after all, what could be groovier than three groovy buttons?",
        "isbn": "978-0062110589",
        "pages": 100,
        "price": 1175,
        "slug": "pete-the-cat-and-his-four-groovy-buttons",
        "title": "Pete the Cat and His Four Groovy Buttons"
      },
      "id": "20ee1ef4-6000-460a-b715-40da50979fc7",
      "type": "books"
    },
    {
      "attributes": {
        "author": "Charles C. Mann",
        "cover_image": "https://upload.wikimedia.org/wikipedia/en/b/b7/1491-cover.jpg",
        "description": "In this groundbreaking work of science, history, and archaeology, Charles C. Mann radically alters our understanding of the Americas before the arrival of Columbus in 1492.",
        "isbn": "978-1400032051",
        "pages": 100,
        "price": 1299,
        "slug": "1491-new-revelations",
        "title": "1491: New Revelations of the Americas Before Columbus"
      },
      "id": "2d831aba-75bb-48b6-bae6-9f1bb613d29b",
      "type": "books"
    },
    {
      "attributes": {
        "author": "Eric Litwin",
        "cover_image": "https://i.harperapps.com/covers/9780061906220/x300.png",
        "description": "Pete the Cat goes walking down the street wearing his brand new white shoes. Along the way, his shoes change from white to red to blue to brown to WET as he steps in piles of strawberries, blueberries, and other big messes.",
        "isbn": "978-0061906237",
        "pages": 100,
        "price": 1139,
        "slug": "pete-the-cat-i-love-my-white-shoes",
        "title": "Pete the Cat: I Love My White Shoes"
      },
      "id": "008672a6-c48c-49b2-98e8-b78914f2fce4",
      "type": "books"
    },
    {
      "attributes": {
        "author": "Mo Willems",
        "cover_image": "https://images-na.ssl-images-amazon.com/images/I/61jurA6wsFL._SX258_BO1,204,203,200_.jpg",
        "description": "Diva, a small yet brave dog, and Flea, a curious streetwise cat, develop an unexpected friendship in this unforgettable tale of discovery.",
        "isbn": "978-1484722848",
        "pages": 100,
        "price": 987,
        "slug": "the-story-of-diva-and-flea",
        "title": "The Story of Diva and Flea"
      },
      "id": "2497fe7d-4797-4506-a3cd-85e42d90415b",
      "type": "books"
    }
  ]
}

With everything implemented server-side, we have to make a tweak to the api_client library so that we can make calls to that endpoint. The json_api_client gem makes it ridiculously easy to give ourselves access to that endpoint; we just need to define a custom_endpoint in the Book class:

module ApiClient
  module Books


    class Book < Base
      custom_endpoint :related, on: :member, request_method: :get
    end


  end
end

Turning our attention back to the front-end application, we render those five related books in a separate template, known as a partial, called related:

<%= render "related", books: book_details.related %>

The partial looks like this:

<% for book in books do %>
  <div class="col-md-2 btm-grid">
    <a href="<%= book_path(book.slug) %>">
      <%= image_tag book.cover_image, alt: book.title, style: "width: 120px" %>
      <h4><%= book.title %></h4>
      <span><%= render_price(book.price) %></span>
    </a>
  </div>
<% end %>

The Shopping Cart Page

We've talked about the shopping cart widget , available in all pages in the application, but now let’s turn our attention to the shopping cart page, shown in Figure 12-9, which is where the user is taken when clicking in the shopping cart widget. As expected, the shopping cart detail page has not only the summary about number of items and total price in the cart, but also information about each of the items the user has added to the cart. In our example, we have two books in the shopping cart.

A394450_1_En_12_Fig9_HTML.jpg
Figure 12-9. Shopping cart page

In purchases-service, shopping carts are modeled as multiple entities. A Cart is the shopping cart main model, it has attributes such as items_count, total, user_id, and expires_at, and an Item has attributes such as item_id, quantity, and price. In order to display the shopping cart details, we need to have access to each line item in the cart; this is unlike the shopping cart widget, which only needed basic cart information. We've decided to use a single endpoint to return cart information, but return a richer response. This is what we have referred to as different representations of the same data in responses.

The following service call:

GET http://localhost:5001/v1/carts/35c082ba-b974-4d78-9526-4fc6ed7b54a2

returns this:

{
  "data": {
    "id": "35c082ba-b974-4d78-9526-4fc6ed7b54a2",
    "type": "carts",
    "links": {
      "self": "http://localhost:5001/v1/carts/35c082ba-b974-4d78-9526-4fc6ed7b54a2"
    },
    "attributes": {
      "cart_id": "35c082ba-b974-4d78-9526-4fc6ed7b54a2",
      "expires_at": "2016-10-23T01:54:29.408Z",
      "total": 1618,
      "items_count": 2
    },
    "relationships": {
      "items": {
        "links": {
          "self": "http://localhost:5001/v1/carts/35c082ba-b974-4d78-9526-4fc6ed7b54a2/relationships/items",
          "related": "http://localhost:5001/v1/carts/35c082ba-b974-4d78-9526-4fc6ed7b54a2/items"
        }
      }
    }
  }
}

For the cart page , we pass an extra parameter, include=items, to that same API call; it looks like this:

GET localhost:5001/v1/carts/35c082ba-b974-4d78-9526-4fc6ed7b54a2?include=items

It returns

{
  "data": {
    "id": "35c082ba-b974-4d78-9526-4fc6ed7b54a2",
    "type": "carts",
    "links": {
      "self": "http://localhost:5001/v1/carts/35c082ba-b974-4d78-9526-4fc6ed7b54a2"
    },
    "attributes": {
      "cart_id": "35c082ba-b974-4d78-9526-4fc6ed7b54a2",
      "expires_at": "2016-10-23T01:54:29.408Z",
      "total": 1618,
      "items_count": 2
    },
    "relationships": {
      "items": {
        "links": {
          "self": "http://localhost:5001/v1/carts/35c082ba-b974-4d78-9526-4fc6ed7b54a2/relationships/items",
          "related": "http://localhost:5001/v1/carts/35c082ba-b974-4d78-9526-4fc6ed7b54a2/items"
        },
        "data": [
          {
            "type": "items",
            "id": "2d831aba-75bb-48b6-bae6-9f1bb613d29b"
          },
          {
            "type": "items",
            "id": "8217e21a-97db-4be9-9feb-4cea97a64948"
          }
        ]
      }
    }
  },
  "included": [
    {
      "id": "2d831aba-75bb-48b6-bae6-9f1bb613d29b",
      "type": "items",
      "links": {
        "self": "http://localhost:5001/v1/items/2d831aba-75bb-48b6-bae6-9f1bb613d29b"
      },
      "attributes": {
        "item_id": "2d831aba-75bb-48b6-bae6-9f1bb613d29b",
        "quantity": 1,
        "price": 1299
      }
    },
    {
      "id": "8217e21a-97db-4be9-9feb-4cea97a64948",
      "type": "items",
      "links": {
        "self": "http://localhost:5001/v1/items/8217e21a-97db-4be9-9feb-4cea97a64948"
      },
      "attributes": {
        "item_id": "8217e21a-97db-4be9-9feb-4cea97a64948",
        "quantity": 1,
        "price": 319
      }
    }
  ]
}

As you can see, the second response is much richer; it contains information about the two items currently included in the cart, so we'll definitely use that in our cart page. The main advantage of this approach is that we can use the same endpoint in both use cases—cart widget and cart page—but still have a faster response time in the case where we need less data, avoiding sending unnecessary data over the wire and making fewer database calls to provide a response in the service, thus improving overall system performance.

Back to the showcase application, where we define a new helper method to load that information.

An interesting feature supported by the JSON API specification is called sparse fieldsets ( http://jsonapi.org/format/#fetching-sparse-fieldsets ), which allow a client to request that an endpoint return only specific fields in the response on a per-type basis by including a fields parameter. Sparse fieldsets can be useful in responses that may return a large number of attributes by default, where clients may gain performance by specifying only the attributes they are interested in. For example, if we had code that needed only the current value in dollars of a cart, we could use the client gem to load only that attribute by calling ApiClient::Purchases::Cart.select(:total).find(..), which would make the following service call:

GET http://localhost:5001/v1/carts/35c082ba-b974-4d78-9526-4fc6ed7b54a2?fields[carts]=total

and would generate this response:

{
  "data": {
    "id": "35c082ba-b974-4d78-9526-4fc6ed7b54a2",
    "type": "carts",
    "links": {
      "self": "http://localhost:5001/v1/carts/35c082ba-b974-4d78-9526-4fc6ed7b54a2"
    },
    "attributes": {
      "total": 1618
    }
  }
}

Exercises for You

As we mentioned at the beginning of the chapter, we've just scratched the surface of functionality that can be built for our bookstore, and we have some ideas about what could be done next. We'll list a couple of services that you could spend some time implementing, if that sounds like something interesting to you. One of the great things about it is that you can use whatever programming language/environment you're most comfortable with.

An obviously missing piece is a user service, responsible for storing and managing data associated with the users of the bookstore. A user should be able to, among many things, register an account and log in, save books to a wish list, “like” book categories, see his or her own—and others'—wish lists, and follow other users. Based on those quick requirements, some of the endpoints we would want to see implemented are:

  • User CRUD

    • name, email, password (hash), ID, books_in_wishlist, categories

  • Add liked category: [user_token, category_id] -> [user_id, category_id]

  • Remove liked category: [user_token, category_id] -> 204

  • Get all liked categories: [user_token] -> [category_id1, category_id2, …]

  • Add to wish list: [user_token, book_id] -> [user_id, book_id]

  • Remove from wish list: [user_token, book_id] -> 204

  • Get all books on wish list: [user_token] -> [book_id1, book_id2, …]

  • Follow a user: [user_token, user_id] -> [user_id, user_id]

  • Unfollow a user: [user_token, user_id] -> 204

  • Get users I followed: [user_token] -> [user1, user2, ...]

  • Get users that follow me: [user_token] -> [user1, user2, ...]

We also mentioned that the home page would eventually evolve and benefit a lot from showing books that match the logged-in user; a recommendations service would be a very welcome addition to our microservices architecture. That service would probably feed from user-service, since it will have quite a bit of information about users, as well as purchases-service, which has data about what books have been purchased, or are currently in the cart. To make its API very simple, the service could have a single endpoint that, given a user_id, returns a book_id list as recommendations.

Service Orchestration

You probably have noticed that a lot of the service-related code in the showcase front-end application is actually simple, because it has to make fairly simple service calls in many places; you can imagine that this can get complicated, and some service calls depend on the results of other calls, which can introduce complexities at the front-end layer that may be better suited elsewhere. To help simplify some of that complexity, we recommend using a Back-end for Front-end (BFF) service, briefly introduced in Chapter 7, which would be a service that knows exactly what data the front-end needs to render its pages and would be responsible for fetching that data from many different clients. A practical example of that is the home page, which makes multiple service calls to many services. The code in showcase could be simplified if it could make a call to a showcase-service, which would be responsible for orchestrating calls to multiple services, and properly deal with error conditions there. Figure 12-10 diagrams this approach.

A394450_1_En_12_Fig10_HTML.jpg
Figure 12-10. Architecture of application with showcase-service in place

We have described most of the service interactions of this chapter with the web front-end in mind, but most applications have multiple client-facing applications from the start nowadays, which would be mobile iOS and Android applications. In the case of mobile applications, we really recommend the use of a BFF—let's call it mobile-service, because your product and service will evolve; and unlike a web application that has only one version deployed at any time, you will always have a population of users who will not upgrade your application to the latest and will get stuck making calls to APIs that you may want to change, and even deprecate.

Performance Considerations

We have deliberately mentioned performance in this chapter only briefly; we touched on some aspects that will make your application faster, such as sparse fieldsets and using a cache to reduce the number of service calls. We believe that all services should be fairly fast, and that performance will only be a serious problem when you start having slow service responses; so we recommend that you keep an eye on each service’s response times to easily identify which service is slow so that you can fix it. A microservices architecture is not an excuse for having poor performance. We believe that it is actually the opposite; if you have very specific performance requirements, you can build services that will be responsible for reaching those numbers. We will go into detail about monitoring in the next chapter, so you can find out which dependencies are slow or slower, and how to get alerted .

A very efficient technique for speeding up calls to distributed services is to make parallel service calls whenever possible. For example, we know that on the book page, we'd need to make calls to load the book details, the list of categories, and the shopping cart; we could make those three service calls at the same time, and our application's overall response time would benefit as a whole. Some programming languages make it easy—or hard—to work with threads, so your programming environment of choice may make this trivial—or not.

Summary

In this chapter, we dove into our sample project; that is, a bookstore with a client-facing web application that makes requests to a set of four services in order to display book information, and has a simple but working shopping cart. We went into detail showing code in multiple layers to give you a sense of how all the pieces fit together, and left a set of possible enhancements as exercises for the more adventurous. We hope we gave you some extra reasons to choose a standard for your APIs, as we've demonstrated a few nifty features included in the JSON API specification.

In the next chapter, we will describe the what and how of service monitoring, to help you figure out when your services are misbehaving.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.0.53