Channels

Disclaimer: The Maestro project has been renamed. While referred to in this article as ‘Maestro’, moving forward the project is referred to with codename ‘Axum’.

Given a programming model such as Maestro, where components are strictly isolated from each other, we have to ask how components communicate with each other. Clearly, without some form of communication, the components are not merely isolated from each other, they are unaware of each other.

For components to form an application, they need to cooperate and to be aware of each other in some way. In any typical object-oriented model, classes are so aware of each other that they know each others’ names, or can easily discover them through reflection. Even if you get a reference to some abstract base class or interface, you can always ask the runtime system to reveal the true type of what you are interacting with.

Also, most objects are created via explicit constructor calls, rather than through abstract factories. This is true in Maestro, too – while not strictly an object-oriented language, it still has to live within the conceptual framework of .NET and therefore it lives and dies by creating objects.

However, in Maestro there is one kind of object that is not created via constructor calls, and for which we cannot discover (at least not very easily and often not at all) the type through reflection: agent instances. Recall from my earlier post that agents are used to access domain state instead of methods and properties.

Unlike methods, agents are objects with their own thread of control and control logic. Agents are created by asking some abstract factory to create a channel to the agent. The system for establishing such factories is worth a post of its own, so we’ll skip the details here.

Here’s what it looks like to create a channel to a new agent:

Channel1 chan = channelFactory.Connect<Channel1>(addr);

the argument for the ‘Connect’ method is called the “address” of the agent, similar to the address of a web site or service. How we get the address of an agent is irrelevant right now; it belongs in that separate post on factories I mentioned.

Once we have the channel, we use its members to communicate indirectly with the agent.

Channels have three kinds of members:

  • Ports
  • States
  • Functions

First, and most important, are the ports – you can think of them as queues into which data can be placed, only the queues may be distributed across processes or machines. Each port is used either to send data to the agent, or for the agent to send data to the other side.

Ports are therefore directional, and declared as either ‘input’ or ‘output’ ports. Each port is used as a source and a target, depending on which side of the channel you are. To communicate with an agent, you send to the channel’s input ports and receive data from its output ports, while the agent does the opposite.

We call this the perspective on the port, with the agent having the implementing perspective and the other side having the using perspective.

So, we now have all the definitions we need to look at a channel definition:

public channel Channel1
{
input string FirstMessage;

output int Result;
}

This is all we need for the examples from my previous post – a single input port and a single output port. This channel definition is complete and correct and may be used by any agent for which it makes sense to send a string as a first message and receive an integer as a result. In fact, the channel is even more flexible than we think, because it doesn’t define how many messages should be sent via ‘FirstMessage’ before we should expect a response. Thus, it can be used for many different implementations and agents, without much in common.

However, in a world where we have prevented you from knowing whom you’re really communicating with, it’s kind of scary not to know more about the expected behavior of whomever it is that you are dealing with.

To handle this, Maestro gives you the option of adding a protocol definition to the channel. The protocol is written as a state machine. Two states always exist: Start and End. The latter is defined in the runtime’s base channel and cannot be redefined, while the former is overridden by any channel that provides its own protocol – it is by adding state members to the channel that its protocol is defined:

public channel Channel1
{
input string FirstMessage;

output int Result;

Start: { FirstMessage –> State_1; }

    State_1: { Result –> End; }
}

What this protocol tells us is that a single use of ‘FirstMessage’ followed by a single use of the ‘Result’ port leads to the entire conversation between client and agent being over. The state consists of a set of ‘transitions’ each taking an expression of the port names of the channel and pointing at a new state.

What does this provide us, really? Well, even if we don’t ever know which agent implementation we’re dealing with, we can know how it will behave vis-a-vis the messages that it expects and produces, and the runtime can enforce the protocol at each end of the channel, thus moving the detection of protocol errors closer to their sources.

There’s more to channels than this, but I think this is plenty for right now. Channels, domains and agents form the core of the Maestro isolation model and by grasping those three concepts, you have a majority of the higher-level ideas that make up the Maestro programming model.

As Artur pointed out in his post earlier, there are other aspects of the language than isolation, mostly having to do with overcoming the challenges of asynchronous programming. Maybe in the next couple of days or weeks, I’ll find something to say about those, too.

Niklas Gustafsson
Mastro Team