It’s impossible to design the perfect product from day one.
At some point, you realize that the type of user you care about has changed, or that you built something designed for 1999, or that the designer you hired has better ideas than you (the engineer) did.
It’s all too easy to forget to build for the future, especially when you think you know exactly where your product is headed.
A lot of things have changed design-wise and product-wise over the years at Amplitude, which got us thinking again about our front-end architecture. It’s been a long time coming, but we’ve designed a new front-end architecture – which we’re calling Lightning – that is capable of handling all the changes we have on the horizon without sacrificing how fast we can build.
This post is an initial look at our motivations for coming up with a new front-end architecture and the basic approach we took. We’ll be talking more specifically about what we’ve learned along the way in our upcoming posts.
Where We Started
When you think you know where you’re headed, you often end up writing software that’s difficult to change and restructure. That’s what was happening with the front-end architecture at Amplitude.
Amplitude was originally architected and designed in a very ad hoc manner. It started as a small number of tabs: user composition, events, funnels, retention, etc. Over time, the tabs multiplied as the product evolved. Retention blossomed into retention and stickiness. User sessions and timelines were born. A SQL query tab emerged. And a growth discovery engine. And onward, and upward….
Each tab was a standalone view of the world. Certainly some page components were reusable – datepickers, table views, core graph rendering – but a substantial number of components were one-off. At one point, we had not one, not two, but 34 different datepickers in the product. Larger components often duplicated smaller common building blocks rather than sharing them. To make matters worse, each tab fetched and managed its own state, each with its own local flavor thrown in for how to handle it.
So why was this a problem? Everything slowed down. We faced serious business costs every time we improved or refactored existing features:
- Development was extremely time-consuming.
- It was really hard to predict how long development would take.
- It was easy to introduce regressions.
After living in this world for a while, we decided it wasn’t sustainable.
To be able to move quickly, we needed an architecture based around components and reusable building blocks. This became Lightning’s core principle: to create building blocks, not views. Just as important was the ability to reorganize the way things were laid out without needing to rebuild them from scratch and to improve features horizontally without ending up in a world full of painful regressions.
With those three goals in mind, we also had to step back and consider what tools and best practices we needed to incorporate into Lightning. One of the big changes we decided to make was to move off of Angular and onto frameworks/libraries with a smaller footprint, that would give us flexibility to pick and choose the best tools for the job. React and Redux were two clear winners in this regard. We’ll go into more technical detail about this in a future post.
One Block at a Time
After fleshing out Lightning, we dove in and started building a feature in our new architecture.
The first feature was painful for obvious reasons; not only were we figuring out best practices from the ground up, we also needed to build a large library of components that would be used moving forward. But the investment started paying off when we began building out a second feature. And a third. And a fourth.
As of now, we’ve built several features in our new architecture and migrated several older ones from the old. And we’re seeing the results. We’re seeing improvements in consistency, reliability, and speed of development. We’re making improvements horizontally across the features built in the new architecture much faster than we can on features still running on the old legacy code. And we’re migrating legacy features very quickly using all the groundwork we’ve laid.
We’ve seen a lot of improvements with Lightning already, but here’s a secret: The key advantage of our new architecture is yet to come.
A few months back, we hired our Head of Design and tasked her with the challenge of rethinking what it means to be an “easily accessible” analytics platform. How do we build a platform that’s easily understandable for novice users? A platform that is a joy to use? A platform that teaches our customers about the value of analytics?
We don’t yet have a sense of exactly what the answers are to those questions, but we do know that it means change. We might substantially overhaul our information architecture and reorganize everything in the product. Or we might not. The point is, on the front-end engineering side, we’re setting ourselves up so that we can tackle that change with ease. By decoupling components and state from their location in the product, the specifics of the new design don’t really matter. Sure, there will still be lots of work to do when a design finally lands, but we’ll be able to move quickly and to be more flexible. And when design eventually throws us a last minute curve ball, we’ll be ready to handle it.
Have your own opinions on front-end architectures? We’re hiring front-end engineers and we’d love to hear them! Check out our careers page for more info.