This content originally appeared on Level Up Coding - Medium and was authored by Garrett James Cassar
Disclaimer: Grumpy Goose Warning
There’s a prevailing idea that monoliths create bad code. Monoliths don’t create bad code. Developers do.
Splitting a monolith might improve code quality by creating strong boundaries between different domains. But
- It also might not. Resulting in a distributed monolith.
- You can also do that within a monolith.
You also run the risk of dramatically increasing the complexity of your project. Hamstringing your testing capabilities, complicating your infrastructure and deployment process, and potentially creating ‘split brain’ between various services.
This loss of efficiency compounds quickly. And is generally bad vibes.
Complexity in Testing
Software is hard because you don’t get any points if 99 things go right and 1 fails. It all needs to work.
Therefore you need to test things all together. Or to have a large test scope.
Test scope is a measure of how much can be tested within a single test.
The smaller the scope the further you’re getting from the actual user experience. Meaning the less likely a passing test means the feature works and a failing test means a feature doesn’t work.
In a monolith, testing a large scope is easy. Because it does everything
However in Microservices, the same process looks more like this.
Your test scope will naturally be limited by the boundaries of your Microservice.
Forcing us to mock or stub (replace and ignore) the behaviour in the order and fulfilment service.
Meaning that changing and additional behaviour in the Order or Fulfilment services will be ignored.
A passing test is less likely to mean the feature works and a failing test is less a feature doesn’t work.
You could have 10000 passing tests at this level and still face bugs like this.
Or this
Or multiple other iterations, due to changes in the business logic in the order service or product service, data or contracts.
As you aren’t really testing these interactions you are mocking (faking) them.
Even if this isn’t a problem to start — it will become one as processes gain complexity and drift. Causing you to either find more bugs in UAT or to make your mocks more intelligent — which is a great way to waste an afternoon.
This relies on the knowledge and diligence of each individual developer to manually test two things in conjunction before merging in the code. In my experience, that is inconsistent.
Bugs are inevitably found in UAT through manual testing rather than through automated testing. Which is like spending 100 pounds on a 10 pound burger.
If that slips through the cracks and gets to production, that’s like spending 1000.
Read about the exponential cost of fixing a bug here
In a monolith however it is very possible to have a test suite that is scoped as such.
Nothing is mocked, stubbed, faked or hoodwinked.
Providing a larger test scope.
Meaning that it is much more likely that a passing test means the feature works and less likely that a failing test means a feature doesn’t work.
This is a much simpler mode of testing. It doesn’t rely on anyone being perfect, experienced, knowledgeable or sufficiently caffeinated that day.
It doesn’t require your mocks to be up to date or intelligent.
And if you really wanted to mock one of your services — you still can very simply.
While there are ways around this — I’ll address that in another blog post.
Another advantage is that you don’t need any contract tests — the compiler does that for you. (In a strongly typed language anyway).
Complicating your infrastructure
Getting caught in a complex web thing that needs to talk to each other gets really complicated really quickly.
Adding steps
Example: Adding a new endpoint that must be called by the ui and another service.
Monolith
- Create endpoint
- Create permission for endpoint
- Hook up the endpoint to the front end
Microservice
- Create aws endpoint
- Create user permission for endpoint
- * n — create permissions for all microservices to talk to this endpoint.
- * n — make sure all the infrastructure as code is updated correctly and nothing was modified directly.
- * n — Hook up the endpoint
I’ve gone from having 3 steps to 2 + ( 3* n) steps.
I’ve also created several points of failure in addition to making it very difficult to test.
Is that a win? Doesn’t feel like a win.
Managing split timelines
If infrastructure is managed fully by a separate infrastructure team — which I never recommend — delivery will almost certainly be difficult. No matter how good they are.
You’re not answering the question
- ‘Can we get this done in the next sprint?’
You’re answering the questions
- ‘Can we get this done in the next sprint?‘
- ‘Can the infrastructure team get this done in the next sprint, and will they put it together in time so as not to block us?’
It’s inevitable that in the annals of time the answer to the first question will be yes to one and no to the second.
This is not the infrastructure team’s fault, it’s just a reality. You could replace that with a business team, QA team, deployment team or whatever team you want. Managing multiple timelines instead of one will result in clashes of priorities and timelines.
So when the Project manager asks ‘Can you get this done in this amount of time.’ The answer goes from being ‘Yes/No’ to ‘I don’t know, it’s not in my hands’
Putting your face on a project where your hands are not will make you too — a grumpy goose.
Splitting timelines might also encourage shortcuts and compromises based on what’s deliverable by any one team. This can lead to worse design — potentially the second most expensive thing you can do on a project.
Anecdote
I once worked at a company where I designed a solution that required a simple s3 bucket. As a qualified aws solution architect I know this is a five minute job.
So I called the infrastructure team to ask and I asked if they could provide one. They said “Yes, in around 6–7 months”.
Long story short I quit after 8 months, it still wasn’t done. A year later the tech department had massive layoffs. Related? No idea, but I certainly felt they had a bloated inefficient tech department.
Are Microservices a terrible idea?
No. They’re not. They have two irreplaceable advantages and a replaceable one.
- Independent deployability (enjoy your 147 pipelines) — Including independent development teams, yada yada.
- Independent scalability.
- Strong separation of concerns — This can be replaced by a strong, principled development team.
But they add a lot of complexity.
If you’re a get things done kind of developer — and I think you should be — Adding complexity always needs a strong justification.
Jumping in without a strong justification tends towards making decisions based on the dogmatic rather than the logical. That’s a terrible idea.
Conclusion
My advice.
- Start with a monolith.
- Test well with nice, large scopes.
- Obsess around boundaries.
Treat boundaries of your monolith as if it was made up of Microservices. Have the boundaries talk to each other through an interface (just not an application program interface — API). So that you can split them whenever you like. But don’t.
Not until you have a really good justification around deployability, scalability or teams working independently that arises naturally. Justify how you will maintain the same level of testing. Document the necessity and make a case for it.
If it’s a good one, do it. If it’s not, do the simple thing and stick to a monolith.
Your job is to get things from A to B as quickly as possible. Make that as easy as possible.
Why you should never default to Microservices was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Garrett James Cassar
Garrett James Cassar | Sciencx (2024-10-03T14:50:57+00:00) Why you should never default to Microservices. Retrieved from https://www.scien.cx/2024/10/03/why-you-should-never-default-to-microservices/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.