Why Segment Went Back to a Monolith
infoq.com
infoq.com
> Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization's communication structure.
I think microservices work well in organizations that are big enough to have a team per microservice. However if you've just split your monolith up and have the same team managing lots of microservices you've made a lot more work for the team without the organisational decoupling which are the real win of microservices.
In my experience it is really difficult to fight Conway's law, you have to work with it and arrange your business accordingly.
You could still do microservices and still fail to deal with Conway’s law.
That's what the poster suggests happened. Nowhere do they suggest that microservices are overall required.
IIRC Fred Brooks pointed out that the # of bugs in a system correlates closely with the # of lines of communication within and between the teams. Joshua Bloch recommends in "Effective Java" that, if possible, 3 potential clients should participate in the design of an API, for the same reason. So a well-designed interface or OpenAPI spec is worth its weight in gold.
Ofc, "microservices" here means separate running instances available on a network. But monoliths can be "service"-oriented as well. OSGi was good for this in Java, but any system able to load shared objects or plugins dynamically can follow the same pattern. And the benefit is that, if your app hits the jackpot and needs to scale outwards, the service interfaces, ie the lines of communication, are already well-defined.
So, service-oriented monolith first, then microservices if needed.
When I worked on a SOA team, I tried to begin any new effort (whether a new API or modification to existing API) solely discussing the API contract. It was (ideally) high-level enough that business analysts and project managers would understand it, and it helped to guide us away from getting mired in implementation discussions too early.
At that organization, we rarely had the opportunity to involve multiple customers at the same time during design discussions (we were typically engaged to help a specific consumer implement a specific feature), but the institutional memory in the SOA team helped us to keep in mind existing/potential other users of each particular webservice.
We had been doing some UML modeling, sequence diagrams during planning, and still having this problem, so rather than repeating the same action and expecting a different outcome I started trying to flip the script. What ended up working was not code diagrams but data flow diagrams and sequences. To get X you need Y, and to derive Z you need A, B, and X. To publish you need all five.
After that, the APIs mostly wrote themselves, we reordered a few different forms, but most importantly variance dropped like a rock.
The great thing about generated-diagrams is that you can easily store and version the original text representation along with the code it describes or applies to.
This is so accurate. I've heard engineers give state not needing to communicate, chillingly, as a positive for microservices, like "we won't need to talk to each other if all of us are working on different services". My other favorite is using microservices as an excuse for why the product isn't working "oh, my service is working fine, but his service is doing this when it shouldn't", when we're on a small engineering team.
sighhhhhhhhh
API documentation is a medium of communication as much as any user interface.
If you don't keep this in mind, then using your service's application programming interface will be a bad experience.
You have observed that other services also have that quality. Indeed. Nowhere did I say, "all services with decoupled deployment are microservices".
Of course real life is messy and plenty of people realized writing small single purpose services was valuable, and plenty of people build giant "microservices" that have nothing to do with the original term and are just badly constructed monoliths.
I really hate that word when used without further definition.
Which makes the term "microservice" even weirder, given that any microservice is going to be bigger than a single subroutine.
Yes there is. A service is a very generic concept to the point it's only relevant as a high-level concept.
The concept of a microservice makes all the sense in the world if you look back to where we came from: web services. When compared with all the work and requirements and complications of using SOAP and WSDL and UDDI and everything around, just sending small JSON payloads around, and the ability to peel off smaller services leveraging that architecture approach, was a far lighter and uncomplicated way of doing business.
I mean, the name microservices becomes obvious once you look back and all that you see is macroservices.
I think they were saying something more aligned with your opinion than you read it as.
Microservices tended towards a single runtime per service, ensuring the deployment lifecycle was tied to the build lifecycle and thus allowing for independent evolution.
At the beginning Microservice was generally viewed as more granular than SOA, though that’s been backed off of.
But then you’d get some that would make bizarre claims like a microservices must be under 100 lines of code. :shrug:
Cockroft’s Rule of Thumb
Can complete a service in two weeks or less Completed = coded, tested, and in production • Fits in “one or two developers’ heads”
At that rate you quickly hit hundreds of services.
I'll claim that splitting a well-structured monolith into microservices will always make it less maintanable, but it might be worth it if you need to for some reason like elasticity or failure tolerance.
But for the love of god, keep the design open. Don't tie the existence of internal software components to peoples livelihoods.
The claim is that such ties, at the macro-structure level, are inevitable and exist regardless.
The point is then to determine the best way either to restructure the organisation, or, the code base, to cope.
The problem is that the software usually keeps expanding until programmers find it hard to cope. If you split teams up so that some people are only concerned with a certain part of the codebase, chances are you are going to grow the size of the codebase by a quite large factor.
I think there should be an incentive in place to keep the codebase small and understandable by most.
But if your code is living as a few hundred or thousand readable lines in the common codebase, that isn't really a problem. The code is there, readable and working, and if anyone needs to change it they can. If it falls out of fashion, it can be deleted.
Aggressively small teams, with no hands-off middle-management layer.
You can build massive capability around a small number of well-managed message-backbones and a single codebase. By keeping the number of hands small and the structure flat, you force high standards. (Skilled staff won't tolerate distractions caused by bad engineering or inadequate automation.)
Heuristic for analysing firms: who has strategic power in decision-making? Conventional answer: a group of hands-off middle-managers who run on meeting tempo, and who are valued by how many people and systems report into them. Under AST: an engineering effort running on maker tempo in cooperation with a hands-on sales effort.
Microservices tend to have multilateral contracts with other systems in the organisation. This steers all planning towards meetings. This creates middle-management bloat.
That's a tricky way to measure, given that I can eat a large pizza myself in a single sitting ;)
That's not to say that some people are better than others at certain parts of the codebase, but you don't want people fighting to keep old cruft in because it's on their job title (figuratively speaking).
You can organize around customers, use-cases, platforms, concerns or other things. Some might naturally map 1-1 to software components, but the software component should not be the "reason d'aitre" for a team, rather the customer experience or something else which can transcend multiple interations of the software.
Is it wasteful to have whole teams at GOOG, FB et al owning and improving the state of the art of infrastructure? It depends. At a certain point, there are enough internal customers for teams to reach contribution margin positive on engineering initiatives that have no direct but only second order effects on customer experience.
Both have their boatloads of suck, neither is inherently better. Interestingly, trying to mix them to get the benefits of each doesn't seem to invalidate any of their downsides; often it exacerbates them.
My argument here is that it's not so much the size of your team but rather the size and scale of your project that needs to be taken into consideration.
I'm just curious to know, when you said "the easiest and most sensible thing to do is chunk out large parts of the project and put them aside as a microservice" ... were these chunks separately deployable units for external "public" use.
You need to be disciplined to keep a monolith highly modularized. For microservices, in contrast, their architecture encourages modularization.
Microservices actually make you encapsulate your code, at least within the microservices, because you can't call out to it directly. They don't necessarily force you to implement the single responsibility principle, but they do a good job of pushing you. Microservices implement a service-locator pattern through DNS or web routing, one form of the dependency inversion principle. Microservices make you pass data around as entities, instead of Active Record instances.
The price for this sort of thing is very steep, though; distributed systems are inherently icky, harder to trace, and more prone to failure, and besides this, you've added network overhead to each service call.
I wish more engineering teams would consider spending half the effort of microservices on simply disciplining their monoliths. They might get somewhere...
In my experience, if your services are developed by the same people, and not separated by teams, engineers will often tightly couple the services with fragile and opaque dependent changes regardless.
While in monolith this is painful, at least you have a complete stack trace and the ability to run things through a step debugger you orient yourself. In a distributed system tribal knowledge tends to be your only savior.
When we design systems, we need to spend more time thinking about what is most likely to happen as opposed to what we feel should happen.
100%. This is an uphill battle, though. I've encountered so many engineers who equate "real engineering" with "building giant machines." You just can't convince them otherwise.
I've watched people build giant, real-time stream processing pipelines compromising tons of moving pieces (lambda, sqs, s3, sns, stepFunctions, etc..) to build... a reporting table, and all for... 1.3gb of data. Literally.
Ultimately, despite the "sell," I don't think microservices as a forcing function for good practices works in practice. If the team lacks the skills to build a disciplined monolith, then they 100% lack the skills to build a distributed one.
There is this trend in technology - every few years everyone changes their minds about everything:
* 2012 - sql is the best.
* 2016 - sql sucks, nosql is the future
* 2020 - nosql suck, sql is the best.
* 2024 - {fill in the blank}.
The same thing is happening with microservices. But in addition docker, kubernetes and recently unikernels have joined the party. The concept is the same though.
What I am trying to say is that either of those can be good or bad in different scenarios. It's a question of picking the most appropriate one for the situation.
Sun RPC, CORBA, DCE, DCOM, XML-RPC, SOAP, REST, gRPC,....
Honestly, I have never been in an organization so large that this became a necessity (if you solve tens of different problems, that would require almost thousands of developers). But coordinating single developers without an API is hard enough already, I can only assume for teams its nearly impossible.
At least, that's what I understand from his comment.
It's inevitable that the longer the codebase exists, the more difficult it is to maintain. It's a battle that you can't necessarily win and it's turtles all the way down as your dependencies, and their dependencies, tackle the same issues.
All it takes is one or two roughly defined APIs and you've already created the nucleation point for ever-more tech debt, and while you'll be able to tame some of it you won't manage all of it due to business requirements, or other teams depending on private APIs to save time, or whatever else you can imagine. Switch the architecture and you'll either have all your problems bunched in one codebase, or you'll have distributed your problems all over the place.
I'd go as far as saying that a perfect monolith and a perfect distributed architecture are theoretical ideals that require perfect communication to build them.
After 2 years we had over 450 microservices while keeping our AWS bill flat or slightly decreasing.
Presumably you're getting significant value out of the additional engineering work in which case the architecture shift probably makes sense (to stay aligned with the expanded organizational structure), but there are also cases where a small and flexible team maintaining a simple monolith would be much more nimble and cost-effective.
Presumably by definition we’re talking about a few hundred lines of code, or a couple of weeks development time here at most. What does this team do all day otherwise?
1. Microservices will let them ship things faster or
2. It's microservices everywhere or nothing
Microservices might let you ship faster if you are really good at deciding where to draw the lines between services and really good at managing multiple deployment pipelines and all the infra - that's a pretty tough ask.
Also, if you have a monolith it's perfectly fine to pull out one or two parts that need to scale much more efficiently and leave most of your codebase in the monolith, but a lot of times I see companies think once you have created one microservice the monolith is now the worst thing possible and it needs to be broken up entirely.
My general rules for this are to always start in a monolith and break things out as they start to fail or break other parts of the codebase, and don't go all in just because you now have one microservice that works well by itself
1 monolith and 2 "Micro-Serivces"
Working in the monolith is fine, but running tests is slow because it is a giant rails app that is 7+ years old.
There is 1 "microservice" that does its thing and the few people who need to interact with it like it.
the second microservice was created, deployed and abandoned. Now people want to move it into the core monolith. It is a distinct unit of functionality that doesn't really have any overlap with the core app. I'm going through and adding all of the tooling to this project because it enables us to solve a certain class of problem (Report generation) that the monolith can't do very well for a couple of reasons. Articles like this have fueled the fire to re-combine it but the pain points have nothing to do with this particular service being separate.
If your co-workers argument is just "microservices bad" then obviously they are making a mistake. But in the general I've seen far more frequent inappropriate splitting of monoliths than inappropriate combining of microservices. (this is honestly the first time I've heard of it.)
My last two "assimilations" where because one microservice was written in Java. The original guy left and no one (around the company) likes to touch Java (or pretend they don't know/do it) which means it was alaways me who had to update it. It was a very small service, likely why he thought small = micro! but it was about 3 hours of work in the monolith. Now anyone can update/contribute to it and not bug me every time
The other was a microservice that only served the monolith. New features required the monolith to be updated in order to realize the new features.
One implication of this is you need to ensure your APIs are backwards compatible with any other services - even if it's only one service that your team also manages. This also includes databases, if shared by multiple services (which I won't get into, suffice to say congrats, your database schema is now also a crappy API).
As soon as you start having concurrent deployment dependencies -- that is, the updates for service a + b both have to be deployed at the same time or things are broken -- you've effectively built a monolith anyway, just with an annoying code layout (eg, spread across multiple repositories).
You can use orchestration to tie these deployments together, but this means you're effectively building a monolith with a microservice architecture. Is that really what you want?
Versioning APIs is a pretty standard way to get around this.
If your deployment relies on synchronized service deployments you really dont have independent services at all.
I don't think such a blanket statement is justified. There are plenty of situations where it may make sense to pull out some functionality into its own service--so it can be written in a different language, scaled independently, isolated from failures, or whatever--but where giving that service its own separate database would be serious overkill, complicating ops and introducing potential data integrity issues for no real benefit.
"If your deployment relies on synchronized service deployments you really dont have independent services at all."
So what? That's really the point: blindly following the Microservices (TM) doctrine is often a mistake. It's better to just solve whatever problem you're facing in the simplest possible way. While that may mean by-the-book microservices with independent databases, in many cases something in between is a better choice.
"It's better to just solve whatever problem you're facing in the simplest possible way."
I completely agree with this, but I dont beleive that syncronizing deployments across multiple services is ever simple - have been in this situation at a past company where it would take an entire week every 3 months to do a deployment
Your concerns about versioning and deployments are certainly valid, but I don't think they outweigh the costs of turning your data layer into a distributed system until a project gets very large or those issues are actively causing you headaches.
I actually kinda do want that, although maybe it's a niche thing.
It would be nice to be able to deploy a monolith that's already cut at seams where there's an obvious API boundary. At one extreme, you could imagine a single binary where processes communicate via RPC.
What that would give you is an easy way to split off microservices as they're needed.
I'm sure somebody has done work along these lines.
https://gocardless.com/blog/getting-started-with-coach/ was the framework.
That idea had a lot of influence from Smalltalk, where the natural way of developing was in a monolith. So tactics like that which are about decoupling by default were a good idea in that context.
Microservices -> monolith : difficult refactoring
Microservices with poorly chosen context boundaries -> microservices with well chosen context boundaries: very difficult refactoring.
This is why Martin Fowler recommends starting with a monolith and refactoring into microservices unless you have extensive experience building out very similar applications in the same domain.
I think "5 Whys" might be a useful exercise here.
Why was building X as a microservice faster? [reason]? Well, why was that?
My general rules for this are to always start in a monolith and break things out as they start to fail or break other parts of the codebase, and don't go all in just because you now have one microservice that works well by itself
I like this. A key tactic is to always do things, such that one can change one's mind!
I've been trying to advocate for a "solar system model of services", where you have a big core application in the middle (the sun), surrounded by helper services of various kinds. Your important business logic can be left alone, but the database, other data stores, functions, timers, queues, integrations with third-party systems, one-off jobs, and other things can all stay in orbit.
There are benefits that you get from multiple services that you don't get from a monolith: having to rely on service discovery instead of hard-coding addresses or passwords, being unable to assume that the server your code is running on will live forever, and requiring a concrete CI-CD pipeline to get your code up-and-running are all good things to have, no matter your model, so it's important to have a clearly-defined process for them. A service-oriented architecture can give you that — put down the pickaxe, you don't need to split the monolith in two.
Yep, and so many organisations ignore these. Especially after a less successful transition to micro-services.
"you mean you want to spend more time doing non-customer visible development? You just did that micro services thing a while ago!"
"Yes, but to take proper advantage of that we need to invest in the right infrastructure and tooling"
"how can I sell that?"
That doesn’t mean a micro service architecture is wrong... just that you have to either learn from your mistakes or hire an amazingly talented team... that have learned from mistakes somewhere else.
Really small, reusable/shareable and stable domains are the sweet spot for microservices. As you say that is most likely to come from decomposing from a monolith. Microservices can really help with building rich domain components in an overall architecture and removing complexity from other components through delegation to the microservice is my experience. They just don't need to be everywhere.
The same problem of over-eagerness is becoming apparent with some of the movement to event-based architectures. People become obsessed acolytes and there is no other way. When in fact they may well be ideal for a portion of your overall system architecture but are unlikely to serve it all well.
What good do any of these architecture decisions make when the experience for the user, the customer, is measurably worse? I mean, aside from not being able to interact with elements of a page before a chain of JavaScript finally gives the all-clear, sites clearly look worse with grayed placeholders and whatnot. There should be a Conway's Corrollary for revenue-oriented choices.
Disclaimer: I run a Segment competitor. I'm pretty biased, but still...
[0] https://github.com/segmentio/prevent-default/blob/master/lib...
[1] https://github.com/segmentio/canonical/blob/master/lib/index...
[2] https://github.com/segmentio/clear-env/blob/master/lib/index...
The premise of segment.io is that there are lots of tools that take user behavior data from your site and it's a lot of work to integrate them all. For example, when a user signs up, you may tell multiple different tools that a user signed up:
- You tell Mixpanel so you can create graphs of how many people signed up.
- You tell Google Ads so Google knows a specific ad just resulted in a conversion.
- You tell Optimizely so it knows a specific page from an A/B test just converted.
Before Segment, you would need to write code for each tool separately. This doesn't sound so bad, but it becomes a pain when you have dozens of different tools and dozens of different events you want to track. With Segment, you only need to tell Segment that someone logged in. Segment will then send that event to all your other tools. You can think of it as like a multiplexer for user behavior data. Instead of integrating 10 tools, you just integrate Segment.The challenge with Segment is you need to write custom code for every action you want to send into Segment. This is bad for two reasons. Usually the end user of Mixpanel/Google Ads/Optimizely is a non-technical person that doesn't know how to write code. What they have to do is file a Jira ticket for an engineer to add a new bit of tracking to the website. Depending on the size of the organization, that person can end up waiting two weeks or more in order to start tracking a new bit of data from the website.
The other challenge is people often don't know what to track ahead of time or forget to track something important. For example, if you launched a new feature two weeks ago and forgot to setup tracking on it, there's no way to get that data back.
Freshpaint solves these problems by automatically collecting every user action upfront. Anytime someone clicks a button on your site, that fires an event in Freshpaint that someone clicked that button. You can then use Freshpaint's point and click UI to say that whenever someone clicks that button that is a "login" event. Then you can send that event into different tools. This is great because the point and click UI allows a non-technical user to send data into different tools and because we track everything up front, even if you forgot to track something, Freshpaint will still have recorded every instance of that action. That way, even if you decided you want to start tracking some action today, you can use our "time travel" functionality and recover every instance of that action since you installed Freshpaint.
IMHO it doesn't matter if you replace microservices by components, corba objects, rpc objects, soap services, etc. It all boils down to chopping your software into smaller bits that than immediately start having a need for sending messages between them, finding each other, defending their boundaries, etc.
So, the first mistake would be assuming this is a new problem to think about. It's not. You can find similar debates about how to chop up software ever since people moved beyond just having their code ship in punch card form.
The right discussion to have would be first deciding whether you want to break down by your logical architecture so that your deployment architecture reflects that or your organization diagram (aka. Conway's law). Then the next step is deciding whether your primary goal is network isolation of unrelated chunks of code or enabling asynchronous development of these chunks of code (if so, there are other solutions). Usually it boils down to, again, Conway's law: different teams just don't want their stuff to depend on shit happening in another team because of internal bureaucracy and hassle.
Now say you have a valid business reason or technical reason for actually wanting to have different stuff be isolated (e.g. for scaling reasons or security reasons). The next step is deciding whether this means you also want to break up your code base. Monorepos and microservices are a thing. Look at e.g. lerna for node.js, or multi module gradle projects on the jvm. In Go this is well supported as well. If you're really sure that you don't want micro services because of Conway's law there are lots of valid reasons for having a well structured mono repo with a bit of reuse of shared functionality, a simplified review process and more visibility in what is happening.
IMHO people do this for completely the wrong reasons; like wanting to try out some new language, organizational issues, etc. that ultimately result in fragmented code bases, lots of devops overhead and complexity (it's never simple or cheap), lots of project management overhead, etc. You pay a price.
That is a big red flag. Microservices that suffer from shared code changes are not really microservices, but a distributed monolith instead.
They might well all share the same basic framework code, of course. Why not share code for recurring concerns like auth?
If you don't want to do that, then you need a simple shared library for that.
The problem is that there is no easy way to draw the line between "this is obviously a trivial library function we should just link into our code" and "this is something we can't share because it would create friction or break our isolation".
Auth is obviously a "service" but phone number formatting as a service seems extreme.
You clearly haven't finished drinking your Kool-Aid yet.
The auth service will likely have to hit a DB anyways. Assuming the microservice call has roughly the same network latency as the DB call and the DB has 0 response time, it would double the total time to perform the auth. It only gets more favorable as DB response times go up.
More generally, I think microservices make sense in scenarios where the time to process the request is longer than the network latency incurred by making it a microservice. Things that have to hit a DB are generally okay. Pure functional things that just compute on CPU and RAM are generally not, unless they're very computationally expensive like running a simulation or something like that.
So lets say we have shared code for doing something like phone formatting. My question for more experienced microservice practitioners is -- does it make sense to create a microservice for preprocessing data in general? Phone number-formating-as-a-service is excessive but creating a microservice for processing data where phone number formatting is just one aspect of this service makes sense to me. All other services can throw data at the data processing service and get data back in some sort of standard and expected way conforming to whatever business logic/processing rules required.
One common example is, instead of having a shared library that reads & verifies JWTs, use a gateway service that handles this before requests reach the upstream service.
This means changes to your organization's JWT code will only require a redeployment of one service, the JWT Auth service.
Microservices using shared libraries -> ok
Microservices "suffering" from shared libraries -> not ok.
If you change code that other services are using, you can break those other services. No way round that.
The only real difference is that services have a slow serialized network interface that fails 4 or 5 orders of magnitude more often than libraries, but can migrate over memory domains.
I generally avoid creating shared libraries, they're a trap. They have a very narrow band of usefulness squeezed in between the more palatable solutions of creating new services or just copy & pasting code and allowing it to diverge for each different use-case.
In other words, if you can share a lot of code between services, a monolith is actually an appropriate architecture.
For example: let's say you have lots of integrations, and you need to scale compute, and parse and generate common data sent to and from the integrations.
You can either have a monolithic integration service which you scale out on load; or you can have integration-specific services that scale out on load and share your data parsing & generation library. Due to multiple concerns, there's no "best slice".
FWIW, scaling out compute is a stronger argument to me for a service boundary than responsibility segregation. Scaling out requires distribution; scaling up complexity doesn't, though it can help for other reasons, like CI/CD. I prefer FaaS architectural patterns with the freedom to share libraries in different functions (images) to services, especially if long-running state is not needed.
Having a shared library is not a bad thing on its own. Making the library a bottleneck is the anti-pattern.
If you wish to have a shared-library of microservices you should be prepared to have multiple versions of it running at the same time without any pressure to update everything at once.
If your shared library is the bottleneck, it means that your microservices are tighly coupled (hence the distributed monolith)
Shared code shackles everything together, like global variables...
What does that mean?
Why can't you have both independently deployed microservices and a shared code base?
If the deployment lifecycle is different for each microservices and each deployment is self-contained, then they can be deployed with different versions of the code - even if they use the same source tree and share code.
Obviously the shared code needs to be properly maintained and evolved, but it seems to me a lot of the software engineering problems occur when people move away from source code dependencies - with great tooling - versioning, diffs, debuggers - to other types of dependencies ( shared libs etc ) where the tools are non-existent or very simple.
Now granted if you needed to fix a critical bug in the shared code - that would require a redeploy of everything, but that happens much less frequently than the need to deploy a single service with immunity as long as your keep your microservice contract. It also means the discipline of making sure every services is deploy-able at anytime is kept to.
And if you didn't share code - you probably wouldn't be fixing a single bug once, you'd have much more code, with many more bugs.
This is what everyone does, so I can't even comprehend what Segment was doing. Maybe they were deploying a fleet of microservices inside a monolithic deployment? If so, there's no wonder it failed.
Seems to me, though, the problem is people trying so hard to reuse code. That's the main problem cited in the article. People get really gung-ho about reusing code and creating shared libraries, but reusing code is actually bad most of the time. You should strive to only depend on things that you can reasonably expect to not change, and that you don't need to update even if a new version comes out. What you're supposed to do is take that code in the shared library, and make it a microservice, and obey the usual backward/forward compatibility rules.
Using a monolith hides that problem because the code remains easy to update and build, but just as fragile and in need of heavy testing whenever you change code modules that have multiple consumers. That goes against the idea of mono-repos as well.
Disagree here, in general. I'm not in the ruby hyper-DRY camp, but copypasta is not the solution to dependency management problems.
Creating shared libraries does require discipline; you should do your best to just avoid breaking changes ever, and on the rare occasion you must, you need heavy communication and testing to ensure consumers find out about the change. And you can only change the API of the library; you can never incompatibly change how the library interacts with other services. I get that this is hard, but it's worthwhile if you can do it right.
We have thousands (maybe even tens of thousands) of lines of share library code at this point. Some of it is probably not necessary, but most of it we'd be completely lost without. Reimplementing core logic and utility classes and auth code over and over again is a great way to burn out your developers and create bugs. And these bugs are even worse than your garden-variety bugs, because you have to track them down and fix them over and over, and each fix is slightly different because each reimplementation is slightly different.
I agree that sometimes sharing code is a bad idea, but asserting it's bad "most of the time" is completely antithetical to my experience.
So it's easier to just have separate repo's - and then that makes sharing code sensibly a nightmare without additional tooling... etc etc because there isn't a single versioning system.
Everything as a separate git repo has a lot to answer for in my opinion.
Microservices are a modern re-branding of service-oriented architecture, but 'microservices' sounds cuter and less like it belongs in Java-land, and there's some theoretical idea that splitting your app into even smaller pieces will somehow make the whole thing better.
SOA/microservices solves a few problems and introduces a great many. The original SOA proponents were pretty explicit about this. Beware! There be dragons here! But one of the main pieces of prescriptive advice from domain driven design is helpful for splitting into distributed services: split along domain lines with minimum inter-service dependencies. Payments is an obvious one. Microservices seems to buck much of this advice in favor for a blissfully ignorant principle of "small" or "isolated". Good luck isolating something that is not meant to be isolated.
Scaling software is hard. Scaling teams is harder. Trying to scale teams by scaling/distributing software is an understandable goal but extremely hard to pull off because of additional complexities and costs you incur in doing so. Dev gets harder, deployment/ops becomes harder, testing becomes harder. Cross-team communication, documentation, API publishing and adherence goes from being very low impact within an org to suddenly being critically important.
To do SOA/microservices effectively you need complete organizational buy-in, and you have to commit completely to developing tooling and solving all the associated problems in moving to a services approach. Often, it's easier to just put it all back together, organize the code in such a way to minimize merge conflicts and wait for the ungodly slow test suite to run in CI. There are good reasons you rarely hear SOA/microservices success stories outside of enormous companies (Netflix, Facebook, Google, Amazing, etc). Doing this stuff takes an enormous investment and commitment from the entire organization, and there are just lower friction ways to skin this cat if you don't operate at mega web scale.
Growing a monolith is hard. Growing a microservices/SOA architecture is also very hard. Growing is hard.
Exactly, and done right that quite often means big 'microservices'.
All too often I see the 'functional programming disease' where the aim is to deconstruct to the smallest possible reusable functions ( 'micro' services right? ), often prematurely, creating high levels of compositional complexity and with zero tools to help you understand how the actual 'app' - say payment system works if it's distributed across 20 services.
Yep each single microservice is simple - but the payment system might not be and that's what you need to understand - better if your payment system is one thing - with maybe one or two things separated out if you need to scale that part.
I always find it more interesting what's _not_ in the single microservices, the stuff you do see. When you make a diagram with boxes and arrows, the interesting stuff would be the arrows, not the boxes themselves.
The common mistakes that I see are for two services to share read access to a common database, or to discover each other and send RPCs to each other. Both really dangerous for exactly this reason! The common database obscures how the two communicate with each other, and invariably everything connected to a database becomes one service -- call it a "mini-lith" if the overlapping sets created by databases do not cover the whole architecture. The problem is the preponderance of implicit arrows; when I reason about what it means to make this datetime nullable so that I can store such-and-so, I need to consider whether everybody who can read that datetime will be prepared for its nullability.
RPCs and APIs are the same way. I add a contract about what I am outputting and then everybody needs to know about my contracts and I must commit to them or else modify all of my consumers. So because the arrows are bi-directional everything just becomes one monolith again.
Instead, I recommend message brokers -- all that pubsub stuff. A given service tells all the other services simultaneously "this happened," and it is their responsibility in their codebase to listen for that event and then say "okay, then this must happen." Publishing a new version of the event is done by just emitting both the old and the new version of the event and perhaps having a shared standard for deprecation across the codebase so that you get deprecation warnings in your prod logs.
Every service has its own database and they generally only communicate to each other through these broadcasts, makes the arrows into the "stuff you do see".
Kind of the whole point of software is to take a bunch of complexity and make it simpler for the end user. The fact that the software visually looks like shit or is difficult to work with because it's difficult to reason about is a rather unfortunate side-effect, but usually has no bearing on actual business success.
There are lots of things we should try to do to reduce software complexity and make it easier and safer to work with and change. But trying to force simplicity by way of size usually has the opposite effect.
So if any fad/trend comes along promising the sun and stars of simplicity or productivity, search IT history and find the downsides and trade-offs.
I wish there were more KISS pundits than fad pundits.
Totally agree, but I think this is underappreciated by many. People tend to wave this away by just saying we'll just use Swagger/gRPC/whatever-doc-gen-tool, but that's not the main problem. The problem is that each service needs to have some coherent purpose, and must adhere to that purpose. Changes to that must be reflected through a proper API change and migration. But that requires thought, discipline, and (sometimes slow) work.
When you inevitably run into a situation where you could instead throw a quick hack into the wrong service that will make things work now the temptation to do it very strong (bonus points if this is due to regulatory changes). But now you have an undocumented behavior dependency between those services - they're coupled in a subtle way. And eventually the accretion of those results in a distributed monolith instead of a plain-old monolith.
>domain driven design is helpful for splitting into distributed services: split along domain lines with minimum inter-service dependencies
Definitely, yes. But then when your business evolves in some way that causes all your domains to be inappropriate, you're up a creek again. I don't think there's really a solution to that, though.
If you push authority and decision making and responsibility for a service to a (2 pizza) team then guess what, microservices work really well.
If you have vast monolithic centralised production operations teams, and no way in hell is their C-Exec going to assign two of them to look after the user-login service, you might not do so well.
Like most things, the organisation needs to change to get the best out of the opportunities software offers. Those that don't will face increasing friction and eventually die off.
hire("developer", 10, "10x).addToPayroll().office("openplan", "wfh").enforceHRPolicies()
Is all you need
The best thing about this is that you can keep everything in change control and just rollback whenever you need to, or spin up new companies at will.
Occasionally companies actually do this by fragmenting divisions into separate companies, such as outsourcing IT. It has a very broad range of outcomes, from saving to destroying the business.
I've never understood why it needs to be either/or. Is it really that difficult to support a microservice deployment that only represents 50% or even 20-25% of the org/project?
Well finally I might get my own microservice after all.
There are shitty hierarchies and shitty flat organizations, just like there are shitty monoliths and shitty microservices.
Sorry if you actually agree with this more nuanced view, it's just that I've seen Conway's "Law" invoked more than once in this discussion and it drives me bonkers. I get the same way when someone ("Medium Developers" I call them, more than green but less than seasoned who swallow everything the read on Medium as gospel and run around quoting it zealously) quoted liskov substitution principle at me as if it was one of Newton's Laws.
It's also obviously true. The organization builds the architecture. The architecture either helps or hinders the organization. The organization builds a new architecture. There's no indirect connection here. If you've seen hierarchical organizations implement microservices, it's because that organization's complement was a microservices architecture. And likewise for a chaotic organization.
--well, sidetrack: Aren't strongly hierarchical organizations the best suited for microservices? With all the strongly divided responsibilities and whatnot?
It's like a tautology: "In logic, a tautology is a formula or assertion that is true in every possible interpretation."
If you have hundreds of engineers then certainly microservice architecture starts to make sense, since even the idea of transactional deploys of the monolith break down due to queuing at that scale. But jeeze, don't pull that trigger until you actually find yourself backing up on necessary complexity like deploy queues, PRs stuck due to inability to maintain the branch given the velocity of master, etc. Don't let Conways law lead you prematurely to microservices. If I'm ever in a position where I am feeling real pain that leads to an urgency for microservices, I am probably going to first ask the question if I can just fire some people to make the problem go away. The risk of the transition to microservices is just that high.
It's the same rule of thumb with other things like hiring, feature roadmaps, etc: YAGNI. If you are hiring someone before the pain is so high the work cannot be done otherwise, building features before you have people explicitly showing the need for them, or making deep, cross cutting architectural changes that impact everyone before they are strictly necessary due to concrete problems with shipping software, you're probably choosing the wrong use of opportunity cost, capital, etc.
Turning everything into an object can make a small program into a big program, so it’s maybe not such a good idea for small-scale stuff.
http://www.solipsys.co.uk/new/TheParableOfTheToaster.html
However, in my experience, OOP made it possible to do really big stuff.
It’s all about not having a “one-size-fits-all” approach. I don’t think it’s just about scaling architectures; it’s about changing architectures to match scale.
It’s difficult as hell to make these changes, because people get invested in methodology, and insist on applying the same lens to everything we do.
It sounds like they had the right idea, but they probably had the wrong people.
In my experience OOP actually makes programs smaller. Assuming of course they have good programmers/architects and the program itself is larger than "Hello world".
Why don't you try to read carefully what you've just said in the sentence above
Of course, if you guess wrong, you're totally fucked. Well, either that, or you are smart and see it coming in time to rewrite the code that put the complexity in the wrong place.
Hope that helps with the context. I'm not some anti-OOP zealot, and those do exist.
In fact, I have been running into folks, these days, that don't understand it, as, apparently, OOP is becoming "uncool."
I've always been a "right tool for the right job" kind of guy. I started off with ML (Machine Language, not Machine Learning). I am quite comfortable, sitting down with a breadboard, and flashing an OS.
But I remember the old days of OOP, where "classic" structured programmers didn't "get" OOP, and designed these horrific chimeras.
I always make it a point to understand my methodology and drivers "to the bone." Just because someone at a conference said it, doesn't mean that I should use it for everything.
But I have made it a point of personal ethos not to post criticism or polemics, denigrating/excoriating the work of others.
I know that could buy me a lot of clicks (and probably some considerable HN Above The Fold time), but I think we have enough negativity and finger-pointing on the Internet.
If you read my stuff, you won't see much of that. I may, in a rather vague way, allude to something that gives me a frowny-face, but I don't want that to be part of my "personal brand," so to speak.
I do take tremendous personal pride in my work; both coding and writing, and hold myself to a high bar. I may even project that bar onto others (only in some circumstances), but I don't think it's helpful to do so in public.
I find it most gratifying to write a "This is how I do this..." post, as opposed to a "This isn't how you should do it..." post.
In some cases, it is not the best tool for the job, but I find that I tend to use OOP for almost everything I do; large or small.
It isn't of much use in small utility scripts, though, and some languages are just not written to natively support it. In those cases, procedural (or FP) is the way to go.
There are new-ish methodologies, like functional programming, and protocol-oriented programming, that deprecate "classic" OOP. Some folks are using these as backing for declaring OOP "dead."
I suspect that might be a bit...premature. My current fave lang is Swift, which pretty much allows you to use any methodology you want, or mix them together (Will it blend? That is the question).
I have found that it isn't helpful for me to "write off" any methodology, and most of my work is actually a hybrid approach; with elements of multiple methodologies.
BTW: I'm a "lots of words" kind of guy.
Prolix, JAMES Prolix...
Those are anything but new-ish. In programming new often means that some old concept suddenly becomes fashionable. Myself I do not restrict to any single paradigm and use what I believe is the most suitable for current task.
What does happen, though, is that a canon develops, based on these technologies, and [actual] new techniques get created, based on them.
Some of these are nightmares, and need to be strangled before they can crawl off the slab, but sometimes, a gem comes up.
I write about some of my experiences around that here: https://littlegreenviper.com/miscellany/concrete-galoshes/#e...
I remember writing "object-oriented" software for classic C, in the late 1980s. I called it the "faux object" pattern, and was based in state being kept in a structure that was passed around functions. I refined and formalized the idea when I encountered Apple's QuickDraw GX in the early '90s (I suspect that may be the only good thing that I ever got from that sad debacle).
I used the faux object pattern in an SDK I designed in 1994, and it's still being used to this day. Back then, you couldn't pass OOP across module connections, so we had to figure out a way to do it with C.
Nowadays, I can easily pass Swift extensions and virtual implementations across SDKs; no sweat. It's cool.
In general I agree that people should study more the past, but I do not see any point in dismissing new trends just because the do not market their full genealogy upfront.
Perhaps it's more prevalent with OOP programmers, but perhaps it just appears that way because the boilerplate for classes is a bit larger than the boilerplate for functions and structs.
With procedural code, you would need an exceptional programmer to produce a big program. With OOP, an average programmer can deconstruct a problem into its component parts and solve it, mainly because, the human brain can reason about concrete objects more easily, than say, abstract methods like functional programming.
Edit : OOP has encapsulation which, in my view, significantly reduces the cognitive load when thinking about state management in an app. I remwmber writing a small graphics library using Borland Graphics Interface in Turbo C++. It was a breeze to do because I know about 'things' I want on my screen and coded my classes to reflect those things.
I started working remotely as a consultant in the early 2000s when my wife and I moved to a remote area. I had several development jobs that used the same monolith pattern: I would embed everything in a web app using Apache Tomcat, taking advantage of work threads for background tasks. The only external services were a database and crontab settings to frequently snapshot databases. This pattern was so easy to code to, so easy to debug and deal with any runtime problems. One customer reported that a system ran without stopping for six years (ouch, no OS upgrades??) until they restarted it on a larger server.
Micro services can be great, but not always the best choice when horizontal scaling is not required.
From an end user perspective, Netflix runs in “constantly degraded” mod.
From an engineering perspective, they track “number of successful stream starts”, instead of percentage of the time 100% of their services are working. That’s a huge red flag.
As a researcher, the monitoring and fault-propagation / modeling work they’ve done to get it to stay up at all is impressive, but it’s not clear all of that tooling would be necessary if they didn’t have to reason about N^2 fault tolerance scenarios, where N = 100’s of microservices. That’s on the order of one fault tolerance scenario for each atom in the universe.
You answered your own question with Netflix. While you're right that it's not clear Netflix would've needed to develop their chaos monkey tooling and the like, it's not clear at all at that an equivalent system is possible as a monolith. Even if a monolithic Netflix system were technically possible, it's not clear if a monolithic system would be organizationally feasible either (Conway's Law)
But the reason I say this is about the team is because I've seen a well built, well groomed service be passed off to an outside team and immediately turned into a disaster. Same service, same business case, just a team without the DevOps savvy and willingness to follow the patterns.
Netflix does some.. unusual things with microservices, mostly with how they treat version rollovers. It's not bad or good, but it looks different from how many other shops handle the same problem and it means asking the question "is everything working?" is extremely difficult but asking the question "how many people are able to start watching?" is pretty easy.
Netflix runs quite well in practice. I think they do redundant service calls, what is the only minimally sane way to develop a distributed system. The funny thing is that I have never seen any serious discussion of redundant calls, except for it being implicit on practical designs on the anecdotal "how it works for us" articles that pop once in a while. Most times people won't even discuss redundant servers. I imagine everybody thinks it's obvious, and well, I would agree, except for the fact that most people I see do not think so.
But well, Netflix couldn't avoid having a distributed system, so they aren't really representative for nearly anybody.
That doesn't seem true. I would imagine that at Netflix scale, you probably have request tracing libraries that can give you a graph of service dependencies. Whether it's worthwhile to consume that, or easier to just let Chaos Monkey run rampant is another question.
Also, I very rarely have issues with Netflix. Typically when I do I can just exit the stream and restart it. Anecdata, but I could count on one hand the number of times I've had a title just not play, or Netflix be down entirely.
Not to say that wouldn't happen for SPOF microservices (e.g. your auth servers), but the surface area is potentially larger for monoliths.
In every service architecture I've worked with in the last few years, you could theoretically run every single "service" within the same physical process -- provided they all shared the same runtime/lang the way a monolith does. I tend to start service architectures using Ruby exclusively with the Eventide toolkit, so this is actually a viable approach for most teams I've worked with. But it's never ultimately made sense. Weighing the pros/cons, a consolidated deployment topology wouldn't add any benefit, and it would actually make it far more difficult for the operations folks, practically speaking.
I've helped put services into production that 1. carry out crucial, "core" business logic reliably and efficiently, 2. can be scaled horizontally without changing the code, 3. never raise exceptions or cause outages, and 4. don't need to be touched for years because new features are more naturally composed around them anyways (i.e. open/closed principle). Practically speaking, it's quite a bit easier for human programmers to reach this degree of precision with a smaller program than with a much larger one. And if you add up a lot of these high quality programs, you get a high quality system. Building a high quality system out of a single program is much, much harder for humans in practice.
I'll use crude terms here for a second: every good service put into production brings a net benefit to the organization relative to the same code having been entangled with an existing pile of code. That may sound like a "No True Scotsman" fallacy, but the definition of "good" in this context is precisely the net benefit being added. If you build and deploy services that have a high degree of quality, you get a corresponding benefit. If you build and deploy programs that _don't_ stand on their own, _don't_ leverage durable messaging, and take other programs down with them when they crash, then you get a rather large mess on your hands. In fact, you never had a service architecture at all; we call this failure mode a "distributed monolith."
I'll acknowledge that most attempts at microservice architectures in the wild don't seem to succeed. Anecdotally, they are particularly prone to failure when their architects don't understand the underlying principles of distributed systems all that well; they neglect important considerations like messaging idempotence, the deleterious effect of synchronous request/response messaging, and the need for deliberate, thoughtful design. In other words, they build systems out of N number of microservices, and get N^2 fault tolerance scenarios, which you adeptly called out for being foolish. They're arguably worse off than if they went with a monolith, but neither would be an architecture I'd personally want to work with.
Though the transition is still in progress, I can say that the path forward is clear and hopeful. This company had previously explored other "SOA" paths (distributed monolith) and it was clear that those were very problematic. Luckily, we were able to steer them to an actual evented architecture.
As I said, it's still early days for this particular project, but if you're feeling your monolith is getting harder and harder to develop on, you should start looking into evented architectures. Eventide (ruby)/messagedb (postgresql) are awesome technologies and would be the first I'd consider. Eventide also has a nice slack community filled with people that are learning and improving their software design skill both in the large (architecture) and the small (individual classes/etc). It's small, but they're good people.
My point being that if and only if we have a track record as an entire field of making decisions based on case studies, AND if case studies have a track record of being objective rather than proffered as a result of a marketing agenda, then the question is ultimately legitimate.
There are more shops by total count that fail with monolithic architecture. That's inevitable just based on the infinitesimal number of projects executed as service architectures rather than monoliths at large in the wild. But still, we carry on with monolithic architectural style as if its outcomes were assured.
It's far easier for the vast majority of developers to build a monolith because it allows development to proceed without having to have any knowledge of or practice with the tricks and traps of distributed systems - an entire body of knowledge that a developer might never get meaningful and practical exposure to for an entire career.
The trouble starts when microservices are attempted by developers who can't imagine that there are entire bodies of software development knowledge that developers aren't presently in possession of.
Microservices is just, as Adrian Cockroft used to say, "Service-Oriented Architecture with bounded contexts".
Web development is absolutely not a preparatory course in service oriented architecture. But the vast majority of web developers who attempt to take on SOA while simultaneously presuming an omniscience in all things software development due to their experiences only with monolithic web development will often fail to build a SOA. They usually end up with something that isn't quite SOA and isn't quite a monolith. And that's where the failures largely come from.
I work exclusively in microservices and SOA, and have since 2015. Before that, I worked principally as a web app developer, and did some work off-and-on in SOA implementations. And before that I spent years becoming oriented to the architecture. I don't make the mistakes that web developers typically do when they presume that web development knowledge is a sufficient prerequisite for working in SOA.
So, it's not a question of whether an architectural style works or doesn't. The majority of failures in microservices can be attributed to ignorance and to the narcissism that is permissive of it.
So, I would ask this question instead: Are there any cases where developer over-confidence and over-simplification went well?
These qualities don't tend to serve any architectural style well.
I've never heard of a well-designed SOA not going well. Every single case of microservice project remediation that I've participated in had as the most significant contributing factor an utter disregard for the body of knowledge that the microservices architectural style is built upon.
There are a lot of things that developers can get away with when doing the kinds of tinkering and wandering that typifies typical web development work. But those things don't work once we cross the line into SOA. And unfortunately, the incessant chasing after trivial resumé candy hasn't prepared the average developer for the rigorous mindset needed for SOA work.
As "microservices" became to next fad for perennial fad chasers of the software development world, they finally encountered a kind of work that they could not get away with by faking it. And so, we see a lot of failures. But the vast majority of the failures are personal failures and character failures, rather than failures of an architectural style.
The fat part of the developer bell curve was simply overreaching when it presumed to try to get away with building service architectures with the same level of disinterest in architecture and process that we can get away with in typical web development. Like a kid with copious experience building kites presuming to strap themselves to a hang glider and just "going for it". The outcomes are mostly predictable.
In the end, if a little time is invested in learning the fundamentals that have so far been eschewed for the sake of expediency, anyone can succeed with SOA and microservices. It's not that the realities of the architectural style are unlearnable, but it can't be arrived at by the level of tinkering and wandering that we can just get away with in monolithic web development.
There are lots of examples of successful companies using microservices, but I believe the real problem is in defining what constitutes a microservice. Most people call things "microservices" that are nothing of the sort. I can unequivocally say if you built a "service" that depends on other things being 100% available (like another "service") than you haven't built a microservice (ie: those things you built shouldn't be called services).
By that token, autonomy is a pretty important factor. The Udi Dahan teachings (https://particular.net/adsd) (currently available for free) promote this style of architecture. A concrete example of a toolkit for building true microservices can be found in Message DB (https://github.com/message-db/message-db) and/or Eventide (http://docs.eventide-project.org/)
I wouldn't suggest, however, that anyone can just watch the course, pick up these tools and succeed. Like baking a good loaf of bread, it takes a lot of skill, work and experience. Whether or not you succeed at building microservices is ultimately up to you and your team.
They don’t measure how often 100% or their services are up because perfect uptime is not the goal and is too expensive (if it’s even possible). If an internally facing service being down doesn’t affect a core metric like number of stream starts by customers, then it’s foolish to treat it as needing 99.999% uptime.
"Having a code repository for each service was manageable for a handful of destination workers"
Microservices should be introduced to make teams go faster, not to decouple external api endpoints....
Realizing this and circling back is still a useful life lesson.
And if you happen to leak concerns in your services (in a monolith), it's really easy to adjust, as opposed to having to coordinate the deployment of 5+ services.
And even then, a distributed monolith is still a risk.
Micro-services add cement to your project. Be prepared to keep boundaries you write for a long time.
Discipline is fundamental to good software engineering: you can’t force it with Microservices.
This article for me is more about the complexity of managing a large team across different sites where the architecture needs to change rapidly when modularity is absent. They did get a measurable benefit around performance, though. I wonder if Alexandra will comment on the challenges of running a team in an environment of this complexity?
It's the drift and inconsistencies across these concerns across projects that makes deployment and operations less predictable.
I think this article is more evidence against the credibility of multi-repo than against "microservices".
Anecdotally, my current place of work has grown to about 200 engineers, maintains a monorepo, and hundreds of deployed cron jobs, ad-hoc jobs, and "microservices". We have none of the problems discussed here. We invest maybe 20 eng weeks a year in monorepo-specific tooling, and perhaps another 30 eng weeks per year in "microservices"-tooling.
Don't make the divisions too large, don't make them too small.
The art is to make them the proper size for the particular company.
If you have a small company you don't need divisions. If you have a large one, you need to make divisions as you no longer can speak to every single employee.
There are definitely some good insights here that I don't often read about. The idea that with a sufficient number of microservices (say 50+) you not only treat your instances as-cattle-not-pets you have to treat the service types en-masse as-cattle-not-pets. This requires more automation and organized management as pointed out by the need for tuned autoscaling rules. This requires continued investment into automating things you would do manually if you had 50- services.
The other thing to consider is that going to microservices and back to a monolith is not necessarily a failure. Microservices are good for periods of high change velocity, once a platform is mostly built requiring much less new development consolidation completely makes sense. At all points, we're solving for impedance mismatch, whether that's the org structure, velocity of changes, or numbers of developers vs numbers of deployed units.
And of course the Microservices section should be really thin, while the Monolith section is extremely thick.
Like PopeDotNinja pointed out, you can just flip the book over when one approach starts to peak.
https://www.youtube.com/watch?v=dcfwLhDDMz4
>VCF East XI -- Ted Nelson: Ted Nelson designed the Xanadu hypertext software and wrote the two-in-one personal computing book, Computer Lib / Dream Machines, in 1974. His work deeply influenced the personal computing revolution. Ted earned two Ph.D.s and penned several other well-regarded academic papers and books about ethical, historical, and moral issues in computing.
https://en.wikipedia.org/wiki/Computer_Lib/Dream_Machines
>Computer Lib/Dream Machines is a 1974 book by Ted Nelson, printed as a two-front-cover paperback to indicate its "intertwingled" nature. Originally self-published by Nelson, it was republished with a foreword by Stewart Brand in 1987 by Microsoft Press.
>In Steven Levy's book Hackers, Computer Lib is described as "the epic of the computer revolution, the bible of the hacker dream. [Nelson] was stubborn enough to publish it when no one else seemed to think it was a good idea."
A team that fails to understand how to write modular code, is just going to write spaghetti RPC calls, while having to deal with all the traditional failures and performance issues of distributed computing.
Naturally it is a recipe doomed to fail in the large majority of cases, but it doesn't matter because whoever drove the change is no longer at the company and a new consulting team/new hire gets the money to drive everything back to the monolith.
So goes the money around on plenty of consulting gigs.
This is interesting. I always assumed we were talking about good developers here.
I wonder what's a more likely cause for a failed attempt at microservices. Is it developer incompetence and lack of discipline, or is it environmental factors related to the product and the organization?
(My favourite example is John Romero, part of the very small team that produced Doom - but who also produced Daikatana, which keeps showing up on lists of notoriously bad games.)
https://en.wikipedia.org/wiki/Daikatana
>One advert for the game became notorious; a 1997 poster containing the phrase "John Romero's About To Make You His Bitch[. Suck It Down.]". According to Mike Wilson, the advert was created by the same artist who designed the game's box art under order of their chosen advertising agency. Originally, both he and Romero thought it was funny and approved it. Romero had second thoughts soon after but was persuaded by Wilson to let it pass. Speaking ten years later, Romero said while wary of the slogan at the time, he went along with it as he had a reputation for similar crass phrases. In the same interview, he noted that reactions to the poster tarnished the game's image long before release, and continued to impact his public image and career. In a 2008 blog post concerning the recent activities of Wilson, Romero attributed him for the marketing tactic. This prompted a hostile exchange of public messages between the two at the time.
At least he apologized, though:
https://www.youtube.com/watch?v=BF_sahvR4mw
John Romero Apologizes for Trying to Make You His Bitch:
https://v1.escapistmagazine.com/news/view/100748-John-Romero...
>I'm going to quote our very own Shamus Young here for a moment: For almost a decade, Ion Storm's Daikatana has been the example of "industry waste, arrogance, and incompetence, as well as a universal punchline for things that suck." The shooter was supposed to be an epic vision, the masterpiece of John Romero - the mastermind behind genre-defining Doom and Quake.
>Then it came out in May of 2000, and it sucked. The arrogance and hubris that crippled Daikatana have been well chronicled over the years, but none of it is quite as infamous as the ad you see here to the right: "John Romero's About to Make You His Bitch. Suck it Down." It was a pretty ballsy statement in itself, but after the game's failure simply became laughable.
John Romero Is So Sorry About Trying To Make You His Bitch:
https://kotaku.com/john-romero-is-so-sorry-about-trying-to-m...
>Game designer John Romero and John Romero's hair ruled the roost during the 1990s. With titles like Doom and Quake, he not only helped popularize the first-person shooter, he defined it. Then the unthinkable happened. He made Daikatana.
>[...] Romero, who now says he is resigned to the ad, dished on the ad back in 2008, which evoked a saucy response from the marketer that spearheaded the suck-it-down campaign.
Romero Dishes on the Ad:
https://web.archive.org/web/20081225071219/http://kotaku.com...
>[...] these are the kinds of jackass stunts he pulled [...]
Suck-It-Down Campaign Marketer's Saucy Response:
https://web.archive.org/web/20081225070532/http://kotaku.com...
>[...] and ill advised breast implants strewn across this fair nation [...]
When you have 200 developers working on the same product without well defined boundaries it will be a mess with a monolith, and a mess with microservices.
Also arbitrary rules such as "one service per team" or "one service per employee" that force engineers to jerry-rig things that don't belong together. Either allow them to make a new service, make or a new team, or admit that microservices are not single-purpose and are just a blob of multipurpose code. I've seen this way too much.
Also pure organizational inertia working against good engineering practices: sometimes a team will be severely overworked while others are overstaffed. But god forbid there's a temporary reorganization to improve the work of engineers, so people send tasks to other teams, but there's minimal communication between engineers.
There is no substitute for a good model and responsibility separation, (micro)services or otherwise.
Writer Michael Feathers has an article where he suggests that Microservices are a replacement to encapsulation, all we have to do it use encapsulation well.
https://michaelfeathers.silvrback.com/microservices-and-the-...
We didnt stop at monolithic service. We stopped at monolithic repository+organization. There is now 1 VS2019 solution that covers our entire business. All of our services can be built out from this one solution via various configurations. We've even created additional solution "views" for more focused work (i.e. so you don't have to load a bunch of projects you don't care about for a specific task).
At this point, if we ran into scalability issues with a monorepo, I'd start looking for better source control/CI/CD technologies rather than splitting things up in hopes of arbitrarily keeping git viable. The benefits of having all of your source code in 1 repository with strongly-typed models throughout are impossible to overstate. When checkbuilds complete successfully in GitHub, we know the entire business is clean. Not just 1 little aspect of the product stack.
On one end we have a real monolith: a single executable binary, with no external dependencies apart from the OS bindings. This is very rare in practice, and most commonly found in games and probably mobile apps; when it comes to Web-based services, even a traditional idea of single-codebase app usually has a SQL database as an external dependency.
On the other end of the scale there is a complex system consisting of hundreds or even thousands [0] of tiny services that require complex orchestration mechanisms such as a service hub or service mesh.
So each team (in a wide sense: could be a company, organisation, department etc) needs to consider where they fall in the continuum, considering a) which architecture will provide most benefit while b) still being maintainable by the team; both the architecture and the team need to evolve together.
On one end you have a giant monolith. All services rolled into one which includes your API, Middle ware and then Database.
One the other end you have microservices which bundle services into individual distinct units with each service being responsible for its API, middleware and database.
Are there any preexisting patterns which seek to combine these two and come up with an architecture which is midway between those two. A few months ago I had read an article about Data Oriented Architecture on HN which comes close though I'm wondering if there are others.
Layered microservices are an antipattern. In most cases, functionality is best divided by domain.
So, SOA?
https://m.signalvnoise.com/the-majestic-monolith-can-become-...
[0] https://monzo.com/blog/we-built-network-isolation-for-1-500-...
We recently had an interview candidate say this when we questioned the wisdom of having over a thousand microservices: some in languages that only the one developer maintaining them used! For me this is insane, but I digress
Monzo says that they have 800 people, and 1500 services. If we're generous and say 500/800 are developers, then each developer is responsible for 3 services! A team of 6 would have 18 projects in their domain.
Two organisations that I know of who favour the latter are Spotify and Netflix. It has benefits - different languages are good for different jobs and engineers like to be able to choose their tools.
It would be bad if this was taken too far, and something was written in a language only one person knows, but that problem already exists with the technical knowledge if something only has one mantainer.
funny in my experience with digital and particularly larger corporate jabronis. The people who makes decisions are fucking McKinsey consultants who know nothing about the actual project, are only contracted for 6 months, then they are gone. Rinse and repeat, maybe one out of every 3 or 4 attempts somebody actually gets it right and the project doesnt completely fail.
I'm a bit confused, they seem to imply that they need a microservice per costumer/destination, but you generically have one instance (aka process) per costumer not an entire separate codebase. The article seem to use the same term for two different concepts. Or i am missing something?
update : and related video https://www.youtube.com/watch?v=lv5o3qnQu5w
Everyone seems to have their preferred style of coding, and it is an easy defence mechanism, when presented with anyone who tries and finds it wanting, to say that "Well they didn't do it properly".
You find that with Microservices vs Monolith, Strong types vs Weak types, Exception Handling vs Results, Agile vs Waterfall.
People fragment into camps which turn into echo chambers and it's easy to dismiss anyone who doesn't commit to that cult as being unpure and not worthy of being in the cult anyway.
It's incredibly tough to know the full effect of a trade-off on your organization until you start going down that path.
We're early in the process of adopting a micro-service architecture. With only a handful of services so far, I can already see how a team of two is going to spend a lot more time with operational issues and debugging.
I work in a org that migrated to microservices over time, but intentionally adopted a monorepo approach as part of it. It works quite well and seems to avoid a lot of the pain points expressed here, while also gaining the benefits of microservices.
There are definitely tradeoffs to the monorepo approach. It makes development on shared libraries more delicate and stressful, however this can be mitigated by more robust cross-service CI, and I definitely think it's a worthwhile tradeoff to the painful cycle of shared lib versions diverging across services and finding issues when some service finally gets around to upgrading its version weeks/months after shared lib changes.
That usually results in abandoning the effort to actually map the use case of your particular application, model your services to the size that make sense to your project... Any big enough system will need some services or workers beyond a single monolith, it doesn't matter if they say they follow micro-services, if they follow any other type of SOA or whatever, these silver bullets are killing engineering. Every project needs to take time to be planned, thought it, refactored, analyzed. If you read a bunch of shit on HN and go applying you end up with a random monster.
If you already have a platform like Kubernetes/OpenShift (preferably with a service mesh) micro services make a lot more sense to me and can be done well. It gets easy to deploy and scale independently, but still have very low latency communication with a strong security model built in.
If you are deploying everything to completely independent platforms/infrastructure, I get a lot more conservative with "what should be its own service" and what shouldn't. Building a distributed monolith (a bunch of dependent services that aren't reusable/composable) is the worst of all worlds.
Which is nearly verbatim what some of us have been telling you since before the term microservices was coined.
Coupling is the problem. Yes, microservices add friction to coupling, but they don't prevent it. Coupled microservices exist (boy howdy), and they're resource intensive, resistant to evolution, or both.
Especially if you consider RedwoodJS, a new full stack JS framework that's build on Prisma technology (their stuff is an alternative to Rails Active Record ORM). My takeaway is that they provide a similar monolith like experience by acting as a glue between different services.
Exactly my point when I was working on a new project architecture and we had to choose between two authentication methods. Because once all your applications rely on an authentication system you can't just switch to the other one like that... Sadly we did not take the time six month ago and today we are working on a migration which could have been avoided.
They just went from naive monolith, to naive micro services, then to smart coupling of the two...
You are just building services on top of another monolith...
Sounds like you needed to abstract the work being send to the worker, instead of abstracting the worker around the work.
Meaning don't have many workers for one payload type. Abstract the payload and have a single worker...
That's why most systems become complex and spaghetti. Poor abstraction, so you use shared code to fix it...
Did they go back to a monolith service or a monolith repo? It really just sounded like monorepo
Microservices will require more time spent writing interservice APIs, and code execution will be slower since many procedure calls will require data serialization and network requests. But we believe it will be worth the overhead to not be locked into PHP for every new component of the project.
But, it's extremely hard even to do that. Microservices simply complicates things if any of your domains need to share code with each other. Many DDD paradigms exist to address this, but none are practical. For example, authentication related code. IF one domain sets a cookie and the other one has to rely on that to keep the user (a shared model between the two domains) authenticated, then this means, you need to duplicate code bases across two domains or in the very least put them into some sort of shared helper/library, which DDD is kind of against.
That's why it totally makes sense to go Monolith first and really identify the parts of your application that are slowing you down either development wise, testing wise or performance wise and put them into separate contexts.
Phoenix actually does Microservices right. From all the way to scaffold generation to instructing best practices on keeping your domains properly separated. But even then, I've burnt my finger many a times trying to write simple CMS solutions into mutliple microservices then going back to monoliths again.
So the problem here, to me, is how you design the modularity for your system, not how you implement it.
What if I told you this is unrelated to microservices? You can keep all of their sources in the same repository, sharing dependencies.
Performance per watt/dollar of computing.
How is a monolith different from a monorepo?
When it makes sense to use microservices, it makes sense.
Doing anything for no reason whatsoever never makes sense.
That is all.
There's also a clear conflict of interest with SAAS and Cloud providers benefiting from the perception that microservices are the way to go.
Under these circumstances, letting someone else figure out all the issues is the wise thing to do. Thanks to the authors for doing just that.
Within software module interfaces that can only communicate with one another via socket like serial interfaces with no type checking!
Or simply have all your software modules running as forked processes on the same hardware and have them all communicate with one another via sockets or http. That means every software module must be it's own server!
To further imitate microservices, make sure that code in one software module can never ever be moved to another software module. Make it hard to reorganize things. Also make sure teams can only ever work on one section of the code base.
Does the above make any sense to you? If it doesn't make any sense to you it's probably because code organization using microservices doesn't make any sense period because the examples above are literally doing the same thing in software that is done in hardware.
If it does make sense to you, then why are you using microservices to add extra complexity to the code? If you can do the same in software then you'd be doing the exact same thing as the hardware equivalent minus the extra complexity of multiple containers or VMs.
Don't use hardware to organize code, use code to organize code and use hardware to maximize performance.
You can organize code with functions and namespaces, why do you need hardware to segregate code? It only makes the segregation permanent but offers nothing else beneficial in terms of code organization.
The underlying reasoning was always that developers tend to move outside of boxed software modules if it wasn't enforced by hardware so the modules will end up not being modules but everything will be blurred into monoliths.
I always figured that if you want really hard lines drawn between software modules you can still do the same stuff in software itself, like why do you need actual silicon or VMs/Containers to do it?
The only real need for different services is performance, otherwise all the benefits and downsides of microservices can be replicated in software.
Or perhaps there are other reasons you're simply not recognizing? Like your sarcastic tone?