/tailor

A streaming layout service for front-end microservices

Primary LanguageJavaScriptMIT LicenseMIT

Tailor

Build Status Test Coverage

Tailor is a layout service that uses streams to compose a web page from fragment services.

Why Layout Service?

Microservices get a lot of traction these days. They allow multiple teams to work independently from each other, choose their own technology stacks and establish their own release cycles. Unfortunately, frontend development hasn’t fully capitalized yet on the benefits that microservices offer. The common practice for building websites remains “the monolith”: a single frontend codebase that consumes multiple APIs.

What if we could have microservices on the frontend? This would allow frontend developers to work together with their backend counterparts on the same feature and independently deploy parts of the website — “fragments” such as Header, Product, and Footer. Bringing microservices to the frontend requires a layout service that composes a website out of fragments. Tailor was developed to solve this need.

Installation

npm i node-tailor --save

const http = require('http');
const Tailor = require('node-tailor');
const tailor = new Tailor({/* Options */});
const server = http.createServer(tailor.requestHandler);
server.listen(process.env.PORT || 8080);

Options

  • fetchContext(request) a function that returns a promise of the context, that is an object that maps fragment id to fragment url, to be able to override urls of the fragments on the page, defaults to Promise.resolve({})
  • fetchTemplate(request, parseTemplate) a function that should fetch the template, call parseTemplate and return a promise of the result. Useful to implement your own way to retrieve and cache the templates, e.g. from s3. Default implementation lib/fetch-template.js streams the template from the file system
  • fragmentTag a name of the fragment tag, defaults to fragment
  • handledTags an array of custom tags, check tests/handle-tag for more info
  • handleTag(request, tag) receives a tag or closing tag and serializes it to a string or returns a stream
  • requestFragment(url, fragmentAttributes, request) a function that returns a promise of request to a fragment server, check the default implementation in lib/request-fragment
  • amdLoaderUrl - URL to AMD loader. We use RequireJS from cdnjs as deafult.

Template

Tailor uses sax to parse the template, where it replaces each fragmentTag with a stream from the fragment server and handledTags with the result of handleTag function.

<html>
<head>
    <fragment src="http://assets.domain.com">
</head>
<body>
    <fragment src="http://header.domain.com">
    <fragment src="http://content.domain.com" primary>
    <fragment src="http://footer.domain.com" async>
</body>
</html>

Fragment attributes

  • id — optional unique identifier (autogenerated)
  • src — URL of the fragment
  • primary — denotes a fragment that sets the response code of the page
  • timeout - optional timeout of fragment in milliseconds (default is 3000)
  • async — postpones the fragment until the end of body tag
  • public — doesn't send the headers to fragment server
  • fallback-src - URL of the fallback fragment in case of timeout/error on the current fragment

Fragment server

A fragment is an http(s) server that renders only the part of the page and sets Link header to provide urls to CSS and JavaScript resources. Check example/fragment.js for the draft implementation.

A JavaScript of the fragment is an AMD module, that exports an init function, that will be called with DOM element of the fragment as an argument.

Events

Tailor extends EventEmitter, so you can subscribe to events with tailor.on('eventName', callback).

Events may be used for logging and monitoring. Check perf/benchmark.js for an example of getting metrics from Tailor.

Top level events

  • Client request received: start(request)
  • Response started (headers flushed and stream connected to output): response(request, status, headers)
  • Response ended (with the total size of response): end(request, contentSize)
  • Error: error(request, error) in case an error from template (parsing,fetching) and primary error(socket/timeout/50x)
  • Context Error: context:error(request, error) in case of an error fetching the context

Fragment events

  • Request start: fragment:start(request, fragment.attributes)
  • Response Start when headers received: fragment:response(request, fragment.attributes, status, headers)
  • Response End (with response size): fragment:end(request, fragment.attributes, contentSize)
  • Error: fragment:error(request, fragment.attributes, error) in case of socket error, timeout, 50x
  • Fallback: fragment:fallback(request, fragment.attributes, error) in case of timeout/error from the fragment if the fallback-src is specified

Note: fragment:response, fragment:fallback and fragment:error are mutually exclusive. fragment:end happens only in case of successful response.

Example

To start an example execute npm run example and open http://localhost:8080/index.

Benchmark

To start running benchmark execute npm run benchmark and wait for couple of seconds to see the results.