uber's new mobile architecture

77
Uber’s New Mobile Architecture Uber Mobility April 19th, 2017

Upload: dhaval-patel

Post on 21-Jan-2018

2.341 views

Category:

Engineering


4 download

TRANSCRIPT

Uber’s New Mobile Architecture

Uber Mobility

April 19th, 2017

Why a new architecture?Tuomas Artman

Uber’s entire mobile team 4 ½ years ago

“What if we changed everything?”

99.99% availability of core flows

Enable global roll-back of core flows to a

guaranteed working state

Testing as a first-class citizen

Architectural goals

99.99% availability of core flows

Enable global roll-back of core flows to a

guaranteed working state

Testing as a first-class citizen

Support Uber’s growth for years to come

Narrow and decouple functionality as much

as possible

Architectural goals

99.99% availability of core flows

Enable global roll-back of core flows to a

guaranteed working state

Testing as a first-class citizen

Support Uber’s growth for years to come

Narrow and decouple functionality as much

as possible

Provide rails for both design and code

Consistency in engineering, consistency in

UX

Architectural goals

99.99% availability of core flows

Enable global roll-back of core flows to a

guaranteed working state

Testing as a first-class citizen

Support Uber’s growth for years to come

Narrow and decouple functionality as much

as possible

Provide rails for both design and code

Consistency in engineering, consistency in

UX

Monitoring is a first-class citizen

Automatic analytics, logging, debugging, and

tracing

Architectural goals

99.99% availability of core flows

Enable global roll-back of core flows to a

guaranteed working state

Testing as a first-class citizen

Support Uber’s growth for years to come

Narrow and decouple functionality as much

as possible

Provide rails for both design and code

Consistency in engineering, consistency in

UX

Monitoring is a first-class citizen

Automatic analytics, logging, debugging, and

tracing

De-risk experimentation

Application framework with Plugin API

Architectural goals

99.99% availability of core flows

Enable global roll-back of core flows to a

guaranteed working state

Testing as a first-class citizen

Support Uber’s growth for years to come

Narrow and decouple functionality as much

as possible

Provide rails for both design and code

Consistency in engineering, consistency in

UX

Monitoring is a first-class citizen

Automatic analytics, logging, debugging, and

tracing

De-risk experimentation

Application framework with Plugin API

Make magic

Performance second to none, graceful

degradation on low-end devices and networks

Architectural goals

Copyright Tsahi Levent-Levi

Multiplatform architectureDouble the effectiveness of teams

Timeline

RIB Architecture Application Framework

Dependency management

Reactive data flows

Scoping

CompartmentalizationAnalytics

UI Components

Mapping

Testability Experimentation

Plugins

Threading model

Storage

Location services

Logging

Monitoring

NetworkingRouting

Code gen

Business-logic driven

Open source

Deep Scope HierarchiesTony Cosentini

Dealing with State

Lots of apps have tricky asynchronous, state issues

At Uber, this compounds quite a bit

Uber’s apps have a lot of asynchronous state, from multiple data sources

150+ contributors

Scopes?“The lifecycle in which an object exists.”

App Scope

RiderAppDelegate / RiderApplication {

PickupRequestManager

DestinationRefinementStream

DriverLocationMapLayer

… and a lot more stuff…

}

What’s so bad about this?

Stability and Code Quality Impact

Objects that live longer than

necessary are exposed to more

state they don’t need.

In Uber’s apps in particular, many of

these stateful objects observe

different streams of data. This

means these objects might get

frequent updates even when they

aren’t being used.

class DriverIsOnTheirWayToaster {private boolean isOnTripAndHasNotBeenNotified;

public DriverIsOnTheirWayToaster(TripStateStreamtripStateStream) {

tripStateStream.subscribe({ (state, driver?) ->if (state == ON_THEIR_WAY {isOnTripAndHasNotBeenNotified = true;showToast(driver!.name);

} else if (state == OFF_TRIP) {isOnTripAndHasNotBeenNotified = false;

}})

}}

Input and dependency contracts are diluted

When objects live at app scope,

they cannot have very rigid inputs

and dependencies.

Why does this class take an

optional AuthToken if it’s really

required?

How does this hurt testing?

public class AuthenticatedNetworkRequester {private AuthToken? authToken;

public void makeRequest() {networkClient.makeRequest(authToken!)

} }

Other Issues

While most of the classes are very simple, having lots of objects around for the

entire duration of the app’s lifecycle is not super efficient.

Classes can grow to not have a clear purpose.

Although it wasn’t as bad as some of the examples, there was no standard way

to “scope” singletons and objects to different lifecycles of the app.

Previous Rider App Scope Hierarchy

How do we improve this?

Improvements

Lots of our bugs are state related.

A pattern or framework to encourage creating objects only when relevant would help here.

The view hierarchy doesn’t really line up with business logic.

We should create a hierarchy based on business logic states, which does not necessarily map to

what is on the screen.

There was no easy, consistent way to create your own scopes.

ScopesBased on business logic

Scopes

Root doesn’t know anything - it can’t

make any assumptions.

Root

Scopes

LoggedIn knows that you have valid

non-null authentication credentials.

Dependencies created and provided

here can take non-null credentials

without having to make any

assumptions.

Root

LoggedIn

Moving AuthenticatedNetworkRequester to LoggedIn Scope

public class AuthenticatedNetworkRequester {private AuthToken? authToken;

public void makeRequest() {networkClient.makeRequest(authToken!)

} }

Moving AuthenticatedNetworkRequester to LoggedIn Scope

AuthToken is now guaranteed to be

available.

Other objects that depend on

AuthenticatedNetworkRequester

need to be in a logged in state to

use it.

If a dependency requires

AuthenticatedNetworkRequester

outside of the LoggedIn scope, it’s

now a compile error.

public class AuthenticatedNetworkRequester {private AuthToken authToken;

public void makeRequest() {networkClient.makeRequest(authToken)

} }

Scopes

OnTrip knows that the user is logged

in and on a trip.

Dependencies created and provided

here can take non-null credentials and

all data related to a trip without having

to make any assumptions.

Root

LoggedIn

OnTrip

Moving DriverIsOnTheirWayToaster to OnTrip Scope

class DriverIsOnTheirWayToaster {private boolean isOnTripAndHasNotBeenNotified;

public DriverIsOnTheirWayToaster(TripStateStreamtripStateStream) {

tripStateStream.subscribe({ (state, driver?) ->if (state == ON_THEIR_WAY {isOnTripAndHasNotBeenNotified = true;showToast(driver!.name);

} else if (state == OFF_TRIP) {isOnTripAndHasNotBeenNotified = false;

}})

}}

Moving DrvierIsOnTheirWayToaster to OnTrip Scope

Now DrvierIsOnTheirWayToaster is

exposed to much less state - it’s

easier to understand, and less

prone to bugs.

class DriverIsOnTheirWayToaster {

public DriverIsOnTheirWayToaster(Driver driver) {showToast(driver.name);

}}

Composable single-responsibility units

Yi Wang

RIB

Massive ViewController/Fragment

RIBComposable Single-responsibility Units

RIBComposable Single-responsibility Units

RIBComposable Single-responsibility Units

RIBComposable Single-responsibility Units

RIBComposable Single-responsibility Units

RIB Tree & Inter-RIB CommunicationComposing RIBs into features

RIB Tree & Inter-RIB CommunicationComposing RIBs into features

RIB Tree & Inter-RIB CommunicationComposing RIBs into features

RIB Tree & Inter-RIB CommunicationComposing RIBs into features

RIB Tree & Inter-RIB CommunicationComposing RIBs into features

View(Controller) TreeComposing RIBs into features

What about other architectures?

MVC

● Massive ViewController

● Locked in view tree and business tree

MVVM

● Massive ViewModel

● Locked in view tree and business tree

VIPER

● Logic separation based on view

● Locked in view tree and business tree

What did RIBs give us?

● Rider app broken up into more than 500 RIBs

○ Many are reused with multiple parts of the tree

● All RIBs have less than 300 lines of code per class

● All business logic well unit-tested

Brian Attwell

Plugins

● Support Uber’s growth for years to come?

● Provide rails for both design and code

● De-risk experimentation

Architectural goalsConsider three of our architectural goals:

Support Uber’s growth for years to come

RIBs give us code isolation

We want more code isolation

Code Isolation Example

Code Isolation Example

Code Isolation Example

Code Isolation Example

Code Isolation Example

Support Uber’s growth for years to come

Provide rails for both design and code?

De-risk experimentation

How do Plugins play into Architectural goals?Consider three of our architectural goals:

Home Screen

Interface:Router<? extends MapComponent> { }

Example:NearbyVehiclesMapLayer

PresentLocationMapLayerCurbsidePickupMapLayerDestinationRefinementMapLayer

Home Screen

for (HomeMapLayerPlugin plugin: plugins) {attachChild(plugin.buildRib(component));

}

Home Screen

Interface:ViewRouter<?> { }

Example:SnapchatCardRouter

RatingCardRouterTransitConnectionRouterEatsCardRouterPaymentsCardRouter

Interface:interface LocationRowProvider {

Observable<SearchResult[]> query(input); }

interface SearchResult {String tag();Router buildRouter(parent);

}

Example:CalendarResultsPlugin

SavedLocationsPluginGeoSearchResultsPluginFriendsLocationPlugin

Observable.combineLatest(plugins).subscribe(BindLocationRowsConsumer)

@Override

protected void didBecomeActive() {

for (Work worker : mainScopedPluginManager.get()) {

work.start();

}

}

Viewless plugins

Interface:interface MenuPlugin {

MenuCategory menuCategory();Router buildRouter(MenuPluginComponent)

}

enum MenuCategory { TOP, BOTTOM }

Example:PaymentMenuPluginFreeRidesPluginHelpPlugin

Almost all features in the app can be

written as code that plugs into the

core of the app.

80% of Rider’s Application Layer written as Plugins

New plugin points and changes to core get

extra code reviewers added automatically.

Support Uber’s growth for years to come

Provide rails for both design and code

De-risk experimentation?

How do Plugins play into Architectural goals?Consider three of our architectural goals:

Derisking Experimentation

Roll out all new features as plugins

Every plugin is initially A/B tested

Every plugin can be disabled from our servers

UI tests ensure the app is still functional when all plugins disabled

De-risking Experimentation

Engineers want to reuse existing plugin points and now know where the

build new features

We’ve prevented six outages in production by disabling plugins

Statically ensured code isolation between the architecturally significant core

of the app and the app’s features

ResultsFrom using plugins

Development Velocity

Able to sustain 500 diffs/week per platform

Diffs landed per week doubled after finishing the four month rewrite

Crash Free Rate

Immediately after releasing the new app, crash rate was better than the old app

iOS crash free free rate of 99.99% and climbing

Android crash free rate of 99.9% and climbing

Performance

App starts 50% faster

Developer Happiness

78.5 developer NPS score improvement

ResultsFrom the new architecture

Thank you

Proprietary and confidential © 2016 Uber Technologies, Inc. All rights reserved. No part of this document may be

reproduced or utilized in any form or by any means, electronic or mechanical, including photocopying, recording, or by any

information storage or retrieval systems, without permission in writing from Uber. This document is intended only for the use

of the individual or entity to whom it is addressed and contains information that is privileged, confidential or otherwise

exempt from disclosure under applicable law. All recipients of this document are notified that the information contained

herein includes proprietary and confidential information of Uber, and recipient may not make use of, disseminate, or in any

way disclose this document or any of the enclosed information to any person other than employees of addressee to the

extent necessary for consultations with authorized personnel of Uber.

eng.uber.comgithub.com/uber