Balancing The Desire To Under-Engineer and Over-Engineer
Software development is a balancing act of spending too little time planning out a project and spending too much time. If we under-engineer the project by not planning enough, then we will end up with lots of technical debt. The result will be lots of time wasted dealing with that technical debt. Think of it as building a house with a really shaky foundation. You end up spending more trying to fix things than it would have cost doing things properly the first time.
On the other hand, over-engineering is also a problem. It is very easy to get into a mindset of spending lots and lots of time planning. The project then gets delayed because people become hesitant to start it. The buzzword for this is “analysis paralysis”. The result is the same as under-engineering: wasted time. Think of it like planning out the foundation for a skyscraper… when you only want to build a 3 story house. Not exactly a worthwhile endeavour.
The challenge is: how can we get just the right amount of planning done? There’s lots of great materials out there on planning software projects, but what I want to talk about is preparing for being wrong. The world is constantly changing and that means companies and products need to change. The planning you do for a project today could be completely insufficient for what that project needs to do 1-2 years from now. No one can predict the future perfectly, software developers included.
That may sound like an argument for always choosing to over-engineer. It isn’t. While it sounds great to always be prepared for any situation, you still have to deal with the fact that there are an infinite number of possible situations and there isn’t enough time to prepare for everything. What needs to happen is you plan for the things you can think of, but leave room for dealing with the unexpected. To use an analogy, cities don’t just get built in the middle of nowhere. Building dozens of skyscrapers in a low population area risks leaving you with dozens of empty skyscrapers.
Cities don’t start off as cities. They start off as a series of houses. The people who built those houses only planned to have those houses. If no one else moves into the area, then those just end up being the only houses there. No overinvestment in planning.
If more people move into the area, then more houses get built. If there are too many people coming in, apartments get built instead. If even more people move into the area, then some buildings get torn down and bigger buildings are built to accommodate.
If yet more people move in, those big buildings get torn down and even bigger buildings are built. This cycle continues until you have a city.
We can do something similar in software with what are called microservices. The idea is to keep each part of your system isolated so that they don’t depend on each other. For example, in an e-commerce website I have many different aspects of my system such as orders, recommendations, product creation, etc. I could keep each of these systems independent so that if the system for recommendations has an issue, the systems for orders is unaffected. People should be able to buy new things even if I can’t give them recommendations.
Alternatively, many developers opt for what is called a monolith. That means all the systems are tied together. This is to keep things simple and easy. It does that… in the beginning. It makes lots of sense if all you want is a single house.
However, what happens when you need a second house? With the monolith, developers essentially attach the second house to the first. A third house is then added. Then a fourth. You start running out of land so then houses are built on top of other houses. Eventually you end up with something like this:
In this system, if my recommendations system goes down on my e-commerce site, the entire site will most likely go down:
Because microservices are isolated, it gives you more options when your business needs change. Say your recommendation engine worked great for one million users on your site, but then it started crashing because it couldn’t handle ten million users. With the monolith, fixing it is challenging because you need to take into account how your changes will affect every other aspect of the system.
With microservices, you can choose to rebuild it entirely with the confidence that it will not affect your other systems.
The advantage is clear. By reducing a system into bite sized (or micro) pieces that are mostly isolated from each other, we reduce the consequences of any of our systems having issues. We also can maintain them without worrying about causing harm to other areas. And more importantly, we can choose to plan each system individually and start construction on each system individually. We do not need to plan out an entire system (or city) all at once. We plan as we need to.