I have developed a somewhat similar system for Python / C / C++.<p>I declare my data structures in YAML. From this I generate code for memory allocation; data validation; serialization and de-serialisation routines.<p>I also declare the topology of the data flow graph in a separate YAML file, decorating each edge with the data type.<p>Finally, I have a separate YAML file that specifies which computers/processes each node will run on.<p>Nodes communicate by passing messages. For nodes in the same process, this is simply shared memory (zero overhead). For nodes in different processes that share a memory bus, this is a shared memory queue. For nodes on different machines, the data goes over the network.<p>Nodes can be python, C or C++.<p>Very nice. Very declarative. The bare minimum of duplication and boilerplate. Changing from MIL to SIL to HIL is (mostly) just a configuration change. Logging, measurement and replay is built into the framework, as is support for parameter tuning.<p>Within a single process, the model of computation is synchronous data flow. Across processes, it is asynchronous data flow, but because we have queues with blocking reads and nonblocking writes, we still keep the ability to replay data through the system in a deterministic manner. (Modulo nodes exceeding their time budget).
Honestly, I'm surprised by how these very opinionated and seemingly evidence-lacking posts that propose "new cool solutions" are able to get to the front-page of HN. They spice me with some information "look it'll be easy to test" and immediately slap me in the face without a shred of evidence and without even referencing similar or prior research - at least a comparison to event sourcing seems necessary.<p>Not the author's fault - he probably just felt the need to share his thoughts on this cool new idea he had.<p>I just wish HN users would take better care of the implications of clicking the +1 button. You're wanting others to read compelling information you've found.
Alan Kay, one of the fathers of Object Oriented programming mentioned this :<p>> I'm sorry that I long ago coined the term "objects" for this topic because it gets many people to focus on the lesser idea. The big idea is "messaging"<p><a href="http://lists.squeakfoundation.org/pipermail/squeak-dev/1998-October/017019.html" rel="nofollow">http://lists.squeakfoundation.org/pipermail/squeak-dev/1998-...</a>
As a first-class language feature this goes back at least to 1985 and Linda <a href="https://en.wikipedia.org/wiki/Linda_(coordination_language)" rel="nofollow">https://en.wikipedia.org/wiki/Linda_(coordination_language)</a> and tuplespaces. (You'd know about Linda already if you'd read <i>Concepts, Techniques, and Models of Computer Programming</i>. Don't delay, read CTM today: <a href="https://www.info.ucl.ac.be/~pvr/paradigms.html" rel="nofollow">https://www.info.ucl.ac.be/~pvr/paradigms.html</a> ) And here's a Martin Fowler talk about the practise of implementing it informally as a pattern or framework: <a href="https://www.youtube.com/watch?v=STKCRSUsyP0" rel="nofollow">https://www.youtube.com/watch?v=STKCRSUsyP0</a>
Highly dynamic behavior like this is great in very clear scenarios but think carefully before using it for the majority of your program.<p>Dynamic behaviors are gained at the loss of type safety (function calls are type checked, messages are usually not) and system coherence (just because you can add and remove components on the fly, doesn't mean that it's <i>safe</i> to do so). To compensate for these losses, you need to increase documentation and increase testing. Most people don't and the result is significantly less reliable code.<p>> Coupling between callers and responders is removed.<p>But the number of dependencies is the same (reference versus message type). And now it's unregulated dependency without any compiler-level contract between the two. Are the parameters still what you expected? Are the parameters in the correct format? The message type will continue to match, even if these details change.<p>> Messages can be dispatched to multiple subscribers<p>Which is great for communication that is <i>intended</i> to be observable but if you're using public message passing for everything, it's going to be tempting to snoop on the message bus and pull data intended for other recipients instead of passing it cleanly. Now your program is a fragile mess because everything is dependent on precise implementation details of unrelated parts of the program.<p>> Dependencies can be changed on the fly.<p>Which is another way of saying that you need to handle dependencies changing without notice. What happens if you're expecting a response but the generator of that response is <i>removed</i> between request and sending response? What if you get a timeout? What if you get a partial response?
To say that it reduces coupling is deeply misleading, IMO.<p>In order for your application to do things, certain flows must execute; A is followed by B which is followed by C for some specific functionality.<p>Without MOP, you might have A calling B, which calls C. This is straightforward to read and debug. If the interfaces between parts are complicated, they may not be easy to test - but this can be fixed.<p>With MOP, A dumps its output on a bus, and it's picked up by B, which dumps its output on a bus, and it's picked up by C. Now, in order to understand the composition, you need to trace the thread through the entire system, probably using string search - and not be certain you've got it right, because of modalities that may not be explicit.<p>It looks like the dependencies have been eliminated; but it's a complete illusion. They're are still there, they're just dynamic instead of static. C still needs to follow B which needs to follow A. If any of these bits are missing, the system is broken because it won't be functioning. And even worse, this situation won't be found statically without extra tooling.<p>MOP and its close relative event-driven programming is effectively COMEFROM as a first-class feature. It promotes write-only programming.<p>There certainly are reasons to go down that route - for example, you need to execute a lot of unrelated side-effects based on state changes in the system - but don't be under the illusion that it reduces coupling. The coupling is still there because the system breaks when dependencies are absent. It hides essential complexity, not just incidental complexity. Your incidental complexity burden needs to be pretty high before the pros outweigh the cons.
> <i>Coupling between callers and responders is removed. This makes life much easier when we want to refactor our code, since objects don’t directly reference one another.</i><p>This is listed under advantages, but I'd say it's a mixed blessing at best.<p>Objects no longer directly referencing eachother means it is now way harder to reason about and analyze the dependencies in your codebase.<p>The dependency graph no longer exists in design time like it did before, lending itself to various static code analysis tools that could even draw you a pretty diagram thereof. Now it's grown and shaped in run-time.<p>Event buses are sort of like a "goto". It's sure convenient to be just sending events back and forth through some static event bus, but once things go south, trying to trace the root cause will turn you into Carrie Mathison.<p>In Android world, event buses are considered legacy now. One of the most popular choices, Otto, is explicitly deprecated (in favour of RxJava that the authors recommend to use instead).
The article mentions a Xamarin application -- that sounds interesting. I'd like to hear more about how far this approach can be taken in a single-process application. Are they using an existing message bus, or did they build one?<p>I'm more familiar with the traditional (SmallTalk-style) MVC setup where the data model entities implement the observer pattern. In that case, observers subscribe to change events by attaching themselves to model values (event sources). I guess MOP differs from this by replacing centralised model value objects with some kind of identifiers used to key message bus subscriptions (?).<p>Related ideas for client/server are:<p>Event Sourcing <a href="https://martinfowler.com/eaaDev/EventSourcing.html" rel="nofollow">https://martinfowler.com/eaaDev/EventSourcing.html</a><p>and<p>CQRS <a href="https://martinfowler.com/bliki/CQRS.html" rel="nofollow">https://martinfowler.com/bliki/CQRS.html</a>
Isn't this just an event driven architecture?<p><a href="https://en.wikipedia.org/wiki/Event-driven_architecture" rel="nofollow">https://en.wikipedia.org/wiki/Event-driven_architecture</a>
The benefits listed in the article can also be realised with simple java style interfaces and dependency injection. That approach also solves all three disadvantages listed.
The brand of loose coupling message buses offer seems like a mixed blessing to me.<p>In a more oldschool, bus-less message passing system, different modules can't talk to each other unless they know about each other. That's potential a source of tight coupling if you're binding to specific classes, but it doesn't need to be - you can also couple to a protocol, and be willing to chatter with anyone who speaks that protocol. But still, modules can't talk to other modules unless they're explicitly told about each other.<p>Pair this approach with an IoC-style architecture where the communication graph must be assembled in a single composition root that lives very close to the application's entry point, and you've got a great tool that gives you an easy way to monitor and manage the complexity of the application.<p>With a message bus, you're basically just taking that central composition point away and replacing it with a big ¯\_(ツ)_/¯. I've been there, it can be very flexible when you want to build an application quickly, since you don't need to think about how messages will be routed. But I haven't found that the message bus reduces coupling. Since it takes away basically all the need to stop and think about what you're doing before subscribing to a message, you can quickly end up with all sorts of crazy functional dependencies that nobody fully understands because everybody was hacking them together independently.<p>I guess what I'm cautioning is, don't be too quick to conflate loose coupling with message buses. The one does require that you do the other, but that doesn't mean you can't do the other without the one.
Or you could just use Erlang/Elixir and be done with it. Presented approach to message bus has its own share of problems, like mentioned in the article message-type explosion.
weird to assume a bus, since normal message-passing in something like Smalltalk doesn't, and neither does MPI (whose programs are obviously more prevalent, at least in terms of cycles consumed...) the bus-iness does make it look more like event programming.
When you have to update several data stores via messages to several groups, what do you do for transactions?<p>Seems you'll need a consensus layer and the ability to rollback if you don't want to end up in a terrible mess later right?
I agree with the author roughly about the benefits and disadvantages. And would probably add to the disadvantages that synchronous sequences are harder to model, since with pure asynchronous messaging all components will get state machines.<p>Regarding the advantages: Interfaces and dependency injection (with or without frameworks) also solve the first two bullet points also rather nicely. And they keep the synchronous nature and strictly typed interfaces.
The third point can be achieved with data binding or interfaces which allow for subscriptions (e.g. via classical "Event"s or Observables).
I think this is a very interesting direction.<p>Classic programming generally doesn't impose restrictions about which direction the data flow is.
By default, methods take value from their callers as input, and return values to their callers as output.<p>An "notification"/"fire-and-forget" interface allowing no data to go back to the caller (no return value, no "output parameter", no visible change to the program state, from the caller's POV) has very interesting characteristics (Like being trivially mockable).
I like the general idea and have felt the same way for a while. For the benefits they list a message bus isn’t necessary. Even dispatching to multiple subscribers can be straight-forward with a proxy object. Instead you can design your OOP system to behave as though it’s passing messages and get a majority of the benefit along with a straight-forward path to refactor towards a message bus if/when you’re ready.
This seems similar to Cocoa's NSNotificationCenter, where you can post messages or subscribe to messages.<p>One of the better Cocoa books motivates it with the following example:<p>Say you have an app with multiple windows. You also have a settings pane, where you can change the background color of your windows. You don't want the "background color" component to keep track of all of the open windows in the app, that's not at all related to its responsibility. So instead you use the notification center to post a message "hey the user changed the window background color to blue", and windows will subscribe to "the user changed the window background color" messages. This way the color picker and the windows themselves can work together, without ever caring about the others' existence.
What is the recommended message passing library for mobie {iOS, Android}? In particular, if one is mixing javascript within webView with native code, message passing between the native and js components can be valuable.
My Lua system uses MOP too, though I never heard that term before. And structured pipes. I like it and wrote it for a dataflow bsased event driven system.
I don't think this article is very good. The main achievement of message passing is to decouple call stacks, and the reason why you do that is that you get more control about which threads a function is called from. You turn to message passing because you want to get away from callback spaghetti. The article doesn't even mention concurrency.
One important advantage not mentioned is the sending side. You can respond to websocket messages, push notifications or HTTP callbacks using the same message structure so every new source of messages can be added with only one line of code and the receivers can be unaware of a new source.
The author mentions that messages cost more memory, because shared data must be copied. This can be avoided If you base your classes in immutable data. Then you can pass references around without worrying about modifications.
Under disadvantages:<p>> It’s harder to reason out program flow<p>This seems like a big one. You're basically creating a distributed systems problem for yourself.