Long ago, when dinosaurs still roamed the earth and Wealthfront was starting to brainstorm a financial planning offering, early designs amounted to an online financial calculator with lots of bells and whistles.
Users would be expected to enter all of their account information by hand — and keep it up to date! It took minimal debate to realize that we did not want to go down this path. With our mission being to optimize and automate your finances, we realized we would need to automate this aspect of our product as well. To that end, we toiled to create our Linking Platform, which would take responsibility for the aggregation of our clients’ external accounts and keeping that information up to date. Now, users could log in to all of their external institutions one time from our app, and their finances outside Wealthfront would be recorded accurately and kept fresh.
In implementing the flow behind this framework, we quickly encountered a speed bump in the way of a seamless user experience: It could take minutes to complete the entire process of linking, from authentication to processing the information through backend. Historically, our solution to this problem has been to acquaint the user with timeless investing wisdom from our CIO, Burt Malkiel, while we process their linking request:
While it may be delightful to learn a few nuggets of wisdom on your first link, we can imagine this might get old by your tenth…
We eventually got around to asking ourselves, “What if we could relegate the process of linking to the background, demanding attention from the user only when their input is needed to move the linking flow along?” After countless iterations on design, product specs, and engineering implementation, we successfully built Parallel Linking:
In this blog, I’ll recap one of the key design decisions we made on the engineering side and the reasoning behind that decision.
State management: backend vs. clients
One key decision point we had to make early on was whether to manage the state of our Parallel Linking Hub (the expanded view you’d see when tapping on the banner) from the backend or from each individual client (Web, iOS, and Android). This state would include everything around what text would be shown in the collapsed banner (e.g., “Linking in progress…” or “Action required to finish linking”), what link status cards would be displayed in the hub and in what order, and what the content on those cards would be (title, subtitle, and action button) among other things. Doing all this state management on backend would mean saving time and effort on the three client teams at the cost of a bit of extra effort on backend. Code would also be far more maintainable and robust to changes from product or design as there would only be one place to change (backend) as opposed to three different clients.
Initially, this seemed like a no brainer. However, we quickly realized that, due to performance reasons, we had previously implemented a paradigm of polling the backend with only one link at a time. This worked well for the synchronous flow since the user would be blocked on linking another institution until the current link reached a terminal state. With Parallel Linking, we had endeavored to allow multiple links in parallel but still desired to keep the backend solution as performant as possible. The problem we faced here was that backend was naive to which linking syncs were initiated by the client in a particular session. In order to compute the whole state of Parallel Linking, backend would need to ask the database about the status of all of the user’s links, regardless of whether they were initiated in the given session or not. Predictably, this introduced a performance hit somewhat proportional to the number of active links a user had:
Number of Active Links | Average Performance Hit |
1 |
4.58% |
3 |
5.77% |
5 |
3.98% |
8 |
16.72% |
While we might have accepted a minor performance hit in order to make life easier for client-side engineers, those numbers indicated that we would have to rethink doing a state management system entirely on backend.
Fortunately, not all hope was lost. Backend could still provide the status of each individual link, which clients would refresh on each poll. Critical to this would be an enum (e.g., SYNCING, ACTION, or ERROR) that would inform the clients on how to formulate what appears on the main status banner. Here’s a simple example of a desired UI state (on Web) and corresponding API output from backend:
Notably, we decided to serve all of the copy from backend. While this adds a few bytes to the response, it has the aforementioned benefits of backend being the one and only place to make copy changes. Additionally, backend has the ability to release immediately after making such a change while mobile clients in particular would need to push a new build to their respective app stores.
Establishing the request lifecycle
Once we had decided upon the division of implementation between backend and client side, the next step was to get all platforms on the same page as to what the request lifecycle would look like as status in both areas updates over time. One artifact that came out of this was a flow diagram that clients could refer to in order to have a consistent implementation across all platforms:
To add some context to this, let’s start at the first user touchpoint in the top left. When the user loads the app for the first time, we call this “loading the dashboard.” This fires off a flurry of different requests to the backend, but for the purposes of Parallel Linking, the one request we are primarily concerned with is “get external account link status” or “GEALS.” When the client loads the dashboard, it calls GEALS for all of a user’s links in order to surface any relevant updates (indicated by the text in the purple triangle). Moving down from there, we note that backend filters out Parallel Linking status updates for links that are already in a “good” state or links that don’t have a sync id (i.e., the user hasn’t yet initiated a sync for this link in the current session). In most cases, this should entail that the dashboard loads without the Parallel Linking banner appearing.
The meat of the logic in this diagram starts with the user initiating a sync. This most commonly happens when the user links a new institution but it can also start with an existing link needing additional MFA. From here, clients adopt the pattern of polling the GEALS endpoint with only one link id at a time to minimize backend overhead. As long as the backend returns a status of SYNCING, clients continue to poll. Once the backend returns something other than SYNCING, there are three possible scenarios:
- ACTION: some kind of authentication action is required. The client prompts the user to complete this action then resumes the syncing process.
- ERROR: backend encountered some sort of error when polling the link. Perhaps the institution is down or the backend failed to receive a response from our linking aggregator after a long period of time. Backend informs the client about the error, and the client gives the user the opportunity to “ack” the error by tapping the “Got it” button.
- Once the user acks the error, the client fires a “dismissal” request to backend, indicating that this link should no longer show up in the Parallel Linking status hub.
- SUCCESS or CONFIRM_ACCOUNT_SUBTYPES: in either of these cases, backend successfully retrieved the latest information about the link and is either confident in the classification of the account (SUCCESS) or would like the user’s input on account classification (CONFIRM_ACCOUNT_SUBTYPES). For example, the user might have linked a 401K and backend is unsure whether they linked a traditional or roth 401K. In both cases, the client auto-dismisses the link from Parallel Linking via the aforementioned dismissal request and then refreshes the dashboard so the user’s financial plan now includes the new information from the link.
Wrapping up
In working on Parallel Linking, I learned a lot about our linking platform and how the client side interacts with it. This ambitious initiative would not have been possible without the amazing team that built this across Design, Product, iOS, Android, Web and Backend. I’d like to give a special shout-out to Daniel, our Linking eng lead, whose input was critical in implementing a robust design on the backend. I hope this blog has helped demystify this mission critical component of our product at least a little bit. If building intuitive and performant products in the service of optimizing and automating all your finances excites you, come join our team!
Disclosure
This blog has been prepared solely for informational purposes only. Nothing in this material should be construed as tax advice, a solicitation or offer, or recommendation, to buy or sell any security or financial product. Wealthfront Software LLC (“Wealthfront”) offers a software-based financial advice engine that delivers automated financial planning tools to help users achieve better outcomes. Investment management services are provided by Wealthfront’s affiliate, Wealthfront Advisers LLC, an SEC registered investment adviser, and brokerage related products are provided by Wealthfront Brokerage LLC, a member of FINRA/SIPC. Wealthfront, Wealthfront Advisers LLC and Wealthfront Brokerage LLC are wholly owned subsidiaries of Wealthfront Corporation.
© 2019 Wealthfront Corporation. All rights reserved.