Top Banner
THE RECIPE FOR SCALABLE FRONTENDS DAN PERSA & MAXIMILIAN FELLNER 10-11-2017 MILAN
66

Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

Jan 21, 2018

Download

Technology

Codemotion
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

THE RECIPE FOR

SCALABLE

FRONTENDS

DAN PERSA & MAXIMILIAN FELLNER

10-11-2017

MILAN

Page 2: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

2

DAN PERSA

Engineering Lead

@danpersa

[email protected]

Page 3: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

3

MAXIMILIAN FELLNER

Software Engineer

@mxfellner

[email protected]

Page 4: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

EUROPE’S LEADING ONLINE FASHION PLATFORM

15 countries

21+ million active customers

~3.6 billion € revenue 2016

200+ million visits per month

13.000+ employees in Europe

1.600 tech employees

Visit us: tech.zalando.com

Page 5: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

5

ZALANDO FASHION STORE

Page 6: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

6

THE RECIPE

FOR

SCALABLE

FRONTENDS

Page 7: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

SCALING

THE

TECH TEAM

SCALING

THE

ARCHITECTURE

Page 8: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

SCALING THE TECH TEAM

ATTRACT NEW,

TALENTED PEOPLEKEEP THE TEAMS HAPPY

ENCOURAGE

INNOVATIONCREATE DIVERSITY

Page 9: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017
Page 10: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

500+

Apps

~1600

Tech employees

2016

2016 2017

Page 11: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017
Page 12: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

SCALING

THE

TECH TEAM

SCALING

THE

ARCHITECTURE

Page 13: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

13

Conway’s Law

“organizations which design systems

...are constrained to produce

designs which are copies of the

communication structures of these

organizations”

Page 14: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

MICROSERVICES

TEAM AUTONOMY

INDEPENDENT RELEASE CYCLES

MIX DIFFERENT TECH STACKS

EASY A/B TESTING

SCALING THE ARCHITECTURE

Page 15: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

15

TEAMS OWN BACKEND APIS

Page 16: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

MICROSERVICES

ON THE

FRONTEND?

Page 17: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

17

APIS ARE USED BY A FRONTEND MONOLITH

Page 18: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

18

WEBAPP GETS CONTRIBUTIONS

FROM MULTIPLE TEAMS

Page 19: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

WORK AUTONOMOUSLY

MIX OF DIFFERENT TECH STACKS

INDEPENDENT RELEASE CYCLES

Page 20: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

20

MOSAIC

www.mosaic9.org

Page 21: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

21

TEAMS OWN FRAGMENTS

Page 22: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

Translation ServiceTeam Pathfinder

IAM APITeam GreendaleFRAGMENT

Your Team API

Your Team

From Tailor

HTML Render

AJAX APIs

Internal API Client

From Skipper

Cart ServiceTeam COAST

Page 23: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

23

FRAGMENTS USE THE BACKEND APIS

Page 24: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

24

LAYOUT SERVICE

Page 25: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

25

ASSEMBLED CONTENT IS STREAMED TO THE CLIENT

Page 26: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

26

MOSAIC COMPONENTS

JIMMY

Page 27: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

27

SKIPPER

Forwards requests to different

endpoints based on request properties:

Host, Path, Method

Cookies, etc.

Streams content from the endpoints

Runtime updates of routing table

github.com/zalando/skipper

Page 28: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

28

Tailor is a layout service that

uses streams to compose a web

page from fragment services.

Loads content of all fragments

from the template in parallel.

Offers nice error handling and

fallback features.

github.com/zalando/tailor

Page 29: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

29

TEMPLATE

<html><head>

<fragment src="http://assets.domain.com"></fragment></head><body>

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

</body></html>

Page 30: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

HEADER

CART

TAILORlayout service CART FRAGMENT

Team COAST

HEADER FRAGMENTTeam Navigation

QUILTtemplate management API

CART TEMPLATE

TRACKINGTRACKING

FRAGMENT

Team TRCKNG

https://cart.coast.zalan.do

https://eb-fragment.trckng.zalan.do

https://header-fragment-release.navigation.zalan.do

From Skipper

https://zalando.de/cart

Page 31: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

31

FRAGMENT JAVASCRIPT

FRAGMENT SKIPPERrouter

TAILORlayout service

CLIENT

STATIC HTML

<button>click me</button>

LINK HEADERS

<script.js>; rel="fragment-script"<style.css>; rel="stylesheet"

AMD MODULE JAVASCRIPT

define([], () => element => {element.onclick = () =>

alert('Hello, World!')})

Page 32: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

32

FRAGMENT JAVASCRIPT

FRAGMENT SKIPPERrouter

TAILORlayout service

CLIENT

STATIC HTML

<button>click me</button>

LINK HEADERS

<script.js>; rel="fragment-script"<style.css>; rel="stylesheet"

AMD MODULE JAVASCRIPT

define([], () => element => {element.onclick = () =>

alert('Hello, World!')})

Page 33: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

33

FRAGMENT COMMUNICATION

FRAGMENT A

bus.trigger('cart:add', {sku: 'ABZ123'

});

EVENT BUSexternal library

FRAGMENT B

bus.on('cart:add', args => {const { sku } = args;

});

publish &

subscribe

github.com/grassator/happened

Page 34: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

34

HOW IT LOOKS

Header Fragment

Cart Fragment

Tracking Fragment

*Not every fragment has to be visible

Page 35: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

SCALING

THE

TECH TEAM

SCALING

THE

ARCHITECTURE

Page 36: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

MOSAIC

IT’S LIVE!

Page 37: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

SCALING

THE

TECH TEAM

SCALING

THE

ARCHITECTURE

SCALING

THE

CONTENT

SCALING

THE

TECH TEAM

SCALING

THE

ARCHITECTURE

SCALING

THE

CONTENT

Page 38: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

SKIPPERrouter

TAILORlayout service

FRAGMENTS

Page 39: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

39

FRAGMENT ARCHITECTURE

FRAGMENT SKIPPERrouter

TAILORlayout service

CLIENT

?MODERN, INTERACTIVE USER EXPERIENCE

DYNAMIC CONTENT

CONSISTENT LOOK & FEEL EVERYWHERE

Page 40: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

40

FRAGMENT ARCHITECTURE

FRAGMENT

<div><button id="btn">click me

</button><script>$('btn').click(() =>alert('Hello!')

)</script>

</div>

DOESN'T SCALE

Too manual

Inconsistent

Only static content

Page 41: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

41

FRAGMENT ARCHITECTURE

FRAGMENT

let x = parseRequest(req)

let data = await fetch(x)

let html = template(data)

res.write(html)

BETTER

HTML templates

External content data

Dynamic responses

DOESN’T SCALE

Still inconsistent

JavaScript is 2nd class

Page 42: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

42

FRAGMENT ARCHITECTURE

FRAGMENTSOLUTION

Reusable, shared components

Isomorphic/universal code

SSR: JavaScript → HTML

*

* generic universal component framework

Page 43: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

43

CONTENT .HTML

.JS

SKIPPERrouter

TAILORlayout service

CLIENT

DESCRIBE JAVASCRIPT COMPONENTS WITH JSON

Page 44: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

44

DESCRIBE JAVASCRIPT COMPONENTS WITH JSON

type: divprops: nullchildren:- type: h1props: nullchildren:- Hello, World!

- type: aprops:href: https://www.zalando.de

children:- Zalando Fashion

*

* rendered as YAML for readability

Page 45: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

45

LAYOUT .HTML

.JS

SKIPPERrouter

TAILORlayout service

CLIENT

GENERATE CODE AND HTML

USE COMMON COMPONENT LIBRARIES

Page 46: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

46

GENERATE CODE AND HTML

<div><h1>Hello, World!</h1><a href=”https://www.zalando.de”>Zalando Fashion

</a></div>

React.createElement(‘div’, null,React.createElement(‘h1’, null,‘Hello, World!’

),React.createElement(‘a’, {

href: ‘https://www.zalando.de’},‘Zalando Fashion’

));

JSON → JavaScriptgenerateCode()

JavaScript → HTMLrenderToString()

Page 47: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

47

LAYOUT .HTML

.JS

SKIPPERrouter

TAILORlayout service

CLIENT

GENERATE CODE AND HTML

UNIVERSAL

JAVASCRIPT RUNS

ON THE CLIENT TOO!

Page 48: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

48

tessellateverb tes·sel·late \ˈte-sə-ˌlāt\

to form into or adorn with mosaic

github.com/zalando-incubator/tessellate

Page 49: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

49

TESSELLATE: TRANSFORM JSON INTO JAVASCRIPT

.JSON .JSTESSELLATE

bundler

CDNstatic server

1. parse JSON AST

2. generate JavaScript code

3. compile webpack bundles

4. export static files

Page 50: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

50

TESSELLATE: TRANSFORM JSON INTO JAVASCRIPT

import React from 'react';import ReactDOM from 'react-dom';import Foo from 'foo';import { ComponentA, ComponentB } from 'bar';

export const Root = props => {return (

<div id="root">{React.createElement(

Foo, null,React.createElement(ComponentA, null, 'Hello, World!'),React.createElement(ComponentB, {

href: 'https://www.zalando.de'})

)}</div>

);};

Page 51: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

51

import React from 'react';import ReactDOM from 'react-dom';import Foo from 'foo';import { ComponentA, ComponentB } from 'bar';

export const Root = props => {return (

<div id="root">{React.createElement(

Foo, null,React.createElement(ComponentA, null, 'Hello, World!'),React.createElement(ComponentB, {

href: 'https://www.zalando.de'})

)}</div>

);};

TESSELLATE: TRANSFORM JSON INTO JAVASCRIPT

type: foo.default

props: null

children:

- type: bar.ComponentA

props: null

- type: bar.ComponentB

props:

href: ‘https://…’

Page 52: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

52

import React from 'react';import ReactDOM from 'react-dom';import Foo from 'foo';import { ComponentA, ComponentB } from 'bar';

export const Root = props => {return (

<div id="root">{React.createElement(

Foo, null,React.createElement(ComponentA, null, 'Hello, World!'),React.createElement(ComponentB, {

href: 'https://www.zalando.de'})

)}</div>

);};

TESSELLATE: TRANSFORM JSON INTO JAVASCRIPT

type: foo.default

props: null

children:

- type: bar.ComponentA

props: null

- type: bar.ComponentB

props:

href: ‘https://…’

Page 53: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

53

import React from 'react';import ReactDOM from 'react-dom';import Foo from 'foo';import { ComponentA, ComponentB } from 'bar';

export const Root = props => {const bundledProps = { myValue: 'https://www.zalando.de' };const mergedProps = Object.assign({}, bundledProps, props);return (

<div id="root" data-props={JSON.stringify(props)}>{React.createElement(

Foo, null,React.createElement(ComponentA, null, 'Hello, World!'),React.createElement(ComponentB, {

href: jsonPtr.get(mergedProps, '#/myValue')})

)}</div>

);};

TESSELLATE: TRANSFORM JSON INTO JAVASCRIPT

type: foo.default

props: null

children:

- type: bar.ComponentA

props: null

- type: bar.ComponentB

props:

href:

$ref: #/myValue

Page 54: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

54

import React from 'react';import ReactDOM from 'react-dom';import Foo from 'foo';import { ComponentA, ComponentB } from 'bar';

export const Root = props => {const bundledProps = { myValue: 'https://www.zalando.de' };const mergedProps = Object.assign({}, bundledProps, props);return (

<div id="root" data-props={JSON.stringify(props)}>{React.createElement(

Foo, null,React.createElement(ComponentA, null, 'Hello, World!'),React.createElement(ComponentB, {

href: jsonPtr.get(mergedProps, '#/myValue')})

)}</div>

);};

TESSELLATE: TRANSFORM JSON INTO JAVASCRIPT

type: foo.default

props: null

children:

- type: bar.ComponentA

props: null

- type: bar.ComponentB

props:

href:

$ref: #/myValue

Page 55: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

55

...

<div id="root" data-props={JSON.stringify(props)}>{React.createElement(

Foo, null,React.createElement(ComponentA, null, 'Hello, World!'),React.createElement(ComponentB, {

href: jsonPtr.get(mergedProps, '#/myValue')})

)}</div>

);};

export default function render(element) {const props = JSON.parse(element.getAttribute('data-props'));ReactDOM.render(<Root {...props} />, element);

}

TESSELLATE: TRANSFORM JSON INTO JAVASCRIPT

Page 56: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

56

TESSELLATE: TRANSFORM JSON INTO JAVASCRIPT

Build portable UMD bundles

Run webpack in memory github.com/mfellner/webpack-sandboxed

Export a root component, export a render function for Tailor

Interface for injected property values

Support JSON Pointers in props { “$ref”: “#/attrs/value” }

Inline props for rehydration

Include external component libraries from npm

{ type: “[node-module-name].[export-name]” }

Page 57: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

57

TESSELLATE: RENDER JAVASCRIPT INTO HTML

.HTML.JSTESSELLATE

fragment

CDNstatic server

1. fetch webpack bundles

2. load external data

3. execute JavaScript code

4. render static HTML

renderToString()

Page 58: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

58

TESSELLATE: RENDER JAVASCRIPT INTO HTML

Fetch the precompiled bundle …

const code = await fetchBundle()

const { Root } = vm.runInNewContext(code)

const props = await fetchContent()

const element = React.createElement(Root, props)

ReactDOMServer.renderToString(element)

Page 59: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

59

TESSELLATE: RENDER JAVASCRIPT INTO HTML

Run the code in the Node vm …

const code = await fetchBundle()

const { Root } = vm.runInNewContext(code)

const props = await fetchContent()

const element = React.createElement(Root, props)

ReactDOMServer.renderToString(element)

Page 60: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

60

TESSELLATE: RENDER JAVASCRIPT INTO HTML

Fetch any external content …

const code = await fetchBundle()

const { Root } = vm.runInNewContext(code)

const props = await fetchContent()

const element = React.createElement(Root, props)

ReactDOMServer.renderToString(element)

Page 61: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

61

TESSELLATE: RENDER JAVASCRIPT INTO HTML

Render to HTML!

const code = await fetchBundle()

const { Root } = vm.runInNewContext(code)

const props = await fetchContent()

const element = React.createElement(Root, props)

ReactDOMServer.renderToString(element)

Page 62: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

62

TESSELLATE: RENDER JAVASCRIPT INTO HTML

Download the code

Precompiled bundle and any dependencies.

Fetch external content

To be injected as properties into the root component.

Send to Tailor

Rendered HTML and links to JavaScript (according to the Fragment API).

Page 63: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

63

TESSELLATE

CONTENT

.HTML

.JS

SKIPPERrouter

TAILORlayout service

CLIENT

TESSELLATEbundler

TESSELLATEfragment

DATA

MODERN, INTERACTIVE USER EXPERIENCE ✓

DYNAMIC CONTENT ✓

CONSISTENT LOOK & FEEL EVERYWHERE ✓

Page 64: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

SCALING

THE

TECH TEAM

SCALING

THE

ARCHITECTURE

SCALING

THE

CONTENT

Page 65: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

SCALING

THE

TECH TEAM

SCALING

THE

ARCHITECTURE

SCALING

THE

CONTENT

RADICAL

AGILITY

MOSAIC

TESSELLATE

Page 66: Dan Persa, Maximilian Fellner - The recipe for scalable frontends - Codemotion Milan 2017

xwww.mosaic9.org

@danpersa

@mxfellner