The Architect´s Napkin

Software Architecture on the Back of a Napkin
posts - 69 , comments - 229 , trackbacks - 0

My Links

News

Archives

Post Categories

Messaging for More Decoupling

Object orientation true to its inventors original intent uses messaging for communication between objects. That means, data is flowing one-way during each “communication act”.

image

It´s easy to depict – but what actually does it mean? Does it mean, each functional unit (a, b, c) calls the next like this?

int doA() {
  var x = ...;
  doB(x);
}

int doB(int x) {
  var y = ...x...;
  doC(y);
}

Could be, at least technically. Formally this would be messaging (with no data, but just a signal flowing between processing steps).

But I understand messaging differently. To me we need to decouple communicating parties even more. Not only should we get rid of the coupling between request and response, but also between the responsibilities of sender and receiver.

This might be subtle, but think about what doA() does: it processes some data () and then requests a particular service by calling doB(). doA() thus not only knows how to do its own A-stuff, but also what comes next: some B-stuff. The same for doB().

The link between the processing steps is part of the processing steps. That, I think, is wrong. It´s too much coupling. It´s too many responsibilities for a functional unit.

Interestingly this coupling is asymmetric: Each functional unit knows about the purpose of the next one – but is oblivious to who called it. Strange, isn´t it?

That´s different with functions. Another translation of the above flow could be this:

var x = doA();
var y = doB(x);
doC(y);

The functions neither know, who called them, nor what´s going to happen next. They don´t know each other. They are completely independent. I call that “mutual oblivion” (MO). And I think, that´s how messaging flows should be implemented. Their arrows mean, senders don´t know about receivers. And receivers don´t know about senders.

When using functions like shown the implementation naturally follows the Principle of Mutual Oblivion (PoMO). But what if functions are not the right tool for the job? That can be the case if functional units have more than one input or several outputs. Take this flow for example:

image

A function can return only one result. But here the processing is supposed to flow in one of two directions after validation. Of course, the obvious solution would be:

void validate(T data) {
  if (...)
    save(data);
  else
    report_validation_error("...");
}

But that´s violating the PoMO. validate() would be hard wired to the next processing steps.

And that wouldn´t change, even if those steps were injected as continuations:

void validate(T data, Action<T> save, Action<string> report_validation_error) {
  if (...)
    save(data);
  else
    report_validation_error("...");
}

No, to follow the PoMO requires a change of attitude. It´s not about technical detail, but semantics and responsibility. validate() still knows too much. It´s not context independent like a function. (That´s what FP programmers like so much about functions. If used right, they are easily composable. A function does not know about its usage context.)

To truly decouple functional units for messaging, they need to focus on their own responsibilities. That means, processing input and producing output. For validate() that could look like this:

void validate(T data, Action<T> onValid, Action<string> onInvalid) {
  if (...)
    onValid(data);
  else
    onInvalid("...");
}

You might think changing the continuation parameter names is a small thing. And technically it is. But it goes a long way to decouple validate() from any other functional units in whatever flow.

Now the procedure focusses on validation. It knows nothing about happens before and after. onValid makes no assumptions about what the next processing step might be if the data is valid. Instead of calling services events are fired. Here the difference lies in the parameter naming. But when switching to classes the language feature changes.

Here´s a traditional implementation of a validation service class:

class Validator {
  IRepository _repo;
  ILogger _log;

  public Validator(IRepository repo, ILogger _log) {
    _repo = repo;
    _log = log;
  }

  public void Validate(T data) {
    if (...)
      _repo.Save(data);
    else
      _log.Report_error("...");
  }
}

That´s SOLID, isn´t it? But it´s not object oriented in the original sense, I argue. Because it violates the PoMO, which – to me – is at the heart of messaging like one-way communication. Maybe I should extend my definition of messaging to underline this:

Messaging is one-way communication by transporting data (message) from a sender to a receiver – and both are oblivious of each other.

Fortunately it´s easy to change the class so that it conforms to the PoMO:

class Validator {
  public void Validate(T data) {
    if (...)
      OnValid(data);
    else
      OnInvalid("...");
  }

  public event Action<T> OnValid;
  public event Action<string> OnInvalid;
}

Not only is this a reduction in LOC, it´s also more decoupled than the IoC version. Technically functionality still gets injected. But semantically it´s different. And that´s the point [1].

Events defined in that way are a feature of C#. But you can do the same in Ruby or JavaScript and other languages. Think function pointer or observer pattern.

Bottom line: If you want to try out messaging, then be sure to keep the functional units decoupled. Sending messages alone does not do the job. Senders and receivers should not even know about one another.

Endnotes

[1] Interestingly the switch from service injection to events also leads to a technical decoupling. As long as services are injected using interfaces there is a topological dependency between client and service. The client not only is dependent on some functionality (e.g. Save()), but also on where this functionality is located in the environment (e.g. IRepository). That means, should services get shuffled around because someone applies the ISP, dependent services need to change. That to me looks like a violation of the SRP, where each functional unit should only have one reason to change.

When using events, injection works on single functions instead of set of functions. That means the dependent functional unit does not know about its environment´s topology. It does not care on which interfaces these functions are implemented. It´s not affected, if they move around.

Print | posted on Monday, August 19, 2013 9:40 AM | Filed Under [ OOP as if you meant it ]

Feedback

Gravatar

# re: Messaging for More Decoupling

I'm not sure you've made any point other than reducing coupling. The problem with your example, is your final code is not functionally the same, what you have done is improved the design of validator, but now there is no wiring between the repository and log to the validator. IOC does that wiring, so now you would need a builder that would wire the validator to the log and repository which then needs dependency injection. So you'll actually end up with more code, but a more generic validator. But there is a bigger problem lurking underneath what you are saying....

So the first an most important point is don't couple a class with other classes if it doesn't need to be. Cool. Lets now assume you decouple nicely and don't make this mistake.

Now, if you really do need to know about a repository, if you start trying to decouple from it by introducing events you are going to create a mess, all of a sudden your events are going to represent the interface to the repository. We should really group those events into a logical grouping.... lets call it "Repository". Now since all we really need to do is call those events, lets not concern ourselves with how we manage the events or how they get hooked up, lets just give our class that needs a repository a "IRepository", and something else will take care of the wiring. In fact, we can automate and make the wiring configurable by using IoC. Tah Dah!

See the problem with your example is its like a strawman argument, it sets up a simplistic class that doesn't really need a repository or log, shows how to get rid of them, then says its better. However, as soon as you have a class that REALLY needs either Log or repository you are going to create untyped messes that will be fragile if anything changes.

Even with your your simple example I'd possibly have a IValidator with the events on them which then get injected into the concrete validator, and I wouldn't couple it to an event, because hats too concrete, that's a implementation decision of whatever implements IValidator can make.

Basically all you are doing is double dispatching and ditching type information of the interface.
9/12/2013 12:58 AM | Keith Nicholas
Gravatar

# re: Messaging for More Decoupling

Keith, I appreciate the time you´ve put into analysing my example. And thanks for challenging my approach.

Unfortunately it seems I haven´t been able to explain all the implications of messaging done this way. So let me try to correct a misunderstanding or two:

You´re right: A DI container does wiring which now seems to be absent. But sure it isn´t. I just did show it. For that please refer to other pots in this series.

Wiring is need with IoC and messaging. But it´s different, you see. And that´s the point - at least to me.

Wiring for IoC sets up, well, nomen est omen, dependencies: it´s called dependency injection (DI).

Wiring for messaging on the other hand does not serve any dependencies. The Validate() functionality in my messaging examples is never (!) dependent on any service, whereas the SOLID Validator is. The SOLID Validator thus does two jobs at the same time: it validates and it saves or reports an error.

What you mean by "if you start trying to decouple ... you are going to create a mess" I don´t understand. Sorry.

But rest assured, events are not the only means by which functional units can be decoupled. And they are not appropriate for all scenarios.

However, events are better known than delegates and CPS programming. So for didactic purposes I sometimes use events whereas in production code I´d use continuations.

Also I don´t know what you mean by "as soon as you have a class that REALLY needs". Because in my world there will never be a class that really needs a service (for itself). Never. That´s the whole point of the article and of messaging.

Maybe what I mean becomes clearer when comparing software to a company.

What you´re talking about is traditional management: A manager delegates work to some subordinates - but then 1. wants to control their work results, and 2. also wants to add to these results some more "value". That´s because he´s the boss/manager/leader (whatever you want to call him). He´s supposed the expert, isn´t he? That´s makes him responsible for each product output by his department. So he better checks on that for himself.

Now compare that to my view of management: A modern manager is not responsible for each product. She need not be an expert in how to produce it. She does not delegate individual production steps and then check on the intermediate results.

A modern manager´s responsibility is integrating a bunch of specialists into a production flow. Her product is the flow, not the good produced by this flow.

A modern manager only (!) "wires up" production steps, but never adds any value to their output herself. Rather than delegating work (she could do herself, maybe even better), she assigns responsibilities - and then steps back. No interference.

Back to code: You need to read this article in conjunction with this one: http://geekswithblogs.net/theArchitectsNapkin/archive/2013/08/19/nested-messaging---flows-on-different-levels-of-abstraction.aspx

Both together describe the bigger picture of messaging represented by the two principles PoMO and IOSP.
9/12/2013 7:36 AM | Ralf Westphal
Post A Comment
Title:
Name:
Email:
Comment:
Verification:
 

Powered by: