Monday 13 February 2017

Microservices with Scala

I will build an application to save user’s favorites bookmarks. It will use REST microservices and have a simple web interface. This application will give you an idea of how microservices are implemented and exchange data between themselves and a user.

Each microservice should run in its own isolated environment and only deal with its business domain. It could be deployed independently of other microservices. However, for simplicity, my microservices will share the same database instance and be the part of the same code base. I will maintain the isolation: each microservice will have its own set of database tables, forbidden to others, and no shared tables. Code base will also have a package per microservice.

 

Technical details

  • Authentication is through OAuth2 token
  • Microservices communicate via HTTP with JSON
  • Backend: Scala, Akka HTTP, Akka Actors, MongoDB, SBT 
  • Frontend: AngularJS, Bootstrap, jQuery, Node JS 

 

Source

You can download the application from GitHub and give it a go.  This article will not provide a source code but may refer to the application source files. So, go ahead, download it, open the project and read on.

The GitHub page has the detailed information of what you need in order to run the application. I will not go over it again.

 

Backend

The backend is where microservices are implemented. Each microservice is running on a dedicated port as HTTP server. The backend source code shows how to use JSON with microservices, OAuth2 token and MongoDB. You can also take a look at the Specs for each microservice in the source files. Those are written with custom REST DSL, which should be concise and readable.

 

Overview

The domain model is rather simple and has three objects: Token, Profile and Bookmark. And the three microservices dealing with each of the domain objects respectively: Auth, Profiles and Bookmarks.
The above looks like the nanoservice anti-pattern, too fine-grained services whose overhead outweighs their utility. The application is just an example, but consider this: the Profiles microservice could be extended to provide additional information such as users' addresses, roles, back details, which could be pooled from third parties. As its complexity ramps up, it stops being a nanoservice.

 

Auth microservice

The starting point is the Auth microservice. It maintains users' tokens and credentials. When a user signs in, the Auth microservice provides the user with a unique token, which is then included by the user into each request as Authorization header. E.g. Authorization: Bearer 1234567890. This token will tell the microservices who the user is (authentication) and what it can do (authorization).

 

Profiles microservice

Next stop is the Profiles microservice. It holds users' accounts: username, first name, last name and email address. It talks to the Auth microservice and provides user's account data with a Profile ID (aka user's unique primary key).

 

Bookmarks microservice

Finally there is the Bookmarks microservice. It can create, read, update and delete (CRUD) users' bookmarks. It talks to the Profiles microservice to get a Profile ID. Profile ID is then used to distinguish one user from another when doing CRUD operations on users' bookmarks.

 

Microservices Structure

All my microservices follow the similar structure and workflow:
  1. A user sends a HTTP request to a microservice.
  2. The microservice accepts the request with its REST server part and authenticates the user by a token. It then calls an actor passing the following into it: request entity, user's profile, etc.
  3. The actor is doing parameters validation and user authorization. It also calls various database methods to form a response entity and replies it back to the REST server. 
  4. The REST server then sends the response back to the user.

 

OAuth2 token

Here is how a user successfully signs in:

 O
-|-                           [Auth MS]
/ \
 | POST (username, password)      |
 |------------------------------->| Find a user
 | 201 (token)                    |
 |<-------------------------------| Generate a token

A user sends its credentials to the Auth microservice in JSON format: {"username": "test", "password": "test"}. The microservice then looks up the user's details and compares the passwords. If the passwords match, a token is generated and saved for this user. The token is then sent back to the user, and it may look like "AABBCCDDEEFF".

From this point onward, the token should be included into request headers: "Authorization: Bearer AABBCCDDEEFF". Each request, the user makes, should have this header included. And the other microservices will validate the token by querying the Auth microservice. This will tell them who the user is by providing the token.

 

OAuth2 token in action

Here is another example, a user, who was previously successfully signed in, looks up its account details in the Profile microservice:
 O
-|-             [Profiles MS]           [Auth MS]
/ \
 | GET (token)       | GET (token)          |
 |------------------>|--------------------->| Find a user
 |                   | 200 (username)       |
 | 200 (account)     |<---------------------| Provide a username
 |<------------------| Find an account      |
 |                   | by a username        |

A user sends a request to the Profiles microservice. All it provides is a token acquired earlier in the request header: "Authorization: Bearer AABBCCDDEEFF". The Profiles microservice asks the Auth microservice who the user is, which in turn, looks up a username by the token from its database. Now the Profiles microservice knows the username and looks up the account details: {"profile_id": "5", "username": "test", "full_name": "Homer Simpson"}
The solution is far from perfect when a username is used to connect a token data with an account data. The Auth microservice has a token saved with a username and the Profiles microservice has an account data saved with a username, thus the additional effort is required when a user wants to change its username. But introducing a foreign key to connect those two pieces of data will just complicate things.

 

OAuth2 token and the Bookmarks microservice

The final diagram I want to show is when a signed in user tries to create a new Bookmark:
 O
-|-            [Bookmarks MS]      [Profiles MS]       [Auth MS]
/ \
 | GET (token, data) | GET (token)       | GET (token)     |
 |------------------>|------------------>|---------------->| Find
 |                   |                   | 200 (username)  |
 |                   | 200 (account)     |<----------------| Provide
 | 201 (bookmark)    |<------------------| Find an account |
 |<------------------| Create a bookmark |                 |
 |                   | with a Profile ID |                 |

A savvy reader may ask: why the Bookmarks microservice does not validate a token. It does not it query the Auth microservice because it relies on the Profiles microservice to do the validation. As the Bookmarks microservice needs a Profile ID so save bookmarks, it simply requests a Profile object from the Profiles microservice, which in turn will validate a token and provide an account data. In case if the token is invalid or not present, the Auth microservice will return 401 status code, which will be propagated to the Profiles microservice and then to the Bookmarks microservice, the user will get 401 status code in a response.
As a Profile ID is the cornerstone of CRUD operations on users' data and the Profile object is needed by all microservices, I created an authentication chain so that only the Profiles microservice validates tokens. In other words, when the Bookmarks microservice is requested, it will ask the Profiles microservice for a Profile object, which in turn will do the token validation. This saved us redundant HTTP calls and the latency involved.
As the above diagram pictures, a user sends a request to the Bookmarks microservice with the token in the request headers. The request entity looks like: {"url": "http://example.org", "rating": "7"}. The bookmarks microservice queries the Profiles microservice for a user's Profile object:  {"profile_id": "5", "username": "test", "full_name": "Homer Simpson"}. The resulting Bookmark object is then saved into the database. As I am using MongoDB, the Bookmark object looks like: {"id": "12", "profile_id": "5", "url": "http://example.org", "rating": "7"}

 

Room for Improvements

I omitted some of the vital token's actions: invalidating (sign out) and refreshing. Also there is no way to add a new user, new profile or modify an existing one. If you have followed the instructions on the GitHub page then all the test data should already be in your database.

Furthermore, inner-microservice communication is done with a user's token. There could be cases when a microservice A needs to invoke admin only URL of another microservice B. One solution could be to introduce a "system token" which both microservices know, and when a request comes with this special token, it can be trusted.

The OAuth2 token validation, which is a part of the Bookmarks and Profiles REST servers, should be done a bit differently. But in this example it is implemented as simple as possible.

And finally, my custom REST HTTP client, plus the DSL I am using in Specs, is based on Apache HTTP client because of its simplicity. I could not make Akka HTTP client to work properly.

 

Frontend

The frontend role is twofold: serve web content to browsers and act as a proxy to access the microservices.
Why proxy? As the savvy user, you may recall that microservices run on different ports and could reside on different hosts. But UI needs to talk to all of them. If you want to mess with CORS headers to bypass browser's restrictions as I did in the first place then be my guest. Fortunately, the proxy solution is very easy to implement thanks to this article.
My frontend is a proxy weblet:
  • Code which could reside anywhere
  • Salable horizontally
  • Abstracts microservices' hostnames and ports
  • Easier to write JavaScript to talk to the backend

 

UI Structure

The UI follows a default Angular application structure. I mixed in a Bootstrap theme with couple of plugins to make it visually appealing.

 

Authentication

First thing a user should do is to authenticate itself by providing credentials. The service acquires a token and stores it in a cookie. The token is then included in every HTTP request the user makes.

 

Managing Bookmarks

In the frontend, the Bookmark object exists as a JavaScript class to provide auxiliary functions: fetch a user owning the bookmark or to refresh itself. The service operating on bookmarks converts JSON to a proper class. The controller however, has its own JavaScript classes to create a new bookmark and to edit an existing one. The latter wraps a Bookmark class and performs actions on it.
It may sound like over-engineering. For this tiny example it may be true, but when an application grows larger, working with classes instead of plain JSON is a must have: auxiliary functions inside a class, encapsulation of properties etc.

 

Conclusion

In this article I tried to explain how to implement microservices and make them work with web based UI. The application I built has only rudimentary functions, just enough for a reader to grasp the technologies. I also built my application so it can be horizontally scalable, both the frontend and the backend.

1 comment: