[Feature Proposal] About private typed actor messages

Currently there are two documented styles for handling private actor messages:

Extend public base trait with private classes:

sealed trait Command
case class PublicMessage() extends Command
private case class PrivateMessage() extends Command

and make separate base trait, and make public messages extend that trait:

sealed trait Message
sealed trait Command extends Message
private sealed trait PrivateMessage extends Message

Personally, I don’t like both of them because they put public interface and private implementation details together in same place, thus making it less readable and clobbering it. Even if you have 5 public message types and 5 private ones, the definition becomes rather “noisy”. I think that it’s better to keep them separated. So I propose third one, which achieves this goal, but isn’t possible with current Akka since Akka is missing one small thing:

object Actor {
  sealed trait Command
  ...

  def apply(...): Behavior[Command] = ...
}

private object ActorImpl {
  sealed trait Message
  case class CommandMessage(cmd: Command) extends Message
  ...
}

private class ActorImpl[Message] extends AbstractBehavior[Message] ...

This way primary Actor object contains only public messages and factory method, and implementation details can be hidden in separate file.

And here comes the main point: there is no something like .map on behavior.

You can’t create abovementioned pattern since you’ll end up with Behavior[Message] instead of Behavior[Command], and .narrow will not help in this place. Method like

trait Behavior[T] {
  def contramap[U](f: U => T): Behavior[U]
}

would be exteremly useful in such situation.

So the whole point of this topic: what do you think about above pattern and is contramap feature feasible for Github feature request? It seems like rather simple and obvious feature, and propably there were reasons for not including it.

1 Like

Well, I found transformMessages, that simlifies a lot.

So the request becomes rather documentation request, since I think that third pattern is cleaner :)

1 Like

Is the reason for not using Message extends Command that you want to keep sealed but place them in separate files?

private object ActorImpl {
  sealed trait Message extends Command
  ...
}

Would you be able to open a PR with the documentation example that is using transformMessages?

Well, if you write Message extends Command, then all messages are commands, but not all commands are messages. This will simply don’t typecheck on inner Behavior[Message]. You can’t also write the opposite, since then Command will “leak” private type as part of it’s definition.

About PR - probably yes, at some time :)

There is, however, other API weakness.

The weakness comes from a fact that I cannot replace/“become” outer behavior.

E.g. I have something like this:

object ActorInterface {
  sealed trait Command
  // ...
}

private object ActorImpl1 {
  sealed trait Message1
}

private object ActorImpl2 {
  sealed trait Message2
}

If I will use .transformMessages when creating actor, then I am bound forever to specific private message type. I cannot replace inner actor even if it accepts the same public message set.

Strictly speaking, other two methods have exactly same limitation. But this limitation is an abstraction leakage.

By the way, this is interesting question in general, not related to specific code pattern.

Can actors swap their private message type at runtime, while retaining public message type that is presented via Behavior[T]?

Currently the answer is “No.”, and this makes sense. However, this is a point for discussion.

The problem that arises when actor changes private message type is “What to do with messages with older private type?”. That makes sense, because someone may still have actor ref with private message type.

However, from application point of view, these messages are equivalent to “dead letters” anyways. If application logic means changing private message type, it also means that it can’t meaningfully handle old messages. Probably they will be just dropped. So I think that there is a place for API improvement.