We'll describe our development and production experiences with three mainstream software architectures. But we'll start by defining our terms:
Monoliths keep all their code in one place, which gives a friction-free development experience for code navigation, refactoring, debugging, code reuse, and testability. However, as Monoliths grow, they trend towards "big balls of mud", which become very difficult to maintain.
Working with a single Microservice is great, as it gives us the same benefits as working with a small Monolith. However, the more Microservices we maintain, the worse our development experience becomes. That's because each new service boundary increases the friction for code navigation, refactoring, debugging, code reuse, and testing.
Serverless architecture is inherently modular and functional, which gives significant advantages for simplicity and testability. However, the distributed nature of its code execution has a negative impact on our code reusability, debugging and testing.
Monolith's one artefact approach keeps operation costs low and simplifies deployment, but makes horizontal scalability difficult.
Microservices' distributed approach gives excellent scalability and robustness, but makes deployment complex and hosting expensive.
Serverless' "outsourcing" approach gives excellent scalability, reduces our ownership of deployment complexity, and keeps server costs in-line with our usage, but also gives us an air-tight vendor lock-in.
These architectures give us plenty of guidance on how to deploy our systems, but very little guidance on how to structure our code within each system. Over the years, we've tried many different approaches to improve our systems' internal structures (e.g. DCI, DDD, Design Patterns, SOA, SOLID, etc.), but none of them took us all the way to development nirvana.
To get there, we realised we needed to roll up our sleeves and invent a whole new approach.