4 ms·
> Large Erlang applications have a flat “bus like” structure. They are structured as independent parallel applications hanging off a common communication bus. T
by platz 11y ago
> Large Erlang applications have a flat “bus like” structure. They are structured as independent parallel applications hanging off a common communication bus. This leads to architectures that are easy to understand and debug and collaborations which are easy to program.
How is it easier to debug a message-passing architecture than a monolith? Observe the JS world is slowly moving away from using Pub/Sub for connecting components together. Each individual piece is easy to understand, but the chain of causality is very hard to determine with all those messages flying around.
- jondubois 11y agoI would say that the JS world is moving towards pub/sub but I understand what you mean about causality. I think the change that's happening is that devs are moving towards using a central shared store for all data (one source of truth) instead of making each component load/save data independently from each other.
- bcherny 11y agoI'm not sure that's a trend, but a peculiarity of Redux/Baobab. Without built in immutable data structures, I think this kind of paradigm will always be harder to work with in JS than smaller, distributed, mutable models.
- chc 11y agoIt's also one of the big ideas of Om, which I think is where it started catching on from.
- EvanPlaice 11y agoOr, you know... You could just use immutable.js. It's not like OOP languages where the built-in data structures are built up from many layers of inheritance that all have to be made immutable. Where access is blocked at the language level (ex private/protected). Immutabilit is not difficult in JS. It's just not immetiately intuitive unless you understand the value of comparison by reference. The == vs === always seemed like a strange vestigial appendage of JS. With the recent trends, it's beginning to make a lot more sense.
- lostcolony 11y agoImmutability is part of what leads to it being easy to program, as well. An actor in a given state, receiving a given kind of message, will always behave the same way (barring some abuse of ETS tables or the process dictionary or other bad practices). Meaning that to reason about the code I usually don't need to -care- about the chain of causality. I can break each part of my complex interaction into a series of "message received -> actor does something -> message sent", and trace through the code that way, turning what would be a big complex hairy problem of mutable state and race conditions in many other languages into a fairly innocuous, straightforward series of small, simple interactions.
- platz 11y agoYes, as I said, each individual piece (actor) is easy to understand, locally. But you cannot in general understand the global behavior of the system that way. One option might be to run an integration test, for example, but understanding why a certain actor recieved a given message will be difficult.
- lostcolony 11y agoAnd what I was saying is that in an immutable system, that's -usually enough-. It's not that difficult, because the reasons for messages being sent are predictable. You get a lot of emergent behavior in systems with mutable state that is avoided in systems that enforce immutable state, and without that it becomes far more tractable to trace back a series of events and reason about them. You got a message? Okay, who can send that message? If it's more than one, is there a reason why you aren't indicating -who- sent that message (in Erlang, including a pid as part of the message; this is done automatically any time you use a gen_server and send a synchronous message via call)? But regardless, what does that message mean? Is it the same across all senders? Because if so, your bug is in the implementation in this actor. If not, if the message means different things when sent from different places, you need to figure out what the different messages you want to send are, and send different messages for them. If you have mutable code, even mutable message passing, the time the message arrived changes what it does. Suddenly you have situations where the message being sent, and the behavior the actor exhibits, is dependent on mutable state. It's totally correct in some instances, and totally incorrect in others; completely dependent on what has happened in the system previously as to whether ~this~ particular message, with the coded behavior, does the right thing or not. And it's that, the interplay of all events that came before, that makes things complex.
- kasey_junk 11y agoMessage passing vs monolith's are orthogonal. Being a monolith has to do with deployment & life cycle boundaries. Message passing has to do with communicating state transitions between multiple logical components, possibly running at the same time (regardless of whether they are in a monolith or not). If you want to compare something with message-passing architectures a better comparison would be to a shared register. Both are abstractions around communicating a shared piece of state. The argument that debugging a message passing abstraction is easier hinges on the idea that the message passing coordination system decouples the sender and receivers state transitions from each other, where as in a shared register the state can be viewed as the state of both. In systems where there are more than a couple pieces of shared state it becomes untenable to put every component that can share state into the precise stateful configuration that you need to debug an particular case. It turns out in practice, that generally it is easier to break up monolithic applications that are message based than it is to break up ones that are using shared registers, but that is a side benefit of the abstraction not the real difference between the 2. I'm a huge advocate of log based architectures, which take message passing architectures to another level. That is, message passing architectures are good at limiting the things that can happen, log based architectures do that and add the benefit of capturing what did happen.
- kitd 11y ago> I'm a huge advocate of log based architectures, which take message passing architectures to another level. That is, message passing architectures are good at limiting the things that can happen, log based architectures do that and add the benefit of capturing what did happen. AKA Event-based programming. Events are what has happened. Components define what events to respond to and how. In practice you need a combination of events and messages (or 'commands'). Which one to use depends on NFRs like dependency, complexity, performance, maintainability, etc.
- kasey_junk 11y agoI don't know if there is a formal taxonomy of these things, but I tend break down messaging passing architectures along the what/how axes. So you can have events or commands being sent in your messaging based architecture as the what. Log based architectures really speak to the how. That is, the medium for message exchange is a durable log. But like you said, in the real world they tend to be hybrids across all these lines.
- toast0 11y ago> How is it easier to debug a message-passing architecture than a monolith? You can observe the messages as they're passed, which is usually enough. In my experience in Erlang, a lot of messages include a pid to reply to, so if you get an unexpected message, you can often see where it came from, and then look at the messages that came into that process, etc (or grep the code on likely identifiers). Erlang makes this pretty easy, you can also peek at the mailbox for a process to see what other messages it hasn't read yet, etc. This model works really well for me, because I've been using it for years to debug networking applications with tcpdump and friends: figure out what's gone wrong over the wire, then you can figure out where it's gone wrong in the code. Added: I guess that doesn't really address emergent behavior. Most of the emergent behavior I've seen is really around overload conditions, most of the debugging around that is focused around finding the bottleneck(s) (which is usually a problem in a simple process -- you can tell which ones because they have a huge mailbox), why there isn't enough back pressure (it's usually because the senders aren't waiting for confirmation, or aren't waiting long enough, especially in combination with retries), and how to shed load instead of falling over (which is tricky)