uber's new mobile architecture
Post on 21-Jan-2018
2.341 Views
Preview:
TRANSCRIPT
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
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
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
RiderAppDelegate / RiderApplication {
PickupRequestManager
DestinationRefinementStream
DriverLocationMapLayer
… and a lot more stuff…
}
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.
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.
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);
}}
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
● 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
Provide rails for both design and code?
De-risk experimentation
How do Plugins play into Architectural goals?Consider three of our architectural goals:
Interface:Router<? extends MapComponent> { }
Example:NearbyVehiclesMapLayer
PresentLocationMapLayerCurbsidePickupMapLayerDestinationRefinementMapLayer
Home Screen
Interface:ViewRouter<?> { }
Example:SnapchatCardRouter
RatingCardRouterTransitConnectionRouterEatsCardRouterPaymentsCardRouter
Interface:interface LocationRowProvider {
Observable<SearchResult[]> query(input); }
interface SearchResult {String tag();Router buildRouter(parent);
}
Example:CalendarResultsPlugin
SavedLocationsPluginGeoSearchResultsPluginFriendsLocationPlugin
@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
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
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
top related