View
154
Download
0
Category
Preview:
Citation preview
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
SCALING THE TECH TEAM
ATTRACT NEW,
TALENTED PEOPLEKEEP THE TEAMS HAPPY
ENCOURAGE
INNOVATIONCREATE DIVERSITY
13
Conway’s Law
“organizations which design systems
...are constrained to produce
designs which are copies of the
communication structures of these
organizations”
MICROSERVICES
TEAM AUTONOMY
INDEPENDENT RELEASE CYCLES
MIX DIFFERENT TECH STACKS
EASY A/B TESTING
SCALING THE ARCHITECTURE
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
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
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
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>
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
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!')})
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!')})
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
34
HOW IT LOOKS
Header Fragment
Cart Fragment
Tracking Fragment
*Not every fragment has to be visible
SCALING
THE
TECH TEAM
SCALING
THE
ARCHITECTURE
SCALING
THE
CONTENT
SCALING
THE
TECH TEAM
SCALING
THE
ARCHITECTURE
SCALING
THE
CONTENT
39
FRAGMENT ARCHITECTURE
FRAGMENT SKIPPERrouter
TAILORlayout service
CLIENT
?MODERN, INTERACTIVE USER EXPERIENCE
DYNAMIC CONTENT
CONSISTENT LOOK & FEEL EVERYWHERE
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
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
42
FRAGMENT ARCHITECTURE
FRAGMENTSOLUTION
Reusable, shared components
Isomorphic/universal code
SSR: JavaScript → HTML
*
* generic universal component framework
43
CONTENT .HTML
.JS
SKIPPERrouter
TAILORlayout service
CLIENT
DESCRIBE JAVASCRIPT COMPONENTS WITH JSON
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
45
LAYOUT .HTML
.JS
SKIPPERrouter
TAILORlayout service
CLIENT
GENERATE CODE AND HTML
USE COMMON COMPONENT LIBRARIES
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()
47
LAYOUT .HTML
.JS
SKIPPERrouter
TAILORlayout service
CLIENT
GENERATE CODE AND HTML
UNIVERSAL
JAVASCRIPT RUNS
ON THE CLIENT TOO!
48
tessellateverb tes·sel·late \ˈte-sə-ˌlāt\
to form into or adorn with mosaic
github.com/zalando-incubator/tessellate
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
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>
);};
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://…’
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://…’
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
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
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
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]” }
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()
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)
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)
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)
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)
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).
63
TESSELLATE
CONTENT
.HTML
.JS
SKIPPERrouter
TAILORlayout service
CLIENT
TESSELLATEbundler
TESSELLATEfragment
DATA
MODERN, INTERACTIVE USER EXPERIENCE ✓
DYNAMIC CONTENT ✓
CONSISTENT LOOK & FEEL EVERYWHERE ✓
SCALING
THE
TECH TEAM
SCALING
THE
ARCHITECTURE
SCALING
THE
CONTENT
RADICAL
AGILITY
MOSAIC
TESSELLATE
Recommended