Bus or Flow? - Messaging done one way or the other

Messaging is neutral to the way it´s done. There is nor right or wrong. You think of a message bus, when you hear about communication by messages in software? That´s fine. You think of “pipes and filters”, when you hear about messaging? That´s fine, too.

Messaging does not dictate specific means for how to send messages. All´s well, as long as messages are sent one-way and sender and receiver are oblivious of one another (Principle of Mutual Oblivion (PoMO)).

Let me show you what I mean with an example. Here´s a scenario:

A small application is to be written to count the lines of code (LOC) in C# files located in a directory tree.

The user should be able to select the root path of the.cs files. And once the LOC counting has started, the progress should be shown. At the end, the stats on the number of files processed and the total LOC are to be displayed.

To solve this problem, there are at least two parties involved: a UI and some domain logic. They should be communicating via messaging. But how?

Should there be a clear flow like this:

image

The processing gets initiated by some interaction of the user with the UI. Then data flows to the main processing unit, which outputs progress updates and the finally the LOC count stats to the UI. Cause and effect, flow of processing are clearly visible.

Or should the cooperating parties hand over their messages to a bus for delivery:

image

What´s the better way to connect UI and domain logic? Codewise both is easy. Here´s how the flow could get wired up:

// build var ui = new Form1; var interactions = new Interactions;

// bind interactions.Progress += ui.Display_Progress;

ui.OnFolderSelected += path => { var stats = interactions.Count_LOC_in_Path(path); ui.Display_Stats(stats); };

// run Application.Run(ui);

Output could flow through events (ui.OnFolderSelected, interactions.Progress) or be returned from a function (interactions.Count_LOC_in_Path). Input would flow by calling methods (ui.Display_Progress, interactions.Count_LOC_in_Path).

And here´s how the bus could be employed:

// build var bus = new Bus; var ui = new Form1(bus); var interactions = new Interactions(bus);

// bind bus.Subscribe("PathToAnalyse", interactions.Count_LOC_in_Path); bus.Subscribe("LOCStats", ui.Display_Stats); bus.Subscribe("AnalysisProgress", ui.Display_Progress);

// run Application.Run(ui);

To receive input, functional units would subscribe to certain notifications. The bus then would call the appropriate handlers. Output on the other hand would be published by handing it over to the bus, e.g. bus.Publish(“PathToAnalyse”, @“c:...”);.

Which approach to messaging would you choose? What are the advantages/drawbacks of one or the other?

And how to continue below that level? Because however Count does its work, it´s cerainly more than one concern involved and more than one step to take:

  • Relevant files have to be gathered
  • File contents have to be loaded
  • Lines of code have to be counted
  • Stats have to be compiled
  • Report progress, maybe as each file gets tackled

For that a flow like this comes to my mind:

image

It could be easily implemented, event without wiring-up events and event handlers. Just a sequence of method calls:

class Interactions { public string Count_LOC_in_Path(string path) { var fsa = new FilesystemAdapter; var lc = new LineCounter;

fsa.Find_CS_files(path, filename => { Progress(Path.GetFileName(filename)); var content = fsa.Load_file(filename); lc.Count_LOC_in_file(content); });

return lc.Stats; }

public event Action Progress; }

Except for fsa.Find_CS_files which does not return a single value, but produces a stream of filenames as output via a continuation:

public void Find_CS_files(string path, Action OnFilename) { Directory .GetFiles(path, "*.cs", SearchOption.AllDirectories) .Where(filename =>!Path.GetFileName(filename) .StartsWith("Temporary")) .ToList .ForEach(OnFilename); }

Using a continuation keeps the enumeration in the leaf method of the call tree: Find_CS_files. This way the code follows the Integration Operation Segregation Principle (IOSP). It keeps nodes in a call tree free of logic; logic only resides in leafs. That makes them easier to test and the whole tree easier to understand.

Of course this functionality could also be implemented using a bus to conduct the messages between the cooperating functional units, e.g. the file system adapter and the true domain logic.

image

But the question would be the same as above: What´s the better approach for a messaging scenario? Should a bus or a flow be used?

My personal answer is twofold:

  1. Whether to use a bus or a flow is less important than to use messaging at all. Following the IOSP and the PoMO makes the most difference, I guess. So if Steve is in favour of “pipes and filters” and I like wiring up events or just plain sequences of method calls, should be not point for much dispute.
  2. That said, I´d like to put it this way: understandability over decoupling. Both, understandability and decoupling are important, but since with IOSP and PoMO the decoupling is already high, I think the next issue to tackle should be understandability and not further decoupling. Since flows are easier to understand in terms of “how is input processes to yield output?” I´d say use a flow whenever in doubt. That means on the other hand: use a bus whenever understandability is not hurt.

What does that mean regarding the above example scenario?

I´d connect UI and domain logic using a bus. Their interplay is readily understandable. Or you could argue, UI and domain logic should be as decoupled as possible to make the UI easier to exchange.

For the domain logic, though, I´d prefer a flow. That´s where the action is. That´s the most likely area for changes. So the need for understandability is high.

Also this is where precise coordination is required. Using a bus it´s harder to correlate response processing with requests. Currently the example system is too small to actually feel this. But if it grows, decoupling the file system adapter and the domain logic would lose some of it´s simplicity.

For me a bus thus is more of a tool for distributed scenarios, where correlation overhead does not have such a big impact. But that does not mean I won´t use it in-process. As I said: if understandability is not hurt, use a bus for it´s added decoupling.

This article is part of the GWB Archives. Original Author: Ralf Westphal

New on Geeks with Blogs