Slide 1

Slide 1 text

Platform-Powered 🚀 Build a frontend platform that scales as fast as you do Andrew Hao (@andrewhao)

Slide 2

Slide 2 text

Ever felt growing pains? 2 / 31

Slide 3

Slide 3 text

"...most tools and processes only support about one order of magnitude of growth before becoming ineffective" Will Larson. An Elegant Puzzle: Systems of Engineering Management 3 / 31

Slide 4

Slide 4 text

Monolith to Microservices Python/Angular monolith ...to Node + React isomorphic apps via a templated service generator ...coupled with infrastructure investments in microservices ...led to a microservice explosion! 💥 4 / 31

Slide 5

Slide 5 text

But the problems were starting to catch up to us Long-lived services require maintenance Platform was fragmenting New infrastructure updates were hard to apply 5 / 31

Slide 6

Slide 6 text

Technical leverage 6 / 31

Slide 7

Slide 7 text

But where to start? 7 / 31

Slide 8

Slide 8 text

8 / 31

Slide 9

Slide 9 text

9 / 31

Slide 10

Slide 10 text

Generation 3: @lyft/service ✨ 10 / 31

Slide 11

Slide 11 text

Principles for Technical Leverage 👟 ✨ 🤖 Stand on the Shoulders of Giants Simplify to Understand Standardize and Automate 11 / 31

Slide 12

Slide 12 text

Stand on the Shoulders of Giants 👟 We chose Next.js as our platform of choice 12 / 31

Slide 13

Slide 13 text

Stand on the Shoulders of Giants 👟 We chose Next.js as our platform of choice Solved: Build configurations, static site generation, AMP pages, code splitting, dynamic imports 12 / 31

Slide 14

Slide 14 text

Stand on the Shoulders of Giants 👟 We chose Next.js as our platform of choice Solved: Build configurations, static site generation, AMP pages, code splitting, dynamic imports 🎷 Now: We don't need to maintain our internal build system anymore 12 / 31

Slide 15

Slide 15 text

Simplify to Understand ✨ Paradigm shift: convention over configuration 13 / 31

Slide 16

Slide 16 text

Simplify to Understand ✨ Paradigm shift: convention over configuration Next.js: Filesystem router, server-side getInitialProps and getServerSideProps handlers 13 / 31

Slide 17

Slide 17 text

Simplify to Understand ✨ Paradigm shift: convention over configuration Next.js: Filesystem router, server-side getInitialProps and getServerSideProps handlers 🍕 Now: Lower cognitive load working in apps, higher developer productivity 13 / 31

Slide 18

Slide 18 text

Standardize and Automate 🤖 We built a plugin system that standardizes our library integrations 14 / 31

Slide 19

Slide 19 text

Standardize and Automate 🤖 We built a plugin system that standardizes our library integrations We made migrations a first-class part of our new system 14 / 31

Slide 20

Slide 20 text

Standardize and Automate 🤖 We built a plugin system that standardizes our library integrations We made migrations a first-class part of our new system 🎸 Now: we have the tools to reuse code, keep the stack modern and prevent drift 14 / 31

Slide 21

Slide 21 text

Anatomy of a Plugin A set of hooks, bundled up in a library Plugin Hooks: Webpack Express Middleware Next.js Configuration Next.js Application Next.js Document 15 / 31

Slide 22

Slide 22 text

1. Install the plugin in lyft.plugins.ts import CookieAuthPlugin from "@lyft/service plugin cookie auth"; const plugins = [ new CookieAuthPlugin(), Other plugins ]; 16 / 31

Slide 23

Slide 23 text

2. Use it! import { useCookieAuth } from "@lyft/service plugin cookie auth"; In React component const Page: React.FC = () { const { userId, isLoggedIn } = useCookieAuth(); That's it! You can now use as you see f t if (!isLoggedIn()) { return

Sorry, you must be logged in p>; } }; 17 / 31

Slide 24

Slide 24 text

CookieAuthPlugin: Express.js server hook import cookieParser from "cookie parser"; import { Application } from "express"; const cookieAuthServerHook = (app: Application) { Gives us req.cookies app.use(cookieParser); app.use(function parseUserId(req, res, next) { Assume this decrypts data and returns a user ID from a session const { userId } = parseSessionCookies(req.cookies); Store userId in response for later retrieval res.locals.userId = userId; next(); }); }; 18 / 31

Slide 25

Slide 25 text

CookieAuthPlugin: Next.js App hook function CookieAuthApp({ App: NextApp }) { return class extends App { static getInitialProps = async (appContext) { const originalProps = await App.getInitialProps(appContext); const userId = appContext.ctx.res locals userId; return { originalProps, userId }; }; render() { return ( {super.render()} ); } }; } 19 / 31

Slide 26

Slide 26 text

Bring it all together into the Plugin export default class CookieAuthPlugin { apply = (service: ServicePluginHost) { service.hooks.server.tap(this.name, cookieAuthServerHook); service.hooks.app.tap(this.name, (App) CookieAuthApp({ App })); }; } 20 / 31

Slide 27

Slide 27 text

And add a nice developer facing convenience hook const useCookieAuth = () ({ userId: React.useContext(CookieAuthContext), isLoggedIn: () { const userId = React.useContext(CookieAuthContext); return userId; }, }); 21 / 31

Slide 28

Slide 28 text

@lyft/service Plugin Ecosystem State management (Redux, MobX, XState) GraphQL Lyft Product Language, styled-components, Material UI authn/authz i18n RUM performance tracking Feature flagging and experimentation MirageJS Logging/metrics/bug reporting 22 / 31

Slide 29

Slide 29 text

Coming Soon Developer Support Tooling Embedded tools to help developers debug or ask for help 23 / 31

Slide 30

Slide 30 text

Flywheel effect Now users are contributing back to these plugins Over 40% of new plugins have been product-engineer contributions 24 / 31

Slide 31

Slide 31 text

Migrations - How we Automate Guardrails to prevent drift jscodeshift scripts 25 / 31

Slide 32

Slide 32 text

Original import { logger } from "@lyft/service plugin logging"; logger.info("test log"); Upgraded import { getLogger } from "@lyft/service plugin logging"; const logger = getLogger(); logger.info("test log"); 26 / 31

Slide 33

Slide 33 text

Migration Versioning We use versioned migrations If you change an interface, you must ship a migration Store migration state per plugin in package.json 27 / 31

Slide 34

Slide 34 text

Release Management: One bold constraint The platform version and the plugin system are pinned to the same version 🎯 Plugins are guaranteed to work with a specific version of the platform This means the entire platform moves together! 28 / 31

Slide 35

Slide 35 text

Organizational process Hands-on migration workshops Migration scripts take services most of the way from Gen 2 to Gen 3 Relentless internal evangelism Technical program management + senior leadership visibility are key 29 / 31

Slide 36

Slide 36 text

Wins Higher developer happiness and productivity Quicker adoption of new service releases, preventing drift 30 / 31

Slide 37

Slide 37 text

Principles for Technical Leverage 👟 ✨ 🤖 Stand on the Shoulders of Giants Simplify to Understand Standardize and Automate 31 / 31