A streaming layout service for front-end microservices

Bartłomiej Szajewski

Tailor is a layout service created by the Zalando team as a part of Project Mosaic which provides a set of libraries that allow frontend developers to decompose monolith applications in the way backend developers split systems into microservices. The libraries also define components interaction and support website scalability.

But what are the microservices?

Microservices is an approach to application architecture based on small, autonomous services that work together. Each of them is focused on doing one thing and each is deployed as an isolated entity. The biggest benefit is that each part of your system might use a different technology. It gives the team the freedom to choose the best stack for a project. The architecture is also more scalable than the monolith and allows you to independently release a service, so that it improves the resilience of your system, because when one of its components stops working, failure won’t cascade and the rest of the elements will still function.

Layout service

The most of modern web applications layouts consists of:

  • Header – with navigation
  • Footer – with some minor links
  • Main where we place a content.

Each of these fragments is treated by Mosaic as a separate service and the main role of Tailor – the layout service – is to compose a website out of these fragments.

In detail, Tailor is just a middleware that you may integrate with each of your Node.js servers. It fetches the template based on the path you specify and parse it to look for all fragments. Next, each of them is requested asynchronously and streamed to a website as soon as possible to avoid blocking of the page.

The main benefits of the library are:

  • server-side rendering
  • it uses streams on backend side
  • it allows you to provide a fallback in case of service failure
  • performance of your application depends on services

Tutorial

To start your journey with Tailor, let’s create a simple Node.js application that will fetch three basic services. Firstly, please create a new project directory and run these commands (with your command line):

npm init
npm i express node-tailor -S

It will create a package.json file with a two dependencies – express and node-tailor.

Now create the file structure:

- public
    - styles.css
- services
    - footer
        - public
            - styles.css
        - index.js
    - header
        - public
            - styles.css
        - index.js
    - main
        - public
            - styles.css
        - index.js
    - runService.js
    - index.js
- templates
    - index.html
- index.js

As you can see, we have three main directories:

  • public – contains global styles
  • services – contains fragments
  • templates – contains main template

Styles

Let’s first focus on styles. Paste the code below into proper files.

public/styles.css
  ------------------------------------------------------------
  body {
  padding: 0;
  margin: 0;
}

#header {
  grid-area: header;
}

#footer {
  grid-area: footer;
}

#main {
  grid-area: main;
  display: flex;
  align-items: center;
  justify-content: center;
}

#grid {
  display: grid;
  height: 100vh;
  grid-template-rows: auto 1fr auto;
  grid-template-areas: "header" "main" "footer";
  text-align: center;
  text-transform: uppercase;
}

services/footer/public/styles.css
  ------------------------------------------------------------
  #footer {
  padding: 10px 0;
  background: #ddd;
}

services/header/public/styles.css
  ------------------------------------------------------------
  #header {
  padding: 10px 0;
  background: #ddd;
}

services/main/public/styles.css
  ------------------------------------------------------------
  #main {
  background: #eee;
}

The grid layout has been defined in global styles. On the very top, header has been placed, footer on the very bottom and in between, the main fragment.The width and height of the layout always fits the browser size. The fragments are styled quite simplistically, mainly with background color.

Fragments

The next step is to create services. To follow a DRY principle, in runService.js we will create a method used in each of them. The method will create and run the localhost server, on a given port, which serves assets from public directory and returns passed HTML, whenever it’s requested.

services/runService.js
------------------------------------------------------------
const express = require('express');

module.exports =  (dir, html, port) => {
    const app = express(); // create express app
    app.use(express.static(dir + '/public')); // serve assets from public

    const css = `http://localhost:${port}/styles.css`; // url to styles.css from public
    const cssLink = `<${css}>; rel="stylesheet"`; // defined by Tailor way to link css file to html

    app.get('/', (req, res) => { // rest endpoint: get /
        res.writeHead(200, { // set headers
            Link: cssLink, // link css files
            'Content-Type': 'text/html' // content type of returned value
        });

        res.end(html); // returned value - html markup
    });

    app.listen(port, () => console.log(`App listening on port ${port}!`)); // run server on port

    return app; // return express app
};

In this module, we have created a basic express server. The only Tailor-specific thing is the ability to set a Link header with URLs to CSS and JavaScript resources. Once the service works correctly, they are appended in the place of the fragment, just like returned HTML.

Now, if we have already registered the service provider, we can create each fragment separately – served on proper port and with properly defined HTML response:

services/footer/index.js
------------------------------------------------------------
const runService = require('./../runService');

const port = 8083;
const html = `<footer id="footer">Footer</footer>`;

module.exports = runService(__dirname, html, port);


services/header/index.js
------------------------------------------------------------
const runService = require('./../runService');

const port = 8081;
const html = `<header id="header">Header</header>`;

module.exports = runService(__dirname, html, port);

services/main/index.js
------------------------------------------------------------
const runService = require('./../runService');

const port = 8082;
const html = `<main id="main">Main</main>`;

module.exports = runService(__dirname, html, port);

…and export all of them in services/index.js:

services/index.js
------------------------------------------------------------
const footer = require('./footer');
const header = require('./header');
const main = require('./main');

module.exports = {
    footer,
    header,
    main
};

Template

The next step is to create a template that will contain fragments. Tailor allows us to specify a tag for them, by default it is just fragment. In the process of parsing template by parse5, they are replaced with a stream from a server.

templates/index.html
------------------------------------------------------------


    <title>First Tailor app!</title>
    <link rel="stylesheet" href="./styles.css">


    <div id="grid">
      <fragment src="http://localhost:8081"></fragment>
      <fragment src="http://localhost:8082"></fragment>
      <fragment src="http://localhost:8083"></fragment>
    </div>

We linked global styles in head and wrapped all of the fragments by #grid – layout container. Each fragment tag has own source related to previously created services. You can specify more attributes e.g. fallback-src, which will be loaded if your src service fails. You can read about them here!

Tailor

In the last part, we will configure Tailor and create a Node server which will use it as a middleware.

index.js
------------------------------------------------------------
const express = require('express');
const Tailor = require('node-tailor');
require('./services');

const PORT = 8080;

const app = express(); // create express app
const tailor = new Tailor({ // create tailor with specific options
    templatesPath: __dirname + '/templates' // path to templates directory
});

app.use(express.static('public')); // serve global assets from public

app.get('/', (req, res) => { // rest endpoint: get /
    req.url = '/index'; // rewrite / to /index

    tailor.requestHandler(req, res); // pass req and res to tailors request handler
});

app.listen(PORT, () => console.log(`App listening on port ${PORT}!`)); // run server on port

By default, Tailor looks for the template by requested endpoint e.g. if you go to /books, it will look up for books.html template in templates directory. In our case, we rewrite the route from / to index, so whenever you go to http://localhost:8080, it will serve index.html.

First run

Ok, now we are ready to run our application. To do that, execute the command below in your command line:

node index.js

…and open the page, where you’ll see the expected layout:

http://localhost:8080

If you open your DevTools, you’ll see that all the fragments were converted into previously defined services responses with attached CSS links, so everything works fine. Great!

Congratulations you just wrote your first app using Tailor!

Summary

Tailor is a library that might be useful for your team to split your monolith frontend application into smaller services.This transformation proves to be useful, because each entity might use different technologies chosen by a team. In case you’d like to add a new feature, only one service needs to be modified instead of the whole monolithic repository. The deployment, release and scaling processes significantly improves! Services also are independent, so when one of them breaks, a website might still work fine, because e.g. you can always specify fallback in case of failure.

I hope you enjoyed it and maybe even some of you will try to use this in your project 🙂 Good luck and have fun!

Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!

Skontaktuj się z nami