How our apps’ architecture is defined by screwups (and how to avoid it)
From the very beginning of my career — I was always fascinated with software architecture. In fact, that was my favorite part. Because coding is fun and all, but it gets old pretty fast. While building a bunch of parts/components that perfectly come together and form an efficient system — it’s almost like artwork for me. And as I evolved in my career, I realized that it’s not about the tech stack you choose, the database, or the folder/files structure (Even though these parts play a role here too). Architecture is about building efficient, scalable systems that last.
But as you may guess, it’s a hell of a hard job to figure out where will life take the product, especially at the very beginning. So we can only do our best to ensure that our products are scalable and can be easily improved without collapsing the whole thing.
And the best way to test it is to mess it up.
We all do it, sooner or later. Most of the time it’s not even an issue with a bad code or poorly designed system. Modern businesses have to be fast and flexible to survive. So when building a product — a lot of features and logic are established with the “Trial and error” method. So eventually, you’ll be requested an update, that just can not be done with the current state of the code. And you’ll have to put a lot of effort into a seemingly small fix (Unless you’re a person, that monkey-patches everything with no regard to the impact, it can have on a system 😉).
I also faced this issue a few times. I try to improve my approaches with every new product and always put a lot of effort into establishing a solid foundation, that can be bent to fit the most complex business rules without the loss in quality or performance. So my experiments led me to try and use an ORM on a new product that I’ve started some time ago. It seemed to save me a lot of time, providing a lot of basic logic out of the box, and let the development progress a lot faster. But as it usually happens, once the more complex business logic kicks in — ORMs can start causing more problems than they solve. I’ve found that customizing that standard logic was a very complex and time-consuming process. I had to trade-off a couple of good solutions just to stick with the original approach. And the further I went — the more obvious it was that I’m spending time to fight the tools instead of building a product. In the end — I realized that while the ORM was a pretty great tool for some tasks, it doesn’t fit in here and I’ll have to get rid of it until it’s too late. The project was still at an early stage, but some foundation was laid already. So I started evaluating how hard will it be to rebuild a big chunk of a system while having a minimal impact on the rest of the code. And that’s where I realized that it’ll be a true assessment of how good this project actually is. That’s when it hit me:
The best architecture shows when you screw up
No one is perfect and we all make a bad call from time to time. But I come to understand that in these moments you can see if you have done a good job with designing software, based on how hard will it be to correct a bad decision. I was able to rebuild that project to a better state relatively easily (Though not AS easy as I hoped). And I re-gained more confidence in my previous approaches 🙂. However, I’ve not given up on trying to improve my processes and approaches in architecture. Nothing will ever be perfect, so you can always improve something. I’m currently experimenting with a “Clean architecture” on a NodeJS app. And even though it’s too soon to say anything, it looks like we’re off to a great start.
OK, Any way to do it without screwing things up?
For sure. You can’t be prepared for everything, but I do have some tips that can help you to minimize the risk.
Start with a business in mind. That’s probably the main difference between an engineer and a coder. There’s always a bigger idea behind the stuff like “We need to add this checkbox to a user profile page”. So ask all the questions you can think of before starting the actual work. No matter how obvious or even foolish your questions are — Ask them. You may find out that the original idea was a bit different from what you had in mind. You shouldn’t have any assumptions, just facts. Because anything that isn’t crystal clear — is potentially a bad decision in the future.
Example: Imagine that you’re building some CRM-application, and you’re tasked with something like this: “We need a way for our managers to prioritize certain clients. Let’s add a featured checkbox-field to a customer page.”.
Now, if you approach the issue with a coder-mindset — it’ll seem like an easy update. All you have to do is to add a checkbox to some form on edit-pages, save the value, and maybe add a fancy badge to highlight featured customers in a list (If you’re like me and you like to exceed expectations).
What I propose is to address the issue from the side of the business. Try to think of why would your client need that featured-status. Should featured customers take priority over others in some searches or queues? Do we bill featured customers differently? Can anyone make the customer featured, or only admins can do that? Can featured customers even know that they are featured? If you get all the answers before you start — You will have much less room for an error.
You may think that if that’s the case — You will get this information right away, but that’s rarely the case. Sometimes, human psychology can play a trick on us. If a person knows the context of their business well — a lot of it seems too obvious to even mention it. But it’s a matter of perception and it won’t be as obvious to you. Another thing that is common in mid-to-big size businesses is when the business context is quite big for a human brain to account for all the little details. You could have experienced this yourself. You get a cool idea that seems so clear to you, but if you run it through with someone else — they can point out some issues/benefits/moments, that you haven't thought of. Sometimes, businesses can come up with a great feature or a problem solution, but they forget to account for some other business-logic that can be broken by their idea. This seems weird, but trust me. About 70% of feature requests that I got — were adjusted, changed, or revised at the discussion/discovery phase before the actual development even started.
Find a way to break your solution before someone else does it. Once you got all the answers and settled on a scope of work — You can finally think of a solution. And when you get an idea and set out some basic foundation for it — It’s time to run it through a bunch of “what if”-scenarios. This is a bit controversial approach, but it had never let me down. You basically need to evaluate how the business may evolve/change approach to whatever you’re building, which will result in breaking your code.
Example: Let’s say you’re building an online store with a basic product listing, cart functionality, and an ability to make an order. You’re done with all the basic stuff and proceed to create a cart and the order functionality. If you only consider the logic you have so far — You should be able to deliver a solution that’s working well. But the question is — for how long? You should take some time and think of a few ways this solution can evolve.
What if sometime after, a client would want to manage items in stock? What’ll happen if someone orders more items, than the store have in stock. Or what if two people order the same item at the same time and the total quantity will be out of stock?
And what if a business decides to store items in a cart for logged-in users (So you’d be able to log in and see items, added to your cart from a different device)? And what if you’re browsing as a guest user, add something to your cart, and then log in before making an order? Should we merge what you had with what you added before the login? Or clear the user cart and transfer items from the guest-mode?
And what if a guest makes an order using the contact info of an existing user?
What if you’ll have to add all these checks on top of your current code, in the future? Will it damage the performance or the UX of your app?
As you can see, there’s a lot of ways to “break” a seemingly simple functionality… Some of these questions can be discussed with a client and further simplified. And other stuff should just be taken into account while designing the architecture. That way you develop what is required right now and leave room for extension/improvement in the future (So you wouldn’t have to rebuild the whole thing from scratch because it doesn’t fit the new business requirements).
Some people don’t agree with that approach. They say “premature optimization is the root of all evil”. And while there’s a lot of truth in that statement — Some people like to apply it in cases where it doesn’t exactly fit.
I get it, if you’re building an app for some startup and don’t expect more than 1k userbase within the first year after the release — it may not be a good idea to go hard on optimizing the website for 1 million users a day just yet (Especially when the half of the functionality on your fastest app isn’t implemented yet 😄). But at the same time, you can’t call a potential problem irrelevant just because you don’t have it now. Imagine if developers were thinking “…Well we don’t expect more than 1k users for the first year or two, so it’s ok if our entire system goes to sh*t with 1001-st user…”.
I like to think in terms of business. When people start a business — They always have some vision of a future in mind. Just imagine a business that would approach investors like this:
- Hi. I have this awesome idea for a website. Please help me with some funding.
- Cool, so what’s the website about?
- Ah, it’ll be like Facebook, but for the farmers.
- Ok, and why would the farmers need your app?
- Well, cause it’ll be awesome.
- Awesome how?
- I don’t know. We’ll think about it once we have a website.
- Have you done some market research? Is there a demand for it? Have you interviewed any farmers?
- Dude, come on. Why think of it right now. We just need to build a website. We’ll worry about demand and market stuff once we get to that problem?
- But what if no one needs it?
- I’m telling you, it’ll be awesome. You’re running ahead of time. We need a website right now, why worry about the demand?
Sounds ridiculous, right? However, that’s how software development can be addressed sometimes. So as long as you’re not get carried away — It’s crucial to have a product's future in mind. Because if your clients’ business operates that way — why shouldn’t you?