Microservice-aware Web Application (MAWA)

Introduction (and TL;DR)

You want to build a website based on microservices.

Leonardo makes it easy to build the services and handle separation of concerns by routing messages to (sub)services exposed to the web browser.
Jo exposes these services as JavaScript objects to the webpage (an idea I’ve been toying with recently).
I call the result a Microservice-aware Web Application (MAWA), because the webpage accesses explicitly (is aware of) the different (micro)services made available by the server.

The purpose of this document is to show you how. The only software you need installed in your computer to try out what I show here is Docker.

I’m going to use some Jolie language [Montesi et al., 2014], in particular its web features [Montesi, 2016], because it makes things very simple for the purposes of this tutorial. You do not need to know Jolie to read what follows: the code is simple enough to be explained on the fly.

References

You can run the finished application with the following Docker commands.

Install the web server

We will use Leonardo to serve static content and to route service invocations from webpages to our services.

Write the main application

Now we write our main Jolie application, in file app/main.ol.

// This includes the LeonardoAdmin output port, which we'll use to configure Leonardo
include "internal/leonardo/ports/LeonardoAdmin.iol"
// Embedding is Jolie for running a service inside of the same VM
embedded {
Jolie:
/*
Run Leonardo and link it to the output port LeonardoAdmin
Communications over embedding use local memory (they're fast)
The Standalone=false parameter means that we'll have to configure Leonardo
*/
"-C Standalone=false internal/leonardo/cmd/leonardo/main.ol" in LeonardoAdmin
}
main
{
with( config ) {
.wwwDir = "../web" // The web content directory
};
config@LeonardoAdmin( config )(); // Send the configuration to Leonardo
linkIn( Shutdown ) // Wait until somebody terminates us
}

Make a run script

First, pull Jolie.

docker pull jolielang/jolie

Now we make a convenience script to run our website. Write this in run.sh in the root directory of your project.

Go to http://localhost:8080/ with your web browser. You should see the “Hello, World!” message we wrote in web/index.html.

Adding services

Let’s add some dynamic fun.
We can use Leonardo to proxy requests to both external and internal services. External services are services run outside of our application. Internal services are provided by our application. Other than that, their configuration in Leonardo is the same.

Our web server now exposes a ChuckNorris service that supports operation search.
Next, we update the web/index.html page to invoke this service.
We use Jo (a library to interact with Jolie web servers) to call the web server,
and jQuery to interact with the DOM (but you can use any other framework you like).

<html><head><script type="text/javascript"src="https://raw.githubusercontent.com/fmontesi/jo/master/lib/jo.js"></script><script
src="https://code.jquery.com/jquery-3.3.1.slim.min.js"integrity="sha256-3edrmyuQ0w65f8gfBsqowzjJe2iM6n0nKciPUp8y+7E="crossorigin="anonymous"></script></head><body><p><inputtype="text"id="jokeQuery"value="Computer"/><buttonid="getJokeBtn"type="button">Get me a Chuck joke!</button></p><pid="display"></p><script type="text/javascript">$(document).ready(()=>{$("#getJokeBtn").click(()=>{$("#display").html("Work in progress...");// Call operation search on the exposed service ChuckNorrisJo("ChuckNorris").search({query:$("#jokeQuery").val()}).then(response=>{// Pick a random joke// api.chucknorris.io returns jokes in a "result" array subelement$("#display").html((Array.isArray(response.result))?response.result[Math.floor(Math.random()*response.result.length)].value:"This is not a joke");}).catch(JoHelp.parseError).catch(alert);});});</script></body></html>

Internal services

What good is a joke if you can’t share it?
We write a Jolie service to post our jokes to https://telegra.ph/.
I’m going to put all the code in our app/main.ol. (In a real project, you’d probably want to start creating separate files at this point and embed them, just like we did with Leonardo.)

So now we have a new service TelegraphPoster that the webpage can invoke to post content on Telegraph. Let’s use it to share our jokes.

<html><head><script type="text/javascript"src="https://raw.githubusercontent.com/fmontesi/jo/master/lib/jo.js"></script><script
src="https://code.jquery.com/jquery-3.3.1.slim.min.js"integrity="sha256-3edrmyuQ0w65f8gfBsqowzjJe2iM6n0nKciPUp8y+7E="crossorigin="anonymous"></script></head><body><p><inputtype="text"id="jokeQuery"value="Computer"/><buttonid="getJokeBtn"type="button">Get me a Chuck joke!</button></p><pid="display"></p><p><buttonid="postJokeBtn"type="button">Share this wisdom</button></p><pid="telegraphDisplay"></p><script type="text/javascript">$(document).ready(()=>{$("#getJokeBtn").click(()=>{$("#display").html("Work in progress...");Jo("ChuckNorris").search({query:$("#jokeQuery").val()}).then(response=>{// Pick a random joke$("#display").html((Array.isArray(response.result))?response.result[Math.floor(Math.random()*response.result.length)].value:"This is not a joke");}).catch(JoHelp.parseError).catch(alert);});// Should be self-explanatory by now// When the button is clicked, invoke createPage at TelegraphPoster$("#postJokeBtn").click(()=>{$("#telegraphDisplay").html("Work in progress...");Jo("TelegraphPoster").createPage({title:"Chuck Norris Wisdom",content:$("#display").html()}).then(response=>{$("#telegraphDisplay").html(`Wisdom shared at <a href=\"${response.$}\" target=\"_new\">${response.$}</a>`);}).catch(JoHelp.parseError).catch(alert);});});</script></body></html>

Here’s a screenshot of what the final page looks like.

Done!

It’s done!

If you’d like to containerise your application, you just need a simple Dockerfile, like the following.