Picture this, your company wants to wow a new customer by presenting them with a prototype (or MVP?) of their next product. The business goal isn’t necessarily to ship – the app will likely not see real users, yet – but rather to build trust with the customer. The company wants to establish that it can deliver on time and on budget, with the right level of quality.
Now, if the company actually has the right kind of process in place, that may be possible. But, more often than not, companies instead put developers on a death march to achieve their target.
The developers will raise concerns over the shortcuts they are making. "This isn’t how real software should be built!" they cry. And their concerns are met with the idea that this is just a prototype. It is mostly going to be throw away once the company secures the customer and gets more money. The developers really want to believe those words, but they are always lies.
There is No Time for a Reset
If the customer likes the product so far and gives your company more money, do you think they care about developers resetting or even refactoring? The expectation has been set: you are a team that delivers fast and on budget. From here on, you will be expected to keep that promise. You may get a bit of room at the start (if you’re lucky), but that’s all you can home for.
This is why I really care about my prototyping stack. If I’m going to invest into a functional prototype, then it needs to be ready to grow into a full product. We don’t get a second chance to do it again. The underlying technology should make it easy to deal with the fact that my prototype will definitely be a technical mess.
Here’s what I think you should look for in your stack:
- Rapid Speed of Development: The chosen stack should facilitate rapid iteration and development cycles. This allows for quick experimentation, learning, and refining of the prototype based on user feedback.
- Ability to Transition to a Real Product: The prototyping stack should have the potential to evolve into a scalable, production-ready solution seamlessly. This means supporting essential functionalities, data model evolution, and integrations necessary for a fully functioning product.
Choosing Your Tech
I’ve used all three to build a functional prototype and transition it to a growing product with real users. If there’s only one advice you take away from this is to pick one and learn it really well – especially if your business context often requires you to go from MVP to growth.
But don’t just focus on the rapid part, think and care deeply about how you can be productive with this stack in the future.
Consider these scenarios and plan ahead:
- How will you quickly add new required fields? How will you run code that backfills those fields for existing entries? How will your backfill mechanism communicate with other data sources and APIs as it is backfilling?
- Can you start with a multi-tenant system from the start? If you don’t do it, how will you transition to it?
- Can you emit custom events anywhere in your system? Can you write code that can ingest them from a single even bus and respond to them?
- How difficult will it be to add an activity stream?
- Do you have audit logs figured out?
- Can you quickly introduce different plans/tiers? What about usage based tiers?
- If you are charging by time/resource usage, what will take to collect and store that information for accurate billing?
Prioritize the recurring activities. This is where your stack should provide the most value.
As you are prototyping none of these questions matter. Data migration and schema management is not a thing – you just delete and start from scratch. But as soon as you sign your first real user this has to change.
After CRUD and Events, Solve Background Jobs
Your product will eventually grow to need some sort of background jobs. There’s a lot of solutions for small activities (such AWS Lambda or Google’s Cloud Function), but what about compute-intensive jobs?
Does your stack and ecosystem already have the right options that integrate well together, or will you need to look for another provider? How long will take to set that up? Will you be able to build other "glue logic" around things happening on this other platform?
Also, think about how tough it could be to handle scheduling for these jobs – especially if you’re letting users configure it.
For me, going all in on one cloud provider really makes life easier. For example, with AWS we can get everything in one package:
- Prototype quickly via AWS Amplify to spin up our real-time CRUD app.
- With S3 we get the cheapest blob storage on the market. (And lifecycle rules make it easy to save money further via scheduled deletes or moving things to Glacier.)
- We can emit events via CloudWatch and use EventBridge rules to trigger the right Lambdas to perform the actions we need. Is the process a bit more complex than a single Lambda? Then construct a simple StepFunction to orchestrate a flow.
- Need a queue? SQS is awesome and can be configured with only 5 lines of CDK.
- With DynamoDB we can easily move data around, migrate it, add highly performant cashing (DAX), and setup awesome searching capabilities via AWS OpenSearch (quite expensive though!).
- For background jobs we have a lot of options via Fargate and Glue.
You can get a very similar experience with Google Cloud as well: Firebase, GC Storage, Pub/Sub, GC Functions, GC Tasks, Firestore, GC Run, and Algolia/Elasticsearch integrations.
It doesn’t matter which one of these is "the best". What matters is that you specialize in particular stack and find ways to get so good at developing software that the line blurs between what is a prototype and what is "real" software from day 1.
Don’t waste time on the idea of throwaway work. Be so good that they can’t ignore you!