15 ms·
> Microservices was always a solution to the organisational problem of getting more developers working on a system at once. I don't really agree. It's probably
by staticassertion 4y ago
> Microservices was always a solution to the organisational problem of getting more developers working on a system at once.
I don't really agree. It's probably the most agreed upon benefit, but there are others. Fundamentally, microservices allow you to isolate state. I can take two services and split them up - and now I've split their state spaces. I can put a queue service between them and now I've sliced out the state of their communication.
This slicing up of state has a ton of benefits if done right.
1. Isolation of state is basically the key to having scalable concurrency. Watch the Whatsapp talk on Erlang and they say "Isolation, Isolation, Isolation".
2. Isolation of services is great for security. You can split up permissions across your services, limit their access in a more granular way, etc.
Those are pretty nice wins. They're achievable through discipline in a monolith (heavy use of module boundaries, heavy use of immutability - something most mainstream languages don't encourage) but a network boundary really forces these things and makes unintentional stateful coupling a lot more painful.
> It is easier to start with a monolith, find the right design and then split on the boundaries than it is to make the correct microservices to begin with.
I disagree. Starting with a bad set of microservices can be fixed by merging. Merging two codebases is trivial compared to splitting. Again, if I have isolation between the services, even if the slicing up was done badly, even if they're coupled, I can just remove the layer between them.
Splitting has to start from a place of coupling and then try to decouple - this is especially hard with languages that encourage encapsulated mutable state (most of them).
- manigandham 4y agoNetwork boundaries cause far more problems than they solve, and you've just shifted the complexity to now securing the network, usually with even more additional services, proxies, services meshes, firewalls, etc.
- staticassertion 4y ago> Network boundaries cause far more problems than they solve, They cause exactly 0 extra problems. A call from function A to function B can fail due to B having a bug. A call from service A to service B can fail due to B having a bug or a network failure. Either way, failure is possible and has to be handled - the network only makes that more obvious. Further, a call between functions can cause mutated shared state - not the case across a boundary, they physically do not share mutable state. > and you've just shifted the complexity to now securing the network Not really. Fundamentally you have split your service capabilites up - now you can apply least privilege as you desire.
- blowski 4y agoYou drop in “…or a network failure” like it’s a rare occurrence that’s easy to handle.
- staticassertion 4y agoFrequency is irrelevant to the complexity. As I said, you have to handle the idea of cross-boundary failures, such as with modules that have bugs. If you aren't taking steps to do so, you're not writing robust code. Anyway, yes, persistent network failures are rare for many people.
- blowski 4y ago> Frequency is irrelevant to the complexity So if something happens 50% of the time you should treat it in the same as if it happens one in 100 billion?
- staticassertion 4y agoMaybe? I can't compare 50% to 100 billion. If your computer crashed every 100 billion instructions that would be a problem. If it crashed every other instruction, that would be very slightly more (or the same amount) of a problem. The point is that if you have a function call, you have the opportunity for a bug/ failure. Networks don't change that - you have the opportunity for a bug/ failure. The major difference is that services have stronger failure isolation.
- blowski 4y agoIt sounds like you’re designing a very hypothetical bit of software.
- staticassertion 4y agoAs opposed to designing software that is already implemented?
- infamia 4y ago> I disagree. Starting with a bad set of microservices can be fixed by merging. Merging two codebases is trivial compared to splitting. Again, if I have isolation between the services, even if the slicing up was done badly, even if they're coupled, I can just remove the layer between them. There is still coupling in microservices, it has just shifted to messaging, networking, and queuing. If you get any of those parts wrong, you have a worse mess to untangle with less mature debugging/logging tooling than a monolith enjoys, all the while likely dealing with eventual consistency (depending on the design). I'm not saying don't start with a microservice, but it likely wouldn't be the very first tool I would reach for when starting out if a monolith would do the job effectively. Most things will never be hyperscale and won't benefit from the increased concurrency. You can go a very long way with a "majestic monolith" and a bit of care.
- atx42 4y agoI disagree. If you are able to merge them you spent the work to originally have them split. So more work to start with microservices. It goes back to agile, the easy solution is have a monolith and figure out later how it can be well-split.
- staticassertion 4y ago> There is still coupling in microservices, it has just shifted to messaging, networking, and queuing. Sure, in the sense that your service is "coupled" to a queue and if you don't abstract that away it's hard to change that queue implementation. But in the sense of two services you wrote being coupled, they aren't, in terms of shared state. That gets pulled out. There is no way for one service to mutate the memory of another - it has to send a message to it. That can be TCP or it can be over some queue or stream or whatever. > If you get any of those parts wrong, you have a worse mess to untangle with less mature debugging/logging tooling than a monolith enjoys This is the case with any concurrent system. The fact that so many languages lack concurrency primitives is probably why people don't run into this more often. If you use concurrency primitives in your language, you already have this. > all the while likely dealing with eventual consistency (depending on the design) There's nothing eventually consistent about this system. It fundamentally has causal consistency (since messages from a service must come after messages to that service that triggered them), and it's perfectly capable of leveraging transactions. > I'm not saying don't start with a microservice, but it likely wouldn't be the very first tool I would reach for when starting out if a monolith would do the job effectively. To each their own. I much prefer it. It's far simpler to maintain "good" design since the network boundary creates a hard line in the sand that you physically can not violate.
- a4isms 4y ago> Fundamentally, microservices allow you to isolate state. I can take two services and split them up - and now I've split their state spaces. I can put a queue service between them and now I've sliced out the state of their communication. Dr. Alan Kay would like a word. This is literally the premise behind OO: > "OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things."—Dr. Alan Kay Outside of "extreme late-binding," which is a fascinating topic in its own right, isolating state is exactly the point of OOP. If we need microservices to accomplish isolation of state, that suggests we got OOP wrong, very wrong.
- mbrodersen 4y agoThe Erlang programming language is AFAIK the only programming language used in production that does what Alan Kay is talking about. So it is probably the only Object Oriented programming language used in production today.
- pixl97 4y agoThen I'll have to say that most get OOP very wrong, for example any time an application crashes for any reason.
- seti0Cha 4y agoArguably, we did. In large codebases worked on by multiple teams it's not unusual to see teams drilling holes in OO walls because they "just want to get their work done" and view the abstractions as barriers. Taken along with the unfortunate fact that the majority of engineers suck at decomposing things into objects and the result is people preferring to move stuff out of process to keep the code simple and make the encapsulation more effective. I don't really think that's a good thing, but that's been my observation.
- a4isms 4y agoErlang/Elixir feels like it strikes a middle ground, where every process behaves like one of Kay's objects, including the emphasis on message-passing rather than methods that behave like procedure calls.
- rightbyte 4y ago> Fundamentally, microservices allow you to isolate state. You can have logical dependencies between those "isolated" states anyway so I don't see that as a benefit really compared to say Java OOP private fields.
- WookieRushing 4y agoWhile isolation is important for managing state, the other side effect of isolation is allowing separate scaling of resources. If you can scale up your number of workers for a particular emergency then things get easier to handle.
- staticassertion 4y agoIt also makes bin packing services much simpler for that same reason.
- ori_b 4y agoYou've moved the state around, but the state is still there -- just hidden. It's hidden in the network communication instead of the function calls. And the distributed, cross-service mutated state is a hell of a lot harder to trace and debug.
- staticassertion 4y agoI didn't say it removes state. I said it split the state up and isolated it. That's critically important - you physically can not mutate state across a network, you have to pass messages from one system to the other over a boundary, either via some protocol like TCP or via intermediary systems like message brokers. Joe Armstrong talks about this better than I'm going to: https://youtu.be/lKXe3HUG2l4?t=1438 https://youtu.be/lKXe3HUG2l4?t=1438 That timestamp is rough, I just found a related section of the talk. > And the network-defined state is a hell of a lot harder to trace and debug. There's no such thing as network-defined state. I assume you're saying that it's harder to debug bugs that span systems, which is true, but not interesting since that's fundamental to concurrent systems and not to microservices.
- xorcist 4y agoI think you have a very narrow idea about what "mutating state" really means. You seem to talk about DMA access only. But you can manipulate the state of an application by writing to a shared data store, by calling an API, and countless other ways. It is really more of a concept for us humans to define where an application begins and ends. Let's take an example. If we have two services that wants to keep the full name of a logged in user for some reason, that piece of state can be said to be shared between the applications. Should one service want to change that piece of data (perhaps we had it wrong and the user wanted to set it right), the service must now mutate the shared state. It does not matter whether it is done by evicting a shared cache or if we write the updated data to the service directly, we still speak of a shared state that is updated. Now we can stipulate that the more of these things we have, the more coupled two pieces of software is, which generally makes reasoning about the system harder. It is not as black and white as one type of coupling is considered acceptable and the other isn't, but some types are easier to reason about than others. Joe really thought hard about these things and it really shows in the software he wrote.
- dagss 4y agoRe immutability -- I would say a well written backend in any language would (probably?) throw away the entire state between each request being handled. It's possible to introduce state, sure, but why and how does that happen? For very many backends the only natural thing to do is to code them stateless, keep all the state is in the database, and each new request starts in a fresh world. I see two common sources of state in any backend (monolithic or not): 1) Caching, whether resources or flags, whitelists 2) Connection pools. If there are ever any issues with those they can be segmented inside a monolith for a fraction of the cost of going to microservices (either using the same boundaries as if you had split into microservices -- or other boundaries, like just one set of caches/connection pools per endpoint handler..) So I agree with the OP that the social aspect and development process is the only rational for microservices. Otherwise, just scale the monolith horizontally to the same number of instances and you have strictly more ways to partition state; microservices only give you one way to partition state that may not even be the best one.
- camgunz 4y ago> Fundamentally, microservices allow you to isolate state. I think it's not really state isolation: your state is now spread across multiple separate services and a queue, which is objectively more complicated. To me, it's more the extreme version of things like dunder methods in Python or opaque structs in C: it prevents a specific type of programmer behavior. But honestly, it feels easier to solve this in code review. Like, I agree it's bad to reach behind the public API of something, but microservices aren't immune to this. I've never worked on a microservice architecture that didn't have weird APIs just to support specific use cases, or had a bunch of WONTFIX bugs because other services depended on the buggy behavior. That's not fundamentally different than "this super important program calls .__use_me_and_get_fired__": you have an external program dictating the behavior and architecture of your own. And you get multiple other layers of complexity here: networks, distributed transactions, separate dependency graphs, securing inter-server communications, auth/auth. I don't think you're entirely wrong--there's a lot of history looking at state as a series of immutable updates (Git, Redux), and I think it is harder to "cheat" in this way using microservices. I just think it's far from a clear win.