Pragmatic Technical Choices in Start-Ups: Lessons Learned
In modern software development, the temptation to use the latest technologies, design patterns, and follow every trending story on Medium is high. However, real-world scenarios rarely align perfectly with idealistic implementations. From my experience working in three different start-ups, I’ve witnessed both good and bad technical choices — including some that I made myself.
The dream of building apps using the most cutting-edge technologies is alluring. However, relying solely on the latest tools can be a downfall for start-ups rather than a boost. Start-ups face unique challenges — tight deadlines, small teams, limited budgets — that demand pragmatic choices instead of theoretical perfection.
How Can We Identify Good vs. Catastrophic Choices Early?
Unfortunately, there’s no universal pattern or recipe for making the right technical choices from the start. What works in one context can completely fail in another. The best way to approach decision-making is by having a deep understanding of the business domain and always paying attention to signals that indicate potential problems. Risk assessment should be an ongoing process rather than a one-time exercise.
A conservative approach can often save the day. Instead of jumping on the latest tech bandwagon, it’s wiser to take time to understand what exactly needs to be built. Once the architecture is clear, the system should be designed in a way that allows growth. I like to think of it as creating an ecosystem where satellites can be easily added rather than building a galaxy from day one.
Microservices vs. Modular Monolith
One of my biggest mistakes in the past was starting directly with microservices. According to most developers online, you should never start a project with microservices — and they’re mostly right. The complexity of distributed systems (communication between services, data replication, monitoring, etc.) is often unnecessary for early-stage products. However, if you truly understand the business requirements and have a strong technical team, starting with microservices isn’t always a bad choice.
That said, for most start-ups, the safest path is to begin with a Modular Monolith. This approach provides a clear separation between business domains while avoiding the overhead of distributed systems. If the business grows and scalability becomes a pressing issue, individual modules can gradually evolve into independent services.
Lessons Learned from a Food Delivery Project
A few years ago, I worked on a food delivery application. I was so fascinated by the promise of elasticity and scalability that I pushed for a microservices architecture from the beginning. The dream turned into a little mess. The team was small, the product was still in its early stages, and the complexity of managing multiple services caused more problems than it solved.
In hindsight, a Modular Monolith would have been the better option. We could have achieved clean separation between domains without the additional burden of service orchestration.
First Case: Smartioo
For microservices, I used the following tools:– Consul
– Docker Hub
– ElasticSearch
– Redis
– Hangfire for jobs
– API Gateway (built with Ocelot) with load balancing
– Blob Storage for image storage
– RabbitMQ
This is an example of the CI/CD pipeline implemented for my infrastructure.
.jpeg)
All this infrastructure and microservices became a struggle at some point. Managing this complexity with a small team put unnecessary stress on the project. Development time was slower rather than faster — exactly the opposite of what we were aiming for.
Second Approach: Delivera
.jpeg)
In the second approach, I designed a Modular Monolith to simplify communication between the app’s components and reduce maintenance costs until the business scales.
To achieve this, I removed RabbitMQ and replaced it with domain events and notifications provided by MediatR. Removing RabbitMQ also reduced distributed transactions over a message broker, eliminating the need for Sagas and Routing Slips. This decision provided a faster way to deliver functionalities, as distributed transactions require longer development and testing times.
The API Gateway was no longer necessary, as there was only a single API. Consul for service discovery was also eliminated, since Azure already provides this functionality out of the box.
Additionally, I migrated from Docker Hub to a fully Azure-based infrastructure, including CI/CD pipelines, which were simplified during the migration.
Hangfire was replaced with a custom job processor based on event-driven processing, where events are generated from code and dispatched through a dedicated event service.
I kept Elasticsearch for logging and metrics, accessible through Kibana, which provided essential observability without introducing additional complexity.
This approach significantly simplified development and maintenance, while also reducing costs — a crucial factor in the context of a small team without dedicated DevOps engineers. All tasks, from development and bug fixing to infrastructure management, were handled entirely by the development team, which also resulted in faster development cycles.
Conclusion
Start-ups need to strike a balance between innovation and pragmatism. While cutting-edge technologies can solve certain problems, they often introduce more complexity than they remove — especially for small teams with limited resources.
By adopting a Modular Monolith approach in the early stages and evolving the architecture gradually, start-ups can achieve clean separation of concerns, faster delivery times, and lower costs without compromising scalability. The key is to always align technical choices with business needs, not with trends or hype.
