TaxReco would like to share our experiences on our product evolution and our technology’s critical role to be a catalyst to evolve our product based on our customer needs. I will try to document our journey so far and break this down into milestones and, more importantly, the challenges in terms of technology and the critical architectural/design decisions we took to overcome those. Here are the steps our product has gone through – stages of evolution.
(Photo by Júnior Ferreira on Unsplash)
First up, the “Aha Moment!”, a point where the very first idea germinates in someone’s mind and resonates: “Hey, I think there’s a problem that needs to be solved. Can we do something about it?”. At this point, we did our initial market and competitors’ analysis in this space to check if this problem is indeed a problem.
There were numerous white-boarding sessions, stories, use cases we discussed and roughly chiselled out a story for the making.
One of the crucial things we ensured was to stay away from technical aspects at this stage. We purely wanted to understand the domain and define a clear scope at a high level. We did not want to jump into architectural decisions at this stage and not have our technology decisions influence our problem statements. As a result, we were able to define the scope of the problem. One of the main takeaways for the technology team was to understand the domain and frame a common vocabulary for the entire team. We strongly appreciate the importance of ubiquitous language in building a product so that there are no misunderstandings that may prove costly at later stages.
Proof Of Concept
(Photo by Med Badr Chemmaoui on Unsplash)
This was the start of the journey for the technical minds to apply technology to the defined problem. Firstly, we needed to define the architecture for the overall software. Numerous aspects influenced our decisions. I’m listing a few critical ones here
- Is this a Web App or a Mobile App?
- Is this a SaaS on the cloud or an on-premises offering?
- What’s the data volumes we’re looking at? Best, Average, Worst case?
- What are the various aspects of the application and data security?
We came up with a simple/lean architecture of a Web Application, talking to a bunch of secured REST APIs in the backend with a simple database as its data store – a de-facto standard pretty much these days.
Next up, we have the architecture ready; which technology stack to choose?
There were several white-boarding sessions, healthy discussions, constructive debates on which technology should we choose.
We felt we must play to our strengths (i.e., we choose a technology that we all are familiar with or at least confident about). The following was a checklist we used:
- Does this technology fit the architecture?
- Do we have enough frameworks/toolkits on this technology – of course, no one wants to re-invent a wheel unless it’s a square wheel!
- Do we have enough community support for the technology?
- Does this technology excite us (and possibly attract fresh talents in the future if we need to ramp up)?
- What are the commercials around this technology?
We felt it was always important for us to stay on the cutting edge and not on the bleeding edge. We felt safer and future-proofed that way.
Considering all the factors, we chose our stack to be the following:
Kotlin – As a mainstream programming language for the backend (a breath of fresh air for java developers).
SpringBoot – The micro-service development framework for the backend. (The de-facto server-side development framework for the java platform)
ReactJS – The UI development framework. (The go-to UI development framework these days).
PostGresQL – The database (simple and a popular relational database with unparalleled community support).
Now that we have the stack, we quickly divided and conquered the problem we defined and started working on various proofs-of-concept to explore the unknowns and convert them to knowns. An important thing we did, and it paid off well, was to design the components in the proof of concepts stage so that it could be re-used for actual development. I’d say 50-60% of our components/utilities we have written were taken to the actual development with just a few minor tweaks.
At the end of this phase, most of the unknowns on the technology front was made known, and we were able to come up with a few solutions to compare and contrast.
It was also vital for us to not strive for perfection at this phase (after all, perfection is always a moving target). At this stage, the software we had produced was like a car with all loose cables/wires hanging under the bonnet). But it was just enough for us to understand the technical complexities and for our functional stakeholders to get a taste of how the finished product would look. We all felt confident that the technology we had chosen was fit for the purpose.
Given the proof of concept done, it’s time to refine the stories and start preparing the user flows/journeys/wireframes to get more visual. Using a component library like PrimeReact tremendously simplified and fastened our UI development as it gave us a lot of widgets for us to design and develop our GUI. Our general recommendation would be to use a component library (unless you’re building something different and not available in the library).
Our prototyping began, and we sliced and diced our stories/tasks. It was imperative for us to keep the demo/feedback cycles short. These were internal demos with the domain experts in the team.
A few essential questions came up
- How are we going to make this application available to the domain experts in the team?
- Do we need to host this on the cloud?
- Should we start provisioning the infrastructure now?
- What are the commercials?
We always wanted to keep things simple and not take any cloud provider subscription at this point, mainly due to commercial other technical complexities it may bring, for which we may need to invest more time.
We decided to package the software (as an executable jar file with both APIs and UI bundled) and use a lightweight database like SQLite. Typically, we’d just distribute this jar file to our internal stakeholders, and it was a simple procedure of running the jar file, and the entire web-app (with UI and REST API) and database are launched without much fuss! It was very important for us to keep our software cohesive and de-coupled.
Cohesive – As we didn’t want to start with the aim of building “micro-services” (an overly hyped word these days) from day one. Instead, we build an app as a cohesive unit that’s easy to ship and if things go way beyond to manage and if we have many people in the team, then micro-services could be the way to go. We are not there yet, and we do not want to prematurely create fragmented services and add more operational complexity (and cost!). We’re of the view that micro-services (or fragmented services) should be treated like a pill. You only take it when you need it. But in general, the software should be designed as services so they can be made to evolve independently and the option of “micro” (or fragmentation) can be added later if needed.
De-Coupled – This is a fundamental principle of software design, and we wanted to have our interfaces and implementations clearly defined so that in the future if we need to slice them as two different services, we have a cushion to absorb such changes. A classic example is we kept all our database access behind a Repository interface so that we initially used SQLite (for faster and simpler packaging) with a view that we’d use PostGresQL sooner.
Our Prototype was ready and was tested by our internal stakeholders. We’re ready to ship this to our “friendly” customers who were guiding us in our journey since inception.
We’re grateful to them and we are always looking for guidance and feedback from the customers as our motive is to build a product built on customer’s feedback.
This is the very first time our prototype version of the software will be seen by different pairs of eyes. We adopted the same packaging model. We sent them an executable jar file (with a built-in SQLite database) and got them installed on their PCs. It was dead simple. It also got us into a situation of not having to take their data and store it in our database outside their PC. It was like using the app totally within their PCs.
But the design was flexible in a way that we could drop in the same application on the cloud, and it becomes a SaaS.
There were numerous valuable feedbacks that helped our solution to evolve and components to be extended. Luckily, our architecture/system design stayed intact. There were quite a few features that weren’t needed, and a few were simplified. This was a perfect phase for us to simplify our implementations and clean up. After a time-boxed window of iterations, we decided to progress to the MVP stage.
Minimum Viable Product
This is the version of the product that we wanted to give to a wider range of customers we knew by references with evaluation licenses.
Our technology had to quickly be geared up for this transition.
We’re no more a desktop tool that can be shipped to multiple customers. We had the following aspects to consider and have answers to
- How do we make the software available as SaaS on the cloud?
- If a customer doesn’t want a SaaS, could they have an On-Premise version too?
- How do we ensure that the application is secure?
- How do we ensure that the customer’s data is secure?
- How can we scale up/down, out/in based on the volume of the customers we onboard in the future?
- What are the commercials and SLAs of the cloud provider
- What should be the lean/simple/cost-effective deployment architecture?
The above questions led us to design our security framework and the infrastructure architecture.
Again, simplicity was the key. We did not want to prematurely make the deployment sophisticated and increase the complexity and burn our pockets. Our deployment was pretty simple and straightforward. Our software is container friendly and can be easily managed by Kubernetes. But we did everything except deploying on Kubernetes because we still don’t need it (therefore don’t need to spend on it). But we’re ready if the need demands. Every “cool” thing added to the product without a compelling need for it is a liability – this goes true from the atomic level (ie a line of code written) all the way up to any complex software that needs to be used with the product.
So many fun experiences so far, lots of learnings. If I have to pick up my top 3 points as my personal favourites in this fun-filled journey so far
- Keep things very simple and trivial. Adding complexity is very easy. Simplifying is killer hard!
- Spend more time and efforts in design. This will pay off well. When you realise that what has been designed fits into newer requirements without going through elementary changes, then that’s a symptom of a good design.
- Feel free and excited to iterate and rework (or perhaps rewrite) – one must be a Rockstar to get everything right in the first attempt!
Welcome to our journey, and thanks so much for your time in reading this. In the coming weeks, I plan to add more specific content related to technology that we at TaxReco find may be helpful to others. Please stay tuned! Bye now
Author – Saiprasad Krishnamurthy