There are various pros+cons for whichever combination you choose, there's no "best" answer that applies to every single situation.
In a job we were mostly Serverless/Microservices, but tested and deployed as a Monolith. We got many benefits from the Monolith (simplified management, testing, code wrangling), and some benefits of Microservices (theoretically independent functions, scaling/costing down to zero).
Though Erlang is a counterexample.
But microservices breaks that, as well as introduces many other consistency problems, plus huge complexity overhead, all of which are forces toward instability in highly complex systems.
I suggest that the important factors for stability are not really about microservices, and that depending on the situation microservices is usually a net negative for stability, for the reasons mentioned above.
So, no net negative.
As other people have pointed out. It’s a tool... you can have bad code in a monolith just as much as micro services.
I absolutely love that our application takes just a little bit longer to build than a smaller isolated microservice. This is when my computer is doing all of the hard troubleshooting work for me ahead of time. 32+ cores diligently seeking out my wasted time and banishing bugs before they can even make it into QA.
Determining that your types don't line up during integration testing seems like a foolish approach if you value your time.
Typescript/GraphQL both have strong type checking. I think you need to be more familiar with the available tech before spreading FUD. You can even compose the micro services into a monolith if you want everything to just run. It’s a tool not a religion.
Yes I’m assuming conventions for services, just as in a monolith I’d assume standards for design and composition (usually an overriding architecture like MVC or some such).
I do not care that each microservice independently passes type checks. This is hardly a compelling argument if we are talking about delivering working products. The most critical points of verification in a system with multiple services are where all of these pieces connect back together.
... so that’s the whole problem domain.
No move of goal post. And no, like I said with things like typescript (And flow or Sorbet) you don’t need to compile any binary at all let a lone a single one.
Also: a monolith does not refer to a single binary it refers to a single application code base. (You can have monoliths in Ruby/Python/JavaScript/php/etc where no binary is produced)
I think you are misunderstanding what I’m saying, because I’m basically saying the same thing again:
- service A consumes service B - they are both type checked (as you readily admit) - the call site of service B in service A is type checked as well. - there are 0 points in the code or interactions that are not type checked.
You deploy to the other one, do a health check, warm it up and start routing to it.
No downtime.
Edit: Oh, or you know, just don't? Maybe no-one will notice 5 secs of downtime :P depends ..
For us, the approach is to use multiple instances of the monolith deployed simultaneously (listening on different ports) and to move traffic using a purpose-built software load balancer that is part of the same solution stack.
The load balancer software is an extremely simple concoction based upon the exact same primitives as our main application (AspNetCore). The only intelligence is in looking at request trace ids to determine old vs new routing.
A deployment can be resolved within a few seconds using this approach, even under heavy load.
We use a single VM per customer environment and are able to effectively realize zero downtime during business hours. We are granted maintenance windows after every business day and over weekends, so it is a little bit easier than if we were managing a credit card transaction processing system or similar.
To be fair, our approach only works because of how integrated we have our entire vertical. We went all-in on writing our own way to build, deploy and manage our own software. The persistence mechanism was developed with all of this in mind.
We are moving beyond all of this deployment magic though. There is a bold new configuration-driven realm that we are entering into which quickly begins to obviate the need for frequent & disruptive software deployments in the first place.
- The monolith has to do all the work of the whole system when it starts, so start-up time is a lot longer, which makes rolling deployments much slower
- The monolith is much heavier in resource requirements, so it's much more expensive to spin up multiple of them
- The monolith has a much larger surface area, so post-deployment but pre-release verification is much more complex
- The migration path for a smaller service to support zero-downtime is simpler than for a larger service, so on average it's probably easier to get it working for an existing 'microservice' (although may be easier for the monolith than an equivalent full set of microservices)
If you have many many microservices what is the combined resource usage vs. the monolith? Probably about the same and microservices may use more resources actually if we are talking anything with a runtime like Java.
Migrations in my experience mean migration of data so its about the volume of the data you need to change and not about the code operating against that data. Whether you have your monolith or a bunch of microservices waiting for the conversion job does not matter. It also does not matter whether your monolith or the microservices have to be able to read the data at any given time. With a quick starting microservice you may be able to say "eff it a transaction might fail but the new service will be up quick and the user can redo it" but that's not what you really want in big mission critical workloads. You really want to wait until all transactions on the old nodes finish before shutting them down. So your load balancer will route all traffic to the old nodes until new nodes are up, then shift traffic over and you kill your old nodes when all transactions have finished. Then you deploy those nodes and bring them back in. You have reduced redundancy and less capacity to handle load if we are talking old real iron businesses but in current cloud environments you just start up new instances and literally just kill old nodes after you've let the transactions finish. So you could even deploy during high load times.
I'm not talking hours of deployment here. Monolith doesn't mean you have to have all the cruft and startup times that say a jboss server with EJBs. You can have a monolith that starts up in a reasonable amount of time if you make the right choices for how you build your monolith.
Post deployment verification is slower why? If you do this manually why does it matter whether your changed customer flows are spread across 6 microservices that you just deployed new versions of or your monolith?
Also monolith doesn't necessarily mean you only have exactly one service. At my last place we had multiple services for various parts of the overall system but they were all monoliths themselves and shared some code through a library as well. But the services definitely weren't micro and did lots of things that weren't that related in the end. Could've split it up into many microservices.
If you still want "micro" services because it actually makes technological sense, you can build these at a lower level of abstraction under some /Services folder. Many languages have namespaces, which allow for boundless organizational schemes. Only for a lack of imagination would you fail to organize even the most complex of applications in this manner.