Monoliths are a natural architecture to start with. However, as monoliths scale, a number of consistent problems tend to emerge:
Early on, code is often written without much thought given to modularity or domain boundaries. Composability is traded off for velocity. This usually makes sense given you know the least about how your application should be structured when you’re starting to build it.
Unfortunately, as dependencies pile up, the ability to reason about the monolith as a whole becomes more and more difficult. These dependencies link together to form a brittle system. It becomes much harder to ship a simple feature or do a basic refactor without causing a cascading set of breakages.
As a monolithic codebase grows, so do the runtimes of many key parts of the SDLC. Builds, tests, deploys, linters, and bundles all tend to increase in size and runtime as the monolith continues to grow. This tends to be a death by a thousand cuts; each incremental change doesn’t have a significant impact, but over time the impact is immense.
As a project grows, the requirements for the project tend to multiply. Enterprise customers drive the need for on-prem deployments. Security requirements necessitate physically isolated services. SLA’s are set up that require true fault tolerance. Compute costs and usage behavior drive the need for independent scalability.
As soon as a company has multiple teams of engineers working on a codebase, these same set of problems inevitably emerge. While microservices can solve both your dependency hell issues and your tooling breaking down, they’re not typically needed if that is the only set of issues you face. Build systems, such as Bazel, provide the ability to enforce isolation, parallelize tasks, and cache results. When set up well, build systems allow you to have your cake and eat it too - a clean dependency graph, and fast-running tests, builds, and more. Existing solutions aren’t easy to drop in place, which is why we built tach.
That being said, if you do need to be able to deploy on-prem, scale independently, or have explicit security requirements, then a separate service for your application likely makes sense.