Stiffstream / sobjectizer

An implementation of Actor, Publish-Subscribe, and CSP models in one rather small C++ framework. With performance, quality, and stability proved by years in the production.
https://stiffstream.com/en/products/sobjectizer.html
Other
481 stars 47 forks source link

[Design] Your opinion on expressing agent intent #58

Closed ilpropheta closed 1 year ago

ilpropheta commented 1 year ago

Hi, I have a design question I would like to know your opinion about.

Agents, message boxes and chains can handle any message type. From this point of view, they do not expose any kind of public interface. Indeed, message subscriptions are implementation details of agents. This is kind of powerful for fostering OCP.

On the other hand, an agent hardly expresses its intent publicly. We have to look into its implementation details to understand which messages it handles (e.g. so_define_agent). This differs from some other paradigms. For example, in classical OOP classes have a list of methods that corresponds to the "messages" they can handle. This clearly exposes to other well-known issues.

I am wondering if your experience with designing, organizing and documenting agents has some insights and recommendations to mitigate this possible issue or, maybe, if this is a non-issue for you.

eao197 commented 1 year ago

Personally I have no problem with the current situation. But it may be because I usually have access to source code of agents in a project. Perhaps if I work with a big project that is split into separate closed-source subprojects developed by separate teams and the only way to get information about the subprojects' interfaces is through the documentation and header files with message declarations... But there haven't been such cases in SObjectizer history yet :)

However, there were a couple of discussions in the past that were related to this topic.


If I remember correctly the first one was about understanding an agent's logic: which messages it receives, in which states, which state transitions the agent performs... One of my colleagues in the mid 2000s even proposed a notation for describing messages/states. But it wasn't used (I don't remember why, but can suppose that it's because such description wasn't in sync with the source code).

So the first part of the problem is how to quickly observe what messages an agent receives and how it handles them.

There are no tools or recipes that allow it. The only way now is to look at the agent's source code. But I hope that the use of so_define_agent (no subscriptions outside of so_define_agent if possible), states as members of agent class (no dynamically created instances of so_5::state_t) and event handlers that accepts mhood_t<T> makes that task a bit easier.

I didn't think much in this direction and it seems that there is no simple way to simplify that task without creation of some external tool (somewhat similar to Doxygen) that will parse C++ source code and extracts SObjectizer-related information like states, subscriptions for every state, state transitions and so on. I know that the LLVM project makes creation of such a tool possible, but I have never dug into that topic.


Another discussion was about a strict description of the message types that can be sent to a given mbox. It could be seen as a contract for an mbox that is expressed in C++ source code in some way. For example, we can declare that mbox A can only receive msg_1 and msg_2, and mbox B can only receive msg_2 and msg_3. An attempt to send a message of type msg_3 to mbox A (or an attempt to subscribe to a message of type msg_3 from mbox A) should fail (preferably at compile time).

It's an interesting topic and I even did a simple sketch of how it can looks like (in Russian): https://eao197.blogspot.com/2017/03/prog.html

But this topic was not developed further.

One reason was the absence of the real demand for such a functionality. It's interesting to speak about a fancy feature, but it's hard to find someone who really needs it and can help in designing and testing it in the field :(

Another reason is the loss of flexibility provided by untyped mboxes.

For example, we can have an mbox A that is used for distributing messages M1 and M2. Everything is going fine until we've encountered some bug and want to find it. Sometimes we have to send some additional message M3 to that we write a special handler that helps us understand the behavior. Once we find and fix the bug, we remove M3 and its handler.

Untyped mboxes allow such an approach easily. But if the mbox A is a typed one then we have to change its contract, rebuild a part of projects, then return the original contract for A and rebuild another time. And the changing of the mbox A can be difficult if that mbox is described in a library we don't own (for example that library is written by a separate team as a part of work on a separate subproject).

Such a scenario can be considered exotic, but I have used it sometimes.

However, if the project involves for a long time then I see cases when an mbox is used for sending new messages (those messages were added to the project much later). I even added a new type of mbox (called unique_subscribers) to the so5extra project as a consequence of such cases. Sometimes we start by using just one MPSC mbox for sending messages to agent X, and after some time we need another agent Y to do something new, but it's convenient for us to keep using the same mbox. Untyped mboxes allow us to easily change one type of mbox to another without rewriting big parts of the project.

Nevertheless, I think this direction is interesting and it may be useful for some situations. And I would be curious to make another attempt to think about typed mboxes. But this requires a real need for such functionality. That's how in the case of the need to subscribe mbox to mbox: you voiced an interest in such a feature, then I encountered a similar need myself. This convinced me that this feature is really needed and will now appear in SO-5.8.0. So if there will be real interest in typed mboxes, I will add such a request into the wish-list.

ilpropheta commented 1 year ago

Thanks @eao197 for your insights. I have no issues in my scenario since I have access to the source code and the agents are pretty small (trying to keep things as simple as possible). I was just wondering what was the state of the art on this in SObjectizer. Your reply is very helpful, thanks a lot.

Making subscriptions among mboxes is definitely something worth for my use cases and typed boxes might be useful I guess, or you can just redirect every message from one mbox to its subscribers. I think both the use cases are worth considering.

In my scenario, I have this generic agent that enables redirecting specific messages from a source to destination. The syntax is more or less:

some_context.add_parrot<MessageA, MessageB, MessageC>(src, dst);

All such "parrots" are added to a single active group (aka: they all share the same thread), however each has its own cooperation in order to be deregistered individually