3 ms·
In the first diagram I see well-encapsulated services B, C and D and their orchestrator A. In the second diagram I see ill-defined responsibilities and a loomi
by LudwigNagasena 2y ago
In the first diagram I see well-encapsulated services B, C and D and their orchestrator A.
In the second diagram I see ill-defined responsibilities and a looming callback hell.
What if we could look at more concrete examples with a dozen services that model a real process? Maybe that would elucidate the benefits.
- xpe 2y ago> In the second diagram I see ill-defined responsibilities Why do you say this? Yes, passing a message along to another network service is another step. But whether or not it is an additional "responsibility" is debatable I think. It depends on what your baseline is. 1. If the baseline is a synchronous, in-process RPC, then, sure, an additional network call is an extra step. (Even so, I don't necessarily view this as a problem.) 2. But if your baseline is an asynchronous RPC, remember that there is often some mechanism to report status back to the caller. (This is part of the cost of doing business in async.) There would be no net difference to pass a result forward compared to back to the caller. Tell me if my logic is not sound. (I wonder if you have fallen for the fallacy of overlooking what is 'normal' and criticizing what is different? Just a guess. Maybe, maybe not.) > and a looming callback hell. There is nothing intrinsically wrong with using callbacks. Callback hell is a syntax problem with some languages. In particular, visually it looks unappealing to programmers. Visual appeal (or not) is not necessarily coupled to wise design decisions. Especially in distributed systems there are a different set of drivers than, say, in-browser JS constraints (event loop, rendering, etc). Example 1: Clojure, for example, uses macros (core.async) so that programmers can write code that is indented one level deep and looks like there are no callbacks. But underneath, callbacks are still used. Example 2: In Rust, callbacks aren't used under the hood for async/await. Instead, the syntax maps to a state machine. But the general point holds: a state machine could be hellish to write explicitly (call it "state machine hell" perhaps, haha) so Rust provides a better syntax for it. Broader point: some people conflate how code looks in some particular language with overall quality. Beware of taking this to an extreme. I have been guilty of this from time to time, especially when doing a lot of Ruby long ago.
- LudwigNagasena 2y ago> Why do you say this? > Yes, passing a message along to another network service is another step. But whether or not it is an additional "responsibility" is debatable I think. It depends on what your baseline is. It's just my feeling based on my experience. What if there are 15 services? Who decides where to route and why? I want to see how it would work in practice. That's why I asked for something more concrete than A, B, C, D. Maybe it would actually be easier and more straightforward, I don't know. > There would be no net difference to pass a result forward compared to back to the caller. For example, A knows it has called B, so if there is no response it knows that it has to handle an error, do a rollback and whatnot. > There is nothing intrinsically wrong with using callbacks. Callback hell is a syntax problem with some languages. Right, bad wording on my part. If it's A's responsibility to say where to route the result to D, is it also its responsibility say where to route the result of D? Would I have to specify the whole call chain? Do I pass the whole call chain to both B and C? That's what I meant. What if the request didn't originate from A but from Ω? And B, C are also called to be routed to D by another service called by Ω in the chain sometimes. As stated in the article, D waits for B, C that originate from the same call, which is Ω. Now someone who maintains A is going to have very good time trying to debug that to understand why sometimes D is called with wrong values and then maybe even crashes or returns an error. > a state machine could be hellish to write explicitly (call it "state machine hell" perhaps, haha) so Rust provides a better syntax for it Every computer is a finite state machine and we are using mental models like async/await to hide this pesky fact and simplify our lives.
- xpe 2y ago>> There would be no net difference to pass a result forward compared to back to the caller. > For example, A knows it has called B, so if there is no response it knows that it has to handle an error, do a rollback and whatnot. Good point. Many application designs wouldn't work as-is in the "pass forward" approach. I hesitate to say in response: "well, then you're architecting it wrong". But perhaps there is a grain of truth in that. Maybe it is hard tradeoff.
- xpe 2y agoThanks. Add the corresponding diagrams and this could be an interesting blog post!