TL;DR: This is a story from my own professional experience about how picking wrong libraries, frameworks, infrastructure services and development approaches causes damages to a web application, thus doubling time and cost of its development.
It was a brand new e-commerce project with a fresh business idea, which involved development of a web application. The stakeholders were two ladies, and one of them has known a guy called Thomas, who is a programmer. Therefore, they invited him to the project as a lead developer (CTO is you will).
At that time I was working as a React front-end developer in the same company where Thomas did. So when he offered me a post of the front-end developer in the new project, I gladly accepted it.
Thomas is a cool buddy, but, to my surprise, he had significantly less experience in web than I did (his specialty has been robotics). But his level of influence and responsibility in the project was far greater than mine (no surprise though, unlike me, Thomas had his skin in the game).
So this is where the things became "interesting". BUT, before blaming others, I want to highlight my own programming mistakes in that project. So let's finally get started!
Front-end
And we'll start from the client part of the web application. Since, as I've already said, I was a front-end developer, this is where I did have power of taking technical decisions.
Since the very beginning we chose React as the primary framework for our front-end. Maybe it was because humans are bad in predicting the future, or maybe I just wasn't smart enough, but I didn't bother with selecting a React platform and simply went with create-react-app.
After some time, when our front-end grew a bit, it became clear that there is a lot of routing logic (of course, our e-commerce application was never meant to be a one-pager!). I then understood that it would be way more efficient to go with something like Next.js to handle that heavy routing. Luckily, the Reach Router I applied to the front-end was good enough to satisfy our routing needs.
(As an excuse at this point I can say that although create-react-app might not be the best choice for our front-end, it was working just fine. With some extra political will it wouldn't be too hard to move it to Next.js, and it would neither be necessary to rewrite the Reach Router stuff right away. We'd be able to do that gradually.)
The next topic was the user interface. No surprise that it was my job to convert that beautiful UI sketches our designer provided to us into responsive and cross-browser markup. It was time to choose a UI library, and I went with Bootstrap. Everything was going good, I was developing the markup page after page, section after section. I even started to override the default Bootstrap theme to match our specific design.
Then it was time for dynamic elements, such as toggleable sidebar menu for mobile screens, for example. The truth is that when it comes to dynamic UI elements, you almost end up with something React-specific like Material UI instead of generic Bootstrap. Thus, I introduced another dependency to our front-end...
Till that moment a lot of markup code had already been written in Bootstrap. What I could do was to declare all the Bootstrap code as legacy and to forbid the team to write new code on Bootstrap. The long-term plan was to gradually replace the Bootstrap code with Material UI and eventually remove the Bootstrap dependency. It never happened, but only because there were a lot of more serious problems in the project than the Bootstrap vs Material UI stuff, so nobody really cared about noticed that.
Finally, another mistake I take 100% responsibility for was not going with TypeScript since the very beginning. The data of our e-commerce application had a very much defined structure, so it was a no-brainer to eventually include TypeScript to our toolchain. Luckily, create-react-app provides an easy way to add TypeScript to an existing application.
And you have already guessed what I decided to do with previously written vanilla JavaScript code: declared it as legacy and started a campaign of gradual replacing that code with Typescript. And yeah, I can confidently claim that most of the vanilla JavaScript code has been replaced with TypeScript till the moment when I left the project.
Back-end
Now it's time for some hardcore }:)
At the beginning of the development of our project there were only two people in the dev team: our tech lead Thomas and me. Early on we split our coding responsibilities by a simple principle: front-end for me, back-end for him. Despite his not so rich experience with web, Thomas has had some knowledge of AWS, so it was a logical step for him to pick AWS as a back-end infrastructure for our project.
Initially Thomas didn't share the details about our back-end with me, nor I thought that was necessary. In fact, I trusted his back-end skills.
But what was happening at that stage was while I was successfully completing my front-end milestones, I was periodically hearing Thomas complaining about his struggles with the back-end part. And not because of laziness at all: it became usual for me watching his red eyes craving to close during our video calls. But I still had no idea what was going on with back-end. What actually happened was that Thomas sold his soul to AWS Amplify.
(Not to blame him though. In fact, all of us must pass through this circle of hell due to the lack of experience. I just did that somewhat earlier than him.)
However, I still didn't rush myself to take a look at the back-end. So Thomas took a decision to involve more people to the project. Thus, two new men joined our team: Alex and Barry.
I have known Alex from another project where we used to work together, so he responded to my invitation for a position of the second front-end developer in the project. Actually I won't say much about Alex, he must be the wisest person in our team because he's the only one I can't blame in anything :D
The tough guy Barry had always had in Thomas's eyes a reputation of a super savvy back-end dev and (what will become important later on) a god of Google Cloud Platform.
In the meanwhile, I realized that our application was becoming brittle without proper quality assurance. It became clear that it was impossible to write meaningful end-to-end tests on Cypress without integration with real back-end. This is when I went out of my front-end capsule and faced the AWS Amplify devil.
The first thing I didn't like was that all our back-end logic was split into modules (product, order, user etc) so each module was deployed to an AWS Lambda. It was still not that bad since each of the Lambda modules was written in plain Express (although even then there was no acceptable way to run the back-end logic on localhost).
Then Barry suddenly announced: "We are now going to write our back-end code in NestJS!". I didn't have anything against NestJS, but I was wondering how they were going to split a monolithic NestJS app into Lambdas, as we had it in Express.
Even though it resulted to be only one Lambda for the entire NestJS server, maybe I don't understand something, but man, functions are not meant for that! Not to mention that it became much harder to run the server on a local machine.
This is when I started to talk about changing our back-end infrastructure. Thomas wanted to stay within AWS, but we couldn't find a better deployment method for the NestJS back-end on the platform (write in comments your furious objections to this right now!). So I told the team: "It is not going to perform well this way. If you want to stay with Amplify so much I vote for sticking with Express for back-end instead of NestJS."
Right below that Slack comment of mine Alex wrote: "I have worked with NestJS, it is awesome! I vote for NestJS!". Sorry, Alex, I lied a couple of paragraphs above, I'm still blaming you :)
But that was not the only problem with AWS Amplify. Because Amplify's primary evil is its heavy configuration. We ended up having more configuration code committed in our repository that the feature code. We were spending more time f@cking with the configuration than actually developing things. And we still could not make the Cypress tests running within Amplify, I am not even sure if the TypeScript decorators stuff of NestJS eventually worked...
Alongside with Amplify, another evil thing of AWS is DynamoDB. And I'm not even talking about its vendor lock-in model. The DynamoDB API itself is probably the most disgusting API I've even seen.
(What I did like in AWS, however, is Cognito. It is OAuth2 compliant, its SDK supports both in-browser login as well as popup/redirect to the authorization server (see OAuth 2.0 specs). And, unlike my favourite Auth0, it doesn't send plain user password via the network!)
Threatened by all this AWS stuff, I started to look for better alternatives. Thomas had always wanted to stay within a single cloud platform for not having to bother with too many accounts. Long story short, between GCP and Azure I chose the latter due to some factors that are beyond this story since we never applied Azure to our project.
What is important is that I had no doubts that switching to Azure will benefit our project, and was trying to explain that to Thomas. I managed to create an Azure account, deployed our front-end there so it was partially working (after all, I spent only 1 evening for that).
Thomas refused to consider all that, he didn't want to take such risk. As he told me some months later: "I thought you were just a front-end guy. I thought you didn't know how to do all that, otherwise I wouldn't be so stubborn.".
Thomas, however, was not the only resistant one. The mastermind Barry also continued to struggle with Amplify, despite his experience in GCP. As for me, during the next 1.5 months there was enough front-end tasks for me which allowed me to distance from the back-end for this time.
After the mentioned 1.5 months passed, I see Thomas posting in Slack: "Barry has moved our project to Google Cloud Platform, team get ready for a group call where he'll explain us about the new infrastructure."
Oh really? So when I was talking about switching the infrastructure, nobody wanted to listen, but when Barry on his own took the decision to move to GCP, it has suddenly become welcomed? What that means, guys, is that eventually I was right, and you just wasted 1.5 months of the project time. I took that personally (not proud of that though), and told Thomas that I was leaving the project. Thomas, however, convinced me to stay.
By staying in the project, I agreed to the new GCP rules. Barry took care of all the deployment and infrastructure processes. Our NestJS server could now be run locally. Nevertheless, after getting known with GCP, I understood that I was right in choosing Azure.
We used Firebase Authentication to work with users, and it worked just fine. I was even able to perform a fancy trick with it in order to close a ticket. The disadvantage of the Firebase Authentication is that it is not OAuth-compliant, which ultimately means vendor lock-in for a project. However, that didn't affect our development work.
The things were harder with Firestore. Similarly to DynamoDB, it is a proprietary DBMS which means what? Right, the vendor lock-in. And this time it made its impact on our work. Firestore has its own SDK for Node.js with its API, which is, however, not as horrible as the one of DynamoDB. Still, due to being proprietary, Firestore doesn't have decent development tools (e.g. ORMs) to simplify developers' lives. You can even agrue with me right now claiming that there are ORMs for Firestore. But they definitely can't be as good as the ones targeting open DBMS like MongoDb or MySQL (let's take Prisma, for example).
Another consequence of using Firestore is having to run the Firebase Emulators on a local machine, since it was the only way to work with the proprietary Firestore locally during our NestJS server development. However, the emulators simulate not just Firestore, but other Firebase services as well. And I have to admit that in case of the Authentication it was quite convenient to work with it locally, unlike it would be possible with Auth0.
There were a couple of other services applied to our project, like mailing or file storage. I can't judge anything about those since I almost didn't work on those parts.
Now, some of you might argue with me since many paragraphs ago: if we were developing an e-commerce application, why the hell did we develop a custom server instead of applying something like headless BigCommerce or Shopify, or at least leveraging Vendure? And you are absolutely damn right! Moreover, I'm taking my part of responsibility for this counter-productive decision. However, if we did that right since the beginning, would we now have such an extensive overview of the popular development tools? ;)
And you, what is your experience in selecting right or wrong tools for web development? Share your stories in the comments!
Also, don't forget to leave your reaction to this article and hit that "Follow" button!
Don't stop coding, don't stop growing!
Top comments (0)