Slide 1

Slide 1 text

From Angular to React by @Jack_Franklin

Slide 2

Slide 2 text

From X to Y My experience migrating complex software (Regardless of the tech choices).

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Music discovery + ticket selling

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

The Store

Slide 10

Slide 10 text

— Started 2 years ago, built on Angular 1 — Primarily used within iframes — Added Redux to be used alongside Angular — 2 main developers no longer with the company — Very heavy on features — Our main source of revenue — Tickets being sold constantly — Used by our flagship clients (Adele, RHCP and others)

Slide 11

Slide 11 text

The store works well.

Slide 12

Slide 12 text

But changing it is very tricky.

Slide 13

Slide 13 text

— Test coverage not bad, but not great. — Unclear which features are actually needed — Many different use cases — Regular general admission tickets — Reserved seating tickets — Queued tickets for big sales — VIP tickets ("meet and greet") — Merchandise addons (Ticket + album = 10% discount) — We have a lot of stores in the wild

Slide 14

Slide 14 text

We want to be in a position to iterate quickly on this product, but with the current codebase we can't.

Slide 15

Slide 15 text

What do we do?

Slide 16

Slide 16 text

1. Start completely from scratch 2. Stick with Angular 1, and rewrite complex parts. 3. Migrate from Angular 1 to 2/3/4 4. Migrate bit by bit to something else

Slide 17

Slide 17 text

Start completely from scratch ! — We can't afford to not work on bugs, improvements on the existing product — The Angular app began as a big rewrite - business wwouldn't like it to happen again

Slide 18

Slide 18 text

Stick with Angular 1 ! — Angular 1 is reaching its end - focus is now on Angular 2 and beyond. — Not much developer expertise in Angular across Songkick

Slide 19

Slide 19 text

Migrate from Angular 1 to 2 and beyond ! — Not much developer expertise in Angular across Songkick — Not many Angular evangelists at Songkick — Apparently David Moyes likes Angular

Slide 20

Slide 20 text

Migrate bit by bit to something else ! — Allows us to work on bugs in existing code whilst migrating others — Allows us to build new features whilst migrating — We can experiment by moving piece by piece and try different approaches — Current stores continue to function correctly — We can move quickly and release small parts at once - avoiding risky big bang releases — Downside: introduce some migration specific code

Slide 21

Slide 21 text

What to migrate to?

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

! I and my team are all familiar with React ! Another, newer, product at Songkick was built with React ! React leaves you with less framework specific code ! It's Rafa Benitez's framework of choice

Slide 24

Slide 24 text

Note: I am not saying that Angular is a bad choice, or that you shouldn't use it. Just for us, it wasn't the best fit.

Slide 25

Slide 25 text

So, we've decided to migrate, and what to migrate to.

Slide 26

Slide 26 text

Now for the hard part — 40,000 lines of JavaScript — Most of those lines are tied directly to Angular — Many dependencies outdated due to lack of maintanance over past 6 months — A store that cannot be broken at any point (we sell tickets pretty much continuously)

Slide 27

Slide 27 text

The Theory Components (or directives in Angular)

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

The Plan

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

The theory is easy!

Slide 36

Slide 36 text

But more complicated in practice...

Slide 37

Slide 37 text

Angular and dependency injection

Slide 38

Slide 38 text

Angular 1 was built in a time where we had no module system in JavaScript, so it provided one.

Slide 39

Slide 39 text

// define your Foo object const Foo = {...} angular.service('Foo', Foo) // in another Angular thing function SomeController(Foo) { Foo.bar() ... } Angular will inject the Foo dependency into SomeController at runtime.

Slide 40

Slide 40 text

In ES2015 we have a module system (which Babel transpiles for us) const Foo = {...} export default Foo // in another file import Foo from './foo'

Slide 41

Slide 41 text

ES2015 modules are amazing cool super awesome (Come chat to me if you want boring details)

Slide 42

Slide 42 text

The step by step process of migrating an Angular directive to a React component

Slide 43

Slide 43 text

1. Find all dependencies that the directive (or a dependency of the directive) injects.

Slide 44

Slide 44 text

2. Migrate all of those one by one to React

Slide 45

Slide 45 text

But wait! You need to migrate localStorage-service.js away from Angular. angular.service('LocalStorage', function() { ... }) But this service is used in ~15 places in the app. And we want to release the migration in tiny chunks.

Slide 46

Slide 46 text

So, we can first pull the logic into a standalone thing: const localStorage = { ... }

Slide 47

Slide 47 text

And then export it so any non Angular code can just import it: const localStorage = { ... } export default localStorage

Slide 48

Slide 48 text

And then leave the small Angular wrapper: const localStorage = { ... } export default localStorage angular.service('LocalStorage', function() { return localStorage })

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

! Existing Angular code can still use the local storage code without being aware it's changed ! New React components can import the module without any of the Angular boilerplate " We had to write a small amount of migration specific code

Slide 51

Slide 51 text

Migration specific code Code that you know will be deleted when the migration is finished. A neccessary part of the migration but you should keep it to a minimum.

Slide 52

Slide 52 text

So, we've migrated all dependencies away from Angular

Slide 53

Slide 53 text

But what about Angular's built ins? — $timeout is a wrapper around setTimeout — $window is a wrapper around $window — $q is a wrapper around Promise

Slide 54

Slide 54 text

Now we can migrate listingsButton- directive.js to React

Slide 55

Slide 55 text

import React, { Component } from 'react' class ListingsButton extends Component { render() { return ( Buy now! ) } }

Slide 56

Slide 56 text

But how do we embed this within Angular?
  • Rihanna at The O2

    Buy now
  • Slide 57

    Slide 57 text

    No content

    Slide 58

    Slide 58 text

    ngReact lets you embed React components within Angular applications It is the piece of code that enables our entire migration strategy to work. Remember: you can render many React apps on a page in any place you want.

    Slide 59

    Slide 59 text

    With ngReact
  • Rihanna at The O2

  • Slide 60

    Slide 60 text

    We can continue this strategy, migrating the other components to React:
  • Slide 61

    Slide 61 text

    No content

    Slide 62

    Slide 62 text

    And now we can migrate this into one big React component! import React, { Component } from 'react' import ListingsTitle from './listings-title' import ListingsButton from './listings-button' class ListingsItem extends Component { render() { return (
  • ) } }

    Slide 63

    Slide 63 text

    And update our template

    Slide 64

    Slide 64 text

    No content

    Slide 65

    Slide 65 text

    Maintaining functionality — At no point can the store be broken. — Newly migrated code must have exactly the same functionality — Put simply: users must not notice any difference

    Slide 66

    Slide 66 text

    Unit Tests — Existing Angular tests can be migrated as the services move away from Angular — Used as an opportunity to add test coverage

    Slide 67

    Slide 67 text

    React components are very testable import React from 'react' import ListingsDate from './listings-date' import { mount } from 'enzyme' describe('ListingsDateComponent', () => { describe('an event with just a start date', function() { const event = { event_date: { date: '2016-01-01' } } const wrapper = mount(React.createElement(ListingsDate, { event })) it('formats the month correctly', function() { expect(wrapper.find('.date-block__seg--month').text()).toEqual('Jan') }) it('formats the day correctly', function() { expect(wrapper.find('.date-block__seg--day').text()).toEqual('1') }) it('formats the year correctly', function() { expect(wrapper.find('.date-block__seg--year').text()).toEqual('2016') }) }) })

    Slide 68

    Slide 68 text

    Unit tests are good because they provide quick feedback and can test at a very granular level.

    Slide 69

    Slide 69 text

    Unit tests (in our case) are bad because when you migrate code, you migrate tests Therefore, they are not technically proof that everything works as before.

    Slide 70

    Slide 70 text

    Acceptance tests

    Slide 71

    Slide 71 text

    No content

    Slide 72

    Slide 72 text

    — Automated tests that are run using Protractor & Selenium — ! They test the entire system, with no faked API calls or fake data — ! This means they are entirely independent of the migration — " They can flake sometimes and are slow to run

    Slide 73

    Slide 73 text

    Because they are slow to run, we keep acceptance tests to our core user journeys

    Slide 74

    Slide 74 text

    They run on every single build, and they have to pass to enable us to deploy

    Slide 75

    Slide 75 text

    The pipeline

    Slide 76

    Slide 76 text

    No content

    Slide 77

    Slide 77 text

    Track user activity to ensure deploys are successful

    Slide 78

    Slide 78 text

    !

    Slide 79

    Slide 79 text

    Unfortunately, fires will happen during this process.

    Slide 80

    Slide 80 text

    To date we've had 3 fires caused by migration: — One was down to bad data in our test environment not matching "real life" — we have now fixed the test data to replicate "real life"

    Slide 81

    Slide 81 text

    To date we've had 3 fires caused by migration: — One was down to a particular journey not being covered by any unit or acceptance tests — it is now covered by tests

    Slide 82

    Slide 82 text

    To date we've had 3 fires caused by migration: — Another was down to timezones ! — we know to check our site on different timezones when making changes that might hit this problem

    Slide 83

    Slide 83 text

    You have to communicate how and why a fire happened, but how you'll prevent it next time Because you can never ever release bug free so!ware.

    Slide 84

    Slide 84 text

    Momentum and prioritisation We knew this migration was going to be at least 6 months, more likely closer to a year.

    Slide 85

    Slide 85 text

    No content

    Slide 86

    Slide 86 text

    Mix larger, multi-week work with short, 1-2 day work, to keep momentum.

    Slide 87

    Slide 87 text

    <1 day's worth of work

    Slide 88

    Slide 88 text

    1 week's worth of work

    Slide 89

    Slide 89 text

    Break down large work into small PRs

    Slide 90

    Slide 90 text

    Release early, release often

    Slide 91

    Slide 91 text

    The boy scout rule Always leave things better than when you found them.

    Slide 92

    Slide 92 text

    Mix visual work + "under the hood work"

    Slide 93

    Slide 93 text

    Have some form of tracking or metrics, however rough it is

    Slide 94

    Slide 94 text

    No content

    Slide 95

    Slide 95 text

    No content

    Slide 96

    Slide 96 text

    Prioritisation One of the core goals of this migration was to make the code easier to work on, add features to and fix bugs with more confidence.

    Slide 97

    Slide 97 text

    Pick work based on bugs and churn rate, not code quality Churn rates: the amount of times a file is changed

    Slide 98

    Slide 98 text

    Code quality Churn rate Bug rate Priority Bad Low Low Low Good Low Low Lowest Bad Medium High High Good Low High High Bad High High Highest

    Slide 99

    Slide 99 text

    Remember: you can go back and rewrite migrated code Don't aim for perfection when you migrate.

    Slide 100

    Slide 100 text

    Communicate to the business Specifically: Why?

    Slide 101

    Slide 101 text

    "Right now when you ask us for a new feature, or bug fix, it's hard and takes a long time to fix and have confidence that we've not inadvertently broken anything else" "This migration will enable us to have a leaner, stable codebase which we will have more confidence in and be able to build features and fix bugs more quickly" "It will enable us to onboard new developers to the team easily and have them contributing quicker than with the current codebase"

    Slide 102

    Slide 102 text

    xf buddies

    Slide 103

    Slide 103 text

    Tooling Because you can't do a JavaScript talk without talking about tools. 1 1 And I don't mean John O'Shea or David Moyes

    Slide 104

    Slide 104 text

    Migrations are a good excuse to reconsider tooling And also provide a break from working on the actual user codebase.

    Slide 105

    Slide 105 text

    From Karma to Jest Our tests were running on Karma, but we wanted to try Jest from Facebook. We had (at the time) ~600 tests across ~130 files. So we started migrating them!

    Slide 106

    Slide 106 text

    No content

    Slide 107

    Slide 107 text

    From Browserify to Webpack The store was built using a combination of Makefiles, misc command line tools and Browserify. This was hard for us to follow and was very brittle. We moved to Webpack which lead to reduce build times, smaller bundles and got us more inline with the rest of the community.

    Slide 108

    Slide 108 text

    By providing a mix of migration tasks (big, small, visual, "under the hood", tooling), we're able to keep work fun and interesting over a (so far) 6 month period, with probably 6 more months to go.

    Slide 109

    Slide 109 text

    How's it going so far? We estimate to be ~ 35% done. Most of the core visual journey is done. All forms that users interact with are now in React. We've had very few bug reports on any of our new React code, compared to the old Angular code.

    Slide 110

    Slide 110 text

    No content

    Slide 111

    Slide 111 text

    We've done some stuff well, and I expect to learn we've done some stuff badly.

    Slide 112

    Slide 112 text

    Will you ever be done? Our natural "finish" point is when we've removed Angular from our codebase. But when that happens we'll keep refactoring & revisiting old code as we fix bugs and introduce new features.

    Slide 113

    Slide 113 text

    Takeaways

    Slide 114

    Slide 114 text

    1. Don't migrate for the sake of it "Framework X is now out of date" is not a valid reason!

    Slide 115

    Slide 115 text

    2. Plan, plan and plan again

    Slide 116

    Slide 116 text

    3. Cross business communication is more important than you realise at first

    Slide 117

    Slide 117 text

    4. Prioritise based on pain points in your current application

    Slide 118

    Slide 118 text

    5. Mix up tasks based on difficulty + visual/tooling/etc to keep it interesting

    Slide 119

    Slide 119 text

    6. Have some rough metrics that you can use to track progress internally

    Slide 120

    Slide 120 text

    7. Don't be surprised if you end up rewriting some migrated code

    Slide 121

    Slide 121 text

    Thank you :) Questions? [email protected] Want to get involved? We're hiring (in London) [email protected]