January 1, 2021
Senior Front End Engineer
Updater helps people move by providing an digital experience to complete all necessary moving tasks. My work at Updater was highly collaborative and consisted of a couple main projects:
Part of our mission was to develop highly efficient flows that allowed users to complete a variety of moving tasks, from setting up home internet to finding a moving company. We needed to develop an analytics framework that could be integrated into any front end codebase, would unlock product analysis capabilities, and allowed us to run experiments.
I led the development of our analytics library built on top of Optimizely X.
Our analytics framework had several main features:
Analytics tracking was fairly straight forward. A track function was provided that could be fired anytime with domain, object, and verb parameters. Additional metadata was also allowed. When the track function was fired, it would trigger a handful of third party tracking tools including Optimizely. Track functions covered basic analytics tracking use cases.
Metadata resolution was a bit more complicated: every track call required some additional metadata about application state and certain metadata was only available at specific moments during the applications lifecycle. We needed additional metadata attached to every event because certain events would be filtered in analysis based on the type of user or the presence of a specific user attribute.
To solve the metadata availability issue, the analytics library provided a metadata resolution function that could be instrumented anywhere in the application and would extract metadata anytime a track function was fired. For example, we instrumented two resolution functions: one at application startup and another after user authentication. When the application started up and before user authentication, we could fire events with browser and page information attached. And after user authentication, we could fire events containing browser, page, and user information. Metadata resolution functions enabled us to setup metadata resolution once, without having to pass that metadata along for every track call.
And finally, experiment setup was implemented by creating a thin layer on top of the Optimizely X SDK. It made sense to lump experiment setup with analytics tracking because they are so closely tied: conversation events were always analytics events.
The single biggest challenge the front end team faced at Updater was updating our Angular 1.6 application to React. The kicker: we had to do it without disrupting our fast-paced product testing process. We had to replace the engine while the plane was flying.
I played a key role in architecting the move to React via Next from Angular 1.6.
The key to moving off Angular was isolating and separating application state logic from view rendering, making it completely framework agnostic. We did this by developing a hand rolled state management system that we called Bones. Bones emphasized small, use-case specific, reactive state loops. With Bones, we were able to lift a specific application use case (ex. reserving TV/Internet) and use it in the legacy Angular application and the Next application. Over time, we would lift individual use-cases out of the Angular application, reuse the state management module, and reimplement the view in Next. We usually did this when we had a new experience to test, so there was virtually no disruption to new product development.
We ran a ton of experiments at Updater. Basically every new interface we built was done as an experiment, testing it’s performance against the status quo.
I collaborated with other engineers and product owners to develop new front end experiments.
In order to build new experiments quickly, we had to be very good at developing reusable, easy to understand React components. We leveraged typescript to improve developer experience by making component interfaces more transparent in code editors. We built new flow pages as components so that they could be reused on the next experiment.
We also became really good at writing code for deletion. Similar to concepts used in state management, we focused on separation of concerns, especially between experiment variations. We tried to use fewer conditionals in our experiments so it would be easier to delete failed code and mainline successful code.
And finally, we had to develop a strong design system and component library to keep our experience consistent from experiment to experiment.
In building out a new application brand, we needed a system to drive it forward while bringing new teams and projects onboard.
We coded the design system styles first because it needed to be built to last. We started with vanilla CSS styles so the system was framework agnostic. With a library of CSS styles, we could easily transition to another view framework in the future, or utilize our styles in an existing codebase written in a different framework.
The CSS first approach is counter to an inline or styled component approach to building a design system. Because our codebase utilized several view frameworks and our company’s interest in building something that lasts longer than any one view rendering library, we opted for a separated approach for our design system.
We developed inside Storybook with CSS and HTML only, using React only as a “template renderer”. Once the design system CSS library was created, we built separate React components to use in our React codebase.