Overengineering a Web Application is Easier Than Ever
Innovation in software happens fastest where scale demands it — but do those innovations tackle developer problems instead of user goals?
Development teams are larger than ever, as is the impact, value, and reliance on what they create. Tech companies top the global market cap list, and we’ve seen several join the trillion-dollar club since 2018.
Whether you look at the rising importance of software as an opportunity or a burden, it’s driving the conversation. The libraries, services, and frameworks that spawn conferences, articles, and communities are often a response to that primary problem: scale. Like Google processing an estimated 70,000 searches per second. Or hundreds of hours of video being uploaded to YouTube each minute.
It’s easy to point at the tools and patterns behind the big companies as the right way. If React handles that kind of throughput for Facebook, why doesn’t everyone use it?
If you make too many assumptions about the future early in development, it’s easy to overengineer a solution. It’s like building a 747 to handle the duties of a helicopter and later figuring out that a boat would’ve been the best option.
A Promise of Scale and Speed
Projects start a bit like browsing supermarket aisles. Pulling in React to manage the front end, letting Webpack deal with bundles, breaking a platform down into microservices, setting up load balancing, incorporating machine learning into recommendations; the list goes on. With the flip of a switch, popular services, frameworks, and libraries come with the promise to:
- Handle traffic for an exponentially growing user base
- Abstract away the painful parts of vanilla languages for long-term development
- Adopt the newest technology and have access to a better talent pool for future hires
- Modularize code and the teams working on it
- Give the sales team license to use buzzwords to describe a platform
If we can make an architecture decision now to cover our bases in the future, why not add everything to our figurative shopping cart?
The Problem with Overengineering Software in Practice
By the time scale is really needed, or a methodology comes in handy for a large team, or a framework is used to its true potential, the landscape has changed. Itamar Turner-Trauring said it best in his article, “Let’s use Kubernetes!” Now you have 8 problems:
“People tend to forget that ‘when we have massive scale!’ they also have massive customers. So, solving that problem later is easier with more revenue, more engineers, and a clear picture of the problem (rather than a guess at what might be a problem later).”
It all comes back around to You Aren’t Gonna Need It, a common refrain among programmers seeking to avoid pre-optimization.
Layers of Abstractions
Even with a clear idea of the problem, adding engineering complexity carries side effects.
Take something we’re a fan of in styled-components, a library that helps clean up common CSS woes. For newcomers, though, it’s hard to figure out when things go wrong. Instead of just being a CSS problem, now it could be a CSS problem, a JavaScript problem, a React problem, or a styled-components problem. You have to weigh the benefits against:
- New ways for things to break
- Additional spin-up time for new developers
- The need for long-term maintenance and upgrades on the new package
- Support ending for the dependency
Story Time: Creating Code School
When we founded Code School, every single course was a separate Rails application. New course? Just copy the last one, and maybe try to iterate and improve a bit before release. It was a nightmare to maintain, especially when each needed a security update for Ruby or Rails. After a stint with Backbone and several overhauls of the functionality, a shared course engine was finally built — years after launch, and with millions of annual recurring revenue.
Looking back, it still feels like the right call. Several truths had changed by the time those architecture decisions were made:
- We’d changed the entire business model, moving from one-off purchases to subscriptions (and the expectation of regular releases)
- Course creation teams grew from three or four to dozens
- AngularJS and front end frameworks had matured to the point of being reliable
- We’d tried dozens of features and interfaces, and finally settled on how unique each course could be — both in design and content
It would be both a wasted effort and a hindrance to iteration if we were to try and lock everything in at the outset.
Architectural Influences to Consider
So, if it’s easy to overengineer and hamstring ourselves in the future, what’s worth considering for a new project? Here’s a handful of details to put on a pedestal, be wary of, or think about in context.
Factors Worth Weighting Strongly
These bits are the bread and butter. When you make architecture decisions for the present and future, pay special attention to these items.
Key MVP Objectives
This one’s a given. Engineering that tackles a platform’s primary value and goals makes the cut.
Features Dropped for Time or Budget
Things will be dropped during a development cycle, but it’s worth tracking why. Features dropped for time are usually priorities after launch, so keeping supportive architecture as part of the plan makes sense.
Six-Month Roadmap
If it’s a year out, expect the potential for a rewrite. At six months, it falls closer to those features dropped for time or budget — if the organization has a reasonable track record of sticking to short- and medium-term plans.
Organizational Uniqueness
Company policy or unique IP tend to win out, even over key objectives. The value of organizational secrets will (and should) win out over just about anything.
Business Phase
Planning an exit or a merger? We’ve been through the process of combining three systems before: Active development leading up to major business shakeups is lost to the sands of time.
Team Size
Patterns and architecture should make sense for the number of people working on a platform regularly. You probably shouldn’t have more microservices than team members.
Factors that Are Usually a Trap
On the other side, here’s a handful of influences that shouldn’t grab your attention.
New Frameworks and Libraries
No matter how flashy or how many problems it solves, resist the urge to put a new technology in something important. Internally, we have a rule to use new things in (at least) something internal of reasonable size before deeming it worthy.
Fringe Platform Features
As opposed to key MVP objectives, nice-to-haves and pet features shouldn’t drive larger platform planning decisions. Something like a single visualization hidden away doesn’t call for a full-fledged graphing library.
Future Scale
This is a callback to our YAGNI discussion. It’s not necessarily cost effective, but modern servers can handle a lot. Scale means different things for different applications, and preemptively tackling it could see you solving the wrong problem.
Factors Somewhere in the Middle
This category is a toss-up: your mileage may vary, and it varies from place to place. Nonetheless, these are important influences to weigh.
The Job Market
Once a product is out, the real work begins. A project’s key objectives are bigger than the language they’re written in, but finding developers to maintain a platform is a real concern.
Take Ruby on Rails: We’re still big fans, and it’s still a rapid path to an MVP — but it’s harder to find developers for today than it was ten years ago.
0.X Open Source Software
Before hitting version 1 (and even after), open source software runs the risk of changing without much warning. For the first version of Cisco’s technical training platform, we worked through several data adapter rewrites in the early days of Ember.js (version 0.9!)
Second-System Effect
Having the opportunity to build something again is a developer’s dream come true, but the promise of getting it right often leads to bloat and overengineering.
In addition, all of the formal and undocumented features of the first system tend to creep back in as requirements of the second. You often end up just rebuilding the first again.
Balancing Engineering Decisions
Articles looking at the strategy behind software continue to highlight the same thing: Technology outcomes are a series of choices and tradeoffs. Our foes — scope creep and overengineering — pop out from that tree of decisions.
It’s easy to fall for a new framework, or mirror the technology stack a big tech company uses, or build features to cover unrealistic revenue projections. Time and again, we’ve seen those decisions lead to lost time, inflexibility, technical debt, and a host of other issues.
Overengineering places software in an adversarial relationship with iteration and hampers your ability to respond to business problems with clarity. A jet, instead of a boat.
WordPress: The Application Framework You Shouldn’t Be Using
WordPress is good for some things, and bad for so many more. Learn why you should only be using WP in specific circumstances to avoid disasters down the road.