My Accidental Entrepreneurship - Technical Decisions
I have written about how I started my software business and how I decided on the pricing and business side of things. However, many technical questions needed careful consideration, as they significantly affected the performance of the whole endeavor. This post might be boring if you are not into techy stuff. I will tell you more about the operation of the business next week.
How I Stopped Worrying and Learned to Love Go
The first and most challenging problem was the decision on the code's programming language.
I made my initial prototypes in Go to quickly produce a simple executable without installing additional frameworks like .NET. My experience with these automatically updating frameworks was that they introduced a severe vendor risk, as they could break the application anytime without warning.
I could have written it in C++, but I wanted to focus more on the algorithms and use pre-existing solutions for simple tasks. I could also have used Python with PyInstaller to bundle an executable, but it seemed like I was about to use it for a job it was not designed for.
My customers mostly used old, low-tier laptops in their work, and my software had to run a complex algorithm in minutes, so it was great to use Go's multithreading capabilities. Once in a while, someone called me to say that the software was running endlessly without a result, and after inspecting their datasets, I was always able to improve the program so it would finish its job in an acceptable time. So, using Node.js and Chromium to build the software was entirely out of the picture.
After creating and validating the prototype with potential customers, I wanted to make a proper desktop application with a graphical user interface. I looked up potential libraries and even contemplated switching the programming language again. My main concern was still the vendor risk; I imagined the day when a Windows Update would knock out my software and all my customers would start to bombard me at the same time while I franticly tried to triangulate what they had changed before they had written "minor fixes and performance improvements" in their changelog. Sounds familiar? Sorry for the unexpected flashbacks.
That's how I ended up sticking with Go and using the GTK toolkit. It allowed me to quickly assemble a usable user interface, although it was a bit ugly. In fact, that was the central theme I heard back from customers later on: "It's not pretty, but I could understand and use it in seconds."
Go Big or Go Home
To run the business, I needed systems to handle registrations, activations, license renewals, automatic emails, and a whole set of other small things. I didn't want to rely on other parties if it wasn't strictly necessary, nor pay money for services I could implement in a day or two. I know what you're thinking: this is what most software engineers say; they tend to underestimate the complexity of every problem. I could have used pre-existing solutions, but I wanted to quickly change things while the day-to-day operations formed based on my early experience. Most mature solutions were loaded with features I didn't want to use, at least not in the first few months. I was very cautious in using the time I dedicated to growing the business, but I also wanted to have fun.
I decided to code everything else in Go, too. It was easier for me to remain in the same language and, hence, mindset while switching between the maintenance of the application and the complementary services. I anticipated finding a developer with only one language criterion would be easier when I decided to hire someone or sell the business.
It was evident that hosting these services was not a good idea; I used a cloud service provider instead. I wanted to focus on building the business and not restarting and updating servers, so I implemented what was necessary with serverless solutions on AWS, mainly utilizing Lambda workers.
The few areas for which I used an external solution:
Invoicing: it's highly regulated, and there were stable, cheap solutions available
Transactional emails and newsletters: I estimated that I would remain in the free tier for a long time and wouldn't have to worry about SPAM management
Website analytics: I used Google Analytics with a custom Tag Manager magic as I had previous experience with them.
Push and Pray: My Battle with CI/CD
My main concern was ensuring that I could deliver updates quickly. For the "additional systems," I was satisfied deploying new versions from my computer through AWS CLI commands. There were just a handful of them, and as they were organized as microservices for small tasks, I rarely touched them after the first versions.
The desktop application was trickier. I implemented a self-updating mechanism into the software because I knew I couldn't rely on the customer to update it manually, especially when I released new versions daily.
I needed to compile the code, bundle it into an installer, pack it in a different format for the automatic updates, sign it, and upload all of these to S3. At first, I did all of these from my local environment. Still, after I got into some trouble because the unfinished parts were getting into production, I decided to automate all steps in a CI pipeline on Gitlab.
It took countless hours to get it right, mainly because of the technological decisions I made earlier. I had to assemble the software on a Windows machine - as I realized after a depressing day spent with different Linux images - and it wasn't easy to install everything before I could start the primary process. I had to make several workarounds in the building process; for example, I had to duplicate the package installation line, as the dependency tree resolution always messed up the first time, but got it right the second time. Specifining exact versions, or any clean solution didn’t work, just this ugly hack. And I still needed to fix a config file with a fine-looking sed command. The fact that I had to kill random tasks that might not stop after the compilation was done was just a minor inconvenience after all of this.
The pipeline became complete after I had set up a "staging" deployment to upload the new installer to a different location. The whole thing looked unstable. Every time I ran it, I feared it would break somewhere, and I would have to start the detective work again. Fortunately, it happened far less than I expected and not once when I was in a hurry. Looking back, it would have been better to create a custom Docker image, which I could have used to run the building process. It seemed to be much more effort at the time, but now I know the time invested would have been worth it.
Conclusions
If I had to start again knowing what I know now, I would most certainly go the same way I did. Go is a great language to work with, and new, more stable contenders for UI frameworks have emerged over time.
Having had more experience with .NET, I might have used WPF, even though I would have to prepare for the customers' inevitable "it worked yesterday" calls. In return, I would have received a more mature framework with better documentation. In this case, I would use Python for the microservices, as finding someone to manage them is easier.
I think the best language and framework for anyone is the one they truly enjoy working with. The most important thing is to know its risks and limitations to ensure healthy product development.