Project Mosaic — an Architecture for the Frontend Microservices

Andrey Kuzmin @unsoundscapes

Arpad Ryszka @arpad_ryszka

The Promise of Team Autonomy

  • teams decide on processes
  • teams decide on technology
  • teams decide on deployment and operation

The Reality of the Fashion Store aka Jimmy

  • takes 8 mins to compile, 4 mins to start
  • has more than 100 active contributors since 2015
  • changes from one team may affect another
  • slow release cycle
  • legacy frontend technologies

The Mission of The Taskforce

  • enable team autonomy in the Fashion Store
  • identify the architecture and implement core components
  • support the transition

The Mission of The Taskforce

  • enable team autonomy in the Fashion Store
  • identify the architecture and implement core components
  • support the transition
  • put Jimmy to REST!
What if we could have microservices on the frontend?

Fragment

  • independent web server
  • renders a part of the page
  • owns its JavaScript and CSS
  • needs to be composed

Composition Requirements

  • compose pre-rendered markup on the backend for SEO
  • ensure a fast time to first byte
  • prioritize the content above the fold
  • fault tolerance
  • enforce the same look and feel

Tailor

  • fetches the template, based on the request
  • parses the template for fragment placeholders
  • asynchronously calls all fragments
  • assembles fragment streams into a single output stream
  • sets response headers and streams the output

Page Template

<html>
<head>
  <fragment src="https://base-assets...">
</head>
<body>
  <fragment src="https://tracking..."
            timeout="200">
  <fragment src="https://header...">
  <fragment src="https://cart..."
            fallback-src="...">
  <fragment src="https://reco..." async>
  <fragment src="https://footer..." async>
</body>
</html>

Progressive Rendering

Time to First Byte

Fragment Attributes

  • id — optional unique identifier
  • src — URL of the fragment
  • primary — denotes a fragment that sets the response code of the page
  • timeout — optional timeout of fragment in milliseconds
  • async — postpones the fragment until the end of body tag
  • fallback-src — URL of the fallback fragment in case of timeout/error

Frontend Composition: Pipe

<script src=".../require-2.1.22.min.js"></script>
<script>
var Pipe=function(e,n){
  ...
}(window.document,window.performance);
_p613 = new Pipe(require);
</script>

Frontend Composition: Fragment

<link rel="stylesheet" href=".../client.css">
<script data-pipe>
_p613.start(2, "https://.../client.js")
</script>
...
<script data-pipe>
_p613.end(2, "https://.../client.js", "cart")
</script>

Frontend Composition: Async Fragment

<script data-pipe>_p613.placeholder(4)</script>
...
...
<script>_p613.loadCSS(".../client.css")</script>
<script data-pipe>
_p613.start(4, "https://.../client.js")
</script>
...
<script data-pipe>
_p613.end(4, "https://.../client.js", "footer")
</script>
How to enforce the same look and feel?

Base Assets Fragment

  • ES6 & fetch polyfills
  • Normalize.css and {box-sizing: border-box;}
  • Defines AMD modules (React, UI Components, Translation, Event Bus)

Composition Outcome

  • SEO friendliness preserved by backend composition
  • Fast time to first byte
  • Ability to prioritize the content above the fold
  • Errored fragments will not be rendered
  • Look and feel enforced through shared dependencies
How to transition from the monolith?

Transition

  • big bang switch impossible
  • step-by-step migration of feature sets
  • gradual ramp-up: measuring operation and business

Other Infrastructure Duties

  • combining old and new solutions
  • mapping the public web site to numerous internal services

Skipper: an HTTP Router

  • identifying routes
  • conditioning requests and responses
How does it work?

Routing Between Old and New

/acme-shoes -> https://tailor.zalan.do

*           -> https://jimmy.zalan.do

Gradual Ramp Up of Features

30% of /acme-shoes -> https://tailor.zalan.do

*                  -> https://jimmy.zalan.do

Combining Old and New

30% of /acme-shoes
-> customer-cookie
-> https://tailor.zalan.do

*
-> customer-cookie
-> https://jimmy.zalan.do

Mapping One to Many

30% of /acme-shoes
-> customer-cookie
-> zalando-stuff
-> template-path
-> https://tailor.zalan.do

POST /search
-> zalando-stuff
-> https://search.zalan.do

*
-> customer-cookie
-> https://jimmy.zalan.do

Reasoning About Routing

30% of /acme-shoes
-> customer-cookie
-> zalando-stuff
-> template-path
-> https://tailor.zalan.do

POST /search
-> zalando-stuff
-> https://search.zalan.do

*
-> customer-cookie
-> https://jimmy.zalan.do

Reasoning About Routing: eskip

mosaicCatalog: Path("/acme-shoes") && Traffic(0.33)
  -> customerCookie()
  -> xalando()
  -> modPath(".*", "/catalog")
  -> "https://tailor.zalan.do";

mosaicApi: Path("/search") && Method("POST")
  -> xalando()
  -> "https://search.zalan.do";

// everything else goes this way
jimmyCatchall: *
  -> customerCookie()
  -> "https://jimmy.zalan.do";

Reasoning About Routing: eskip

  • "Skipper is a runtime for eskip"
  • self documenting
  • reviews
  • syntax check

Scaling The Routing Table

  • custom CMS pages and promotions: ~ 10k - 100k
  • teams interacting with the routing configuration: ~ 15 - 200?
  • continuously changing settings

Configuration Without Downtime

  • etcd (https://github.com/coreos/etcd)
  • awesome, but etcd doesn't know us
  • need to avoid stealing each other's routes

Support Teamwork: Innkeeper

  • organization specific rules for routing
  • OAuth2 based rights management
  • Rest API and supplementary tooling

Bonus: Other Skipper Use Cases

  • authentication proxy
  • compression proxy
  • static file server
  • network throttling and diagnostics

Outcome: a Flexible Router

  • Open for Individual Creativity
  • freely composable predicates and filters
  • simple but powerful declarative language for route configuration (eskip)
  • custom logic through clear extension points (in Go)
  • on-the-fly reconfiguration
The Mosaic Architecture

Recap On Team Autonomy

  • teams decide on processes
  • teams decide on technology
  • teams decide on deployment and operation

Open Source & Links