Make microservices look like monoliths
github.com
github.com
I think both strategies have pros and cons. But critically, you need a company culture that supports whichever choice you've made.
If you have a company where each team gets to totally decide how they want to implement their stuff, sharing a monolith between teams can create a mess. Ownership issues to be resolved, code quality/style issues, operational issues. Monoliths require alignment.
So do microservices.
My only thoughts to add is monoliths can be aligned as well as microservices, if not a bit easier depending on the team.
You have to align microservices to talk to each other anyways, and there's no reason you can't make those boundaries work in one codebase instead of many.
That said ownership is more important in a monolith, whether that be a single team of architects or architects responsible for a specific well defined part of the code base.
Having a solid process for only allowing well thought out code in is a cornerstone to keeping alignment.
(Under the hood, the service may in fact be distributed to a large pool of resources. But a microservice doesn't shout into a void "hey could someone please handle a LaunchInstanceInRegion class??" - it's like using a library, but over the network.)
Remember when microservices were pitched as 'allowing you to use the right language for the job'? What a wonderful life the person who thinks a balkanised tech stack is good must have had. They're one undisciplined developer away from having production prolog.
It's like we're advocating for the outcome of the Tower of Babel tale.
Micro services replace your unmaintainable bean injection XML abomination with 50 reimplementations of database access and a salary for the one guy who knows his to make your kubernetes setup work.
It's not a technology problem, it's a people problem. Micro services make it a distributed people problem.
I... I don't really understand this sentence.
Its not a quote of a quote, its an attempt at a mocking modification of an upthread comment that isn’t a quote, but the original doesn’t fix the incoherence of the mocking reply.
You'd probably notice that if you were to spend more time reading and less time writing.
I personally think that if microservice is doing something so complicated that the rewrite would take we-cannot-afford-that amount of time, then it should be split into multiple parts.
Interestingly, even a simple CRUD can be made in a complex way in Clojure which makes it difficult to understand for non-clojure devs.
Data migration path is also a huge issue (and the major reason it took so long).
I believe the original service took 6 months to develop (including all iterations of the design/spec), a "simple" 1:1 rewrite a double of that.
"You build it you run it", until the OCaml guy quits and now you need to support OCaml.
I politely explained my position on why that seemed suboptimal.
I agree 100%. However after spending 10 years doing microservices everyone wants me to do microservices, despite me telling them what you just said. It's a positive feedback loop.
This project is just an exploration of providing teams with a low (no?) cost way of doing microservices - I also kinda enjoy hacking on things like this. Still in progress though so use it at your own risk.
Yes, maybe the company you work at does not have a good reason to use microservices. But this doesn't mean the same goes for the rest of the industry. Being a nay-sayer and rejecting microservices in any circumstances is just as unreasonable as rejecting monoliths.
No. No no no no! Monoliths (modular or otherwise) are the default and if you have to ask, you don’t need microservices. Period. That’s not a lazy oversimplification, that’s hard earned experience.
In over 15 years I’ve seen microservices singlehandedly kill the engineering momentum at nearly half a dozen companies. They’ve got a worse track record than techbro management. Literally the only time I’ve seen the architecture work well was when I was working at Azure on cloud infrastructure with a dozen teams working on a single product. The vast majority of developers never work on a project of that size and yet try to cargo cult microservices into companies with less than a dozen devs, let alone teams.
Maybe your company is one of the few companies where microservices make sense (or you haven’t come to grips with the stockholm syndrome yet), but the hype around microservices have been a disaster for many more companies than they have helped in my career
But this is not what I am arguing. Yes, monoliths are the default, and unless you reached the point where you have thousands of engineers and teams working completely independently, you don't need microservices. I've also seen momentum being killed by microservices in smaller startups, and I've also seen large organizations operating with microservices wonderfully.
My beef with this kind of comment is that it kills any interesting discussion about architecture into a "don't use microservices" circlejerk, which is not helpful to people who actually want to dive into this kind of architecture. As you said yourself, microservices do work well for large companies, so why pretend it doesn't?
Same reason we don't teach people anything about driving an 18 wheeler when they apply for their regular driver's license. A small minority of drivers will ever even have to think about the topic and when they do, they'll seek out a specialized trucker school or their employer will guide them through the process using institutional knowledge. There is zero value to exposing people with learner's permits to the nuances of driving massive trucks.
Unlike the truckers, however, tech bloggers have hyped up the concept of microservices as a panacea to all developers, resulting in a disastrous cargo cult. There is no interesting conversation to be had about microservices in this context. That discussion is going to be had with all the other senior-most engineers in an organization because any org incapable of such an in-depth discussion internally should not be using microservices.
I would also argue that because of the async calls (NodeJS is single-threaded, right?) you can easily run into weird and unexpected issues where two sequential service calls return out of order (which is impossible if they are just method calls, but very likely if they are sent over the network). How do you handle these cases? Do you just block on each call over the network and hope for the best?
This is solving microservices via dependency injection.
I'm not against it, but honestly if you not gonna be thoughtful about it.. Why not just monolith it? Monoliths are only a problem at large scale, and at that scale you have the manpower to create a reliable messaging service for inter service communications.
Why not monolith it? Maybe one part of the app evolves to really need a time series database or it really does some crazy stuff with image editing or it has new special dependencies that take an eternity to build, and you don’t want to add 20m of build time every time you mess with the rest of the codebase, none of which needs/expects the time-series/image-editor/complicated build thing to be there deployed alongside it. That’s like OG logic of why you want microservices, and it still makes a lot of sense, just people get carried away with cargo-culting on their thousand-microservice hellscapes where each API has four routes and 4000 lines of deploy config, boilerplate, copypasta.
From what I can see, SOLID lets you split the monolith, as long as you happen to have interface segregation on the right seams. It says nothing about the operational properties of the service once split, which is by far the bigger problem, and depends on the non-functional properties of the code using the interfaces. Unless you already know you are headed for a microservice split, you're unlikely to have designed with that in mind.
New spring library? Have fun applying the same changes to 14 different microservices.
Microservices might make sense with completely different teams developing them - but multiple microservices in the same team, with the services talking to each other, really doesn't make sense.
On the flip side, at some point monoliths can get large enough that upgrading libraries or runtimes becomes an incredibly difficult problem. When I was at Amazon a major service was finally getting upgraded from Java 7 to Java 8. Took a cross-functional team 6 months (!) to make it happen due to the massive amounts of existing code. And these are AWS engineers who are much technically stronger than average.
I don't condone the 500 LOC style of true microservices, but people tend to go back to the extreme of "just make everything run in the same process with the same runtime and same dependency chain" as if that doesn't come with its own set of headaches.
(also for what it's worth -- in correctly done SOA, you don't need to upgrade libraries in lockstep. So that Spring upgrade can be done on a per-service basis as needed.)
Some languages (e.g. Go) make this easier than others by allowing two versions of the same import to coexist, but this is why we broke up our Django monolith a few jobs ago.
IMHO unless you have a strong engineering culture, having the same dependencies is the only way to ensure they are all properly updated (regarding security updates, and replacing deprecated dependencies/versions.)
In my experience doing this at AWS and elsewhere, no, the services case is much easier. Even just the ability to do releases in chunks 1/14 of the size is a huge advantage. On top of that, your services are going to have smaller dependency chains, and transitive dependencies are truly where dependency hell[0] comes into play. It boils down to complexity being multiplicative rather than additive. Depending on the size of everything, each service is probably more like 1/50th the amount of work rather than 1/14th.
> IMHO unless you have a strong engineering culture, having the same dependencies is the only way to ensure they are all properly updated (regarding security updates, and replacing deprecated dependencies/versions.)
This is working under the assumption that all your code and services should always be updated to the latest, which isn't necessarily true. I still have smaller, infrequently updated services running and happily chugging away that run on Python 3.5. Other codebases have been upgraded to Python 3.10, sure, but what's the harm in letting this one run on an end-of-life Python, especially given that it's a backend service, with no public access, that only communicates via RPCs? A vulnerability might be found in Python 3.5 that necessitates an upgrade, but it's not like moving to the latest version of everything prevents that -- i.e. "Heartbreak"[1] only affected OpenSSL 3, not people still using OpenSSL 1.1!
[0] https://en.wikipedia.org/wiki/Dependency_hell
[1] https://arstechnica.com/information-technology/2022/11/opens...
The chance of a percentage of 14 microservices just plain not using that feature is bigger than the chance of that monolith not using it.
A completely contrived and fake example:
The language you're using has a complete refactor of its math library, everything must be rewritten in a new style that's incompatible with the old style.
In a microservice world there's a good chance that very few of those actually need the high-powered math library.
But your monolith has a few sections that use it so the whole shebang is delayed until that bit is rewritten to the new API.
Yeah, it's just a laugh a minute.
This meant upgrades were usually as simple as increasing the version, as the infrastructure team had already made sure that the other dependencies had been tested. Each team were free to ignore this and do whatever they wanted, but the benefits were too great to ignore for most teams.
At my current job we don't have this type of shared configuration, which makes micro services much harder to maintain.
This feels like we make our jobs harder than they have to be, sometimes…
I’ve spent the last month just deleting shit and simplifying shit. You don’t need a caching layer for customer data when your entire SQL database is 100 rows in a single table. You don’t need service discovery when everything runs on one node.
My point is that this entire system was designed around their last engineer’s bookmarks of interesting things, not designed to be maintainable or easy to iterate upon.
1. The developer, of course.
2. The person who let them do it.
3. The eight thousand devtool companies that pitch their solutions to developers.
4. The hiring managers for whom 'resume driven development' is effective.
5. The VCs who wrote the playbook for how all these companies (and the dev tool companies) have to grow.
6. The Fed for so much ZIRP that VCs are in power.
7. The government for uncontrolled spending that needs low interest rates to sustain.
8. The electorate for electing the government.
9. The media for influencing the electorate.
10. The human brain for being susceptible to influence.
You just pick the crap that looks good on your CV and use that. Repeat until you get a new job.
OP here. Fortunately the authors of the components you use didn't get discouraged by comments like this otherwise they would've never been written ; )
- Top 5 techniques for building the worst microservice system ever - William Brander - NDC London 2023 (https://www.youtube.com/watch?v=88_LUw1Wwe4)
- Don’t Build a Distributed Monolith - Jonathan Tower - NDC London 2023 (https://www.youtube.com/watch?v=p2GlRToY5HI)
I very much enjoyed the 5 techniques and many of the tidbits. I also liked "Avoiding Microservice Megadisasters" but mostly for the entertainment value (https://www.youtube.com/watch?v=gfh-VCTwMw8)
Unless you have multiple independent development teams you do not need to worry about the human factors. Addressing them fully is always going to come at a technical cost in terms of things like latency and complexity.
Separation for technical reasons (generally scaling) may well be a factor but easily addressed without building 'true' microservices, for example with serverless functions.
Don't let the purists sneer at your architecture because it's not true microservices.
Yeah in part my motivation for open sourcing this was the fact I kept reading on HN how microservices should be an implementation detail etc. in the past 6 months. I totally agree with that sentiment, hence Actio. For the direction I can take some responsibility but not for the details (yet) ;).
The dynamic type system makes it easier to write testable code with less abstraction overhead.
Faster to train up junior devs.
That said, you * must * set up the codebase with appropriate guard rails or bad things can happen.
You mean callback hell? :P
> The dynamic type system makes it easier to write testable code with less abstraction overhead.
My biggest issue with js/ts is that there's nothing really enforcing good code, unless you decide to adhere to it. Also: npm is utter garbage.
> Faster to train up junior devs. I think go is better in that regard, less random happenings.
But yeah, in general I get what you mean!
Fast, battle tested, vue2-like approach, great documentation, good community. The automatic indipendent-scalability as an option is usually the main selling point of these solutions, but honestly I think the real pro is the "composition" approach, which is essential if you want to keep a clean and well-organized codebase. On this regard, I found moleculer pretty great even for large teams.
Spot on.
And how do you know you’re not a thought leader? You call yourself one
They formed a partnership with some dev shop that's going to rebuild everything that I made (that's working great and scales well).
I asked her why, and she said "because they say that doing microsevices on Azure is better architecture".
There goes that bit of equity, I guess.
/shrug
However, I've definitely seen this problem even with legitimately strong, seasoned technical leadership, where the vision is great, but the team just doesn't have the skills to pull it off. You can't have good macro-engineering without good micro-engineering. The problem with the cambrian explosion of tech is that it obfuscates the basics that engineers need to understand. Junior engineers end up struggling just to stay on top of incidental complexity and many come to the conlusion that building good software is just about learning enough languages and tooling, when in reality they are barely keeping their head above water and never learn the deeper lessons of what works and doesn't in software systems.
Can you reasonably expect a junior engineer to push back here and generally get traction? If this architectural decision has been made, it’s been made. At higher levels.
>If everyone had better system design knowledge
I don’t think this is a junior devs fault - they’re junior, after all.
> but then it doesn’t work because the organization…
Bingo.
That's not the case here, where the code lives in a monorepo but the services are many.
Perhaps formalized abstractions around these barriers is a good idea? At the very least better than the 60 kubernetes services at my 100eng headcount team? :-)
Alas, support for OSGi in the general ecosystem is abysmal, and the next best thing seems to be microservices.
Honestly, I find it pretty shocking that so many mainstream languages don't support this sort of "local isolation" better. The diamond dependency problem well-known at this point.
(To be clear, in our shop we generally only do this for very problematic dependencies that have a tendency to break backward bincompat on a whim. Those are almost always the most painful ones, especially if they are in turn dependended on by many of your project's other dependencies.)
I haven't yet tried it myself, but it looks promising:
https://spring.io/blog/2022/10/21/introducing-spring-modulit...
I hesitate to say "microservices" because I strongly suspect that the 95th-percentile use case will see all the parts strongly coupled and deployed as a single commonly-versioned blob. Not saying that's a bad thing necessarily, but if one of the benefits of microservices is loose coupling, it's not going to encourage a good microservice architecture.
IMHO the monolithic microservice (or distributed monolith) is when you don't have firm service boundaries - eg. services can read each other data if they want, sidestepping endpoints/handlers.
Actio avoids that mistake. Each service has its own database.