Interface Message

All Known Implementing Classes:
DefaultMessage

public interface Message

Overview

Messages are used between Actors and therefore are typically declared as independent top-level classes. Most Messages in Sodacan are just containers for some important capabilities:

  • MessageId - can be thought of as a transaction Id. It can group together any number of steps in a business process. And the messageId is also used as a correlation Id during parallel fan-out fan-in processing.
  • Routing stack - where the message is headed. The top-of-stack refers to the next Actor that the message is routed to.
  • Current route - If the message is currently being processed by an Actor, this primarily contains the Verb (the ask) for what it should do,
  • Routing History - When a route is popped off of the routing stack, it is added to the routing history.
  • Payload - The payload carries any number of primitive or complex data organized as key-value pairs. As a message travels from actor-to-actor, the payload can accumulate data relevant to the transaction.

A Message object is what the Actor programmer normally sees. However, when a Message is in transit, it is called a Jug which consists of the serialized content of the message with the ActorId of the immediate destination of the message in plain-text.

Message Lifecycle

A message has a specific beginning and ending. A message travels from Actor to Actor. A message is created by the configuration object by some Actor. Normally, the message exists until it explicitly "consumed". Messages can also be "held" by an actor although this should only occur in specific cases. While a message is traveling between actors, most of its contents are serialized into a Jug making it hard to analyze. This usually isn't an issue because Actors always see messages in their deserialized form.

Note: All of the following occurs in the Actor's thread. While Sodacan processes a message, it ensures that the message makes it back out the other side of the Actor's processMessage method. Technically, the message itself is immutable but the contents of the message are used to create one or more messages. So, if message A is being processed, then Message A should appear among the messages in the "Stage". Any other Messages in the Stage will have been created by the Actor.

Now, to complicate matters, a Message can be "split". This means that a Message inbound to an actor can be cloned into more than one output messages. The rule above still applies, only there is more than one copy of the message in the Stage. Each of the clones will have the same MessageId. This is done to allow a single "business flow" to operate in parallel for one or more steps in its lifecycle. Split messages can and likely will travel to different Hosts. A split like this normally forward the initial message and forwarding it to the Actor that will collect the results and of course zero or more clones that will end up at the same collecting Actor. Usually, the clones will end their lifecycle in the collecting Actor. Once the collecting Actor receives the expected number of results from each clone, it can forward on the original message to the next step(s).

A message is officially done when it is placed on the stage with an empty Routing Queue, ie there is no more Steps in the process.

Observability

Because a message has a unique id, it can be tracked although this is usually not the case in production.

Messages provide a natural transport for certain trace requirements. A Sodacan message represents a business transaction typically carrying with is much of the data associated with that transaction. It carries with it the Actors that it will visit and the Actors that it has visited.

During its lifetime a message can travel to many Actors.

When a message is created it can start with an ActorId already in its routing stack. That Actor can collect some or all Messages for analysis. For example, the default messageId generator in part uses a UTC Instant. If the final route in a messages life is to an actor that measures the total elapsed time of a message.

Another approach is to Subclass the DefaultMessage class in which case the subclass can do fine-grained tracing to any mechanism desired. In this case, a message can keep track of each step in its journey incling the elapsed time used in each step. Also, Sodacan calls additional method stub in a Message allowing further traceability.

  • Method Details

    • getMessageId

      MessageId getMessageId()
    • getTimestamp

      Instant getTimestamp()
    • getNextActorId

      ActorId getNextActorId()
    • getNextVerb

      Verb getNextVerb()
    • getHistory

      List<Route> getHistory()
      Return a copy of the message routing history
      Returns:
    • keepHistory

      Message keepHistory(boolean keepHistory)
    • isKeepHistory

      boolean isKeepHistory()
    • getRoute

      Route getRoute()
      Get the current route. This is the route that gave rise to this call to processMessage
      Returns:
      The current route
    • getTarget

      ActorId getTarget()
      Get the target actorId of the current route. This is the route that gave rise to this call to processMessage
      Returns:
      The current target actorId
    • getVerb

      Verb getVerb()
      Get the verb of the current route. This is the route that gave rise to this call to processMessage
      Returns:
      The current verb
    • getRoutes

      List<Route> getRoutes()
    • peekRoute

      Route peekRoute()
    • popRoute

      Route popRoute()
    • print

      void print(PrintStream out)
    • copyRoutesFrom

      Message copyRoutesFrom(Message inbound)
    • from

      Message from(Message source)
    • ask

      Message ask(ActorId actorId)
    • ask

      Message ask(String key)
    • to

      Message to(Verb verb)
    • preprocess

      boolean preprocess(Config config, Actor actor)
      This method is called after a message is deserialized and ready to be processed by the target actor. The default implementation does nothing. This method can be overridden in a Message subclass to provide fine-grained tracing or additional message validation.

      This method executes in the Actor's thread.

      Parameters:
      config -
      actor - The Actor that is about to process this message
      Returns:
      true if the message should be processed
    • postprocess

      boolean postprocess(Config config, Actor actor)
      This method is called when a message is sent by an Actor. This happens just before the message is serialized for transport to its current destination. A subclass of a default message can use this method to evaluate the message as well as the Actor that created or forwarded the message. This method can be overridden in a Message subclass to provide fine-grained tracing.

      This method executes in the Actor's thread.

      Parameters:
      config -
      actor - The Actor that just processed this message
      Returns:
      true if the message should be processed
    • notImplemented

      Stage notImplemented(Config config, Actor actor)
      This method is called when a message is sent to an Actor but the Actor has not overriden the processMessage method. The default behavior is to return an empty Stage. An alternate would be to put the inbound message back into the Stage (it will be sent to the next item in the Route stack, if any). A subclass of a default message can use this method to provide alternate behavior or to forwarded the message.

      This method executes in the Actor's thread.

      Parameters:
      config -
      actor - The Actor that received (but didn't process) this message
      Returns:
      The Stage to be processed
    • terminal

      void terminal(Config config, Actor actor)
      This method is called when a message is sent by an Actor and the message as no further routes this it will be destroyed just after this method is called. A subclass of a default message can use this method to evaluate the message as well as the Actor that created or forwarded the message to ensure that its demise is justified.

      This method executes in the Actor's thread.

      Parameters:
      config -
      actor - The Actor that just processed this message
    • getPayload

      Map<String,Object> getPayload()
      Return a copy of the payload
      Returns:
      A new payload object
    • put

      Message put(String key, Object value)
      Put new entry in payload
      Parameters:
      value - The Payload value
      Returns:
      Message builder-style
    • put

      Message put(String key, String value)
      Put new entry in payload convenience for a String
      Parameters:
      key - A string identifying the payload entry
      value - The Payload value
      Returns:
      Message builder-style
    • put

      Message put(String key, Integer value)
    • put

      Message put(String key, ActorId value)
      Put new entry in payload convenience for an ActorId
      Parameters:
      key - A string identifying the payload entry
      value - The ActorId to add
      Returns:
      Message builder-style
    • get

      Object get(String key)
      Find the info with the matching name (case insensitive).
      Parameters:
      key -
      Returns:
    • get

      <T> T get(String key, Class<T> clazz)
    • getString

      String getString(String key)
      Find the Info record the matching name (case insensitive) and return its String Contents
      Parameters:
      key -
      Returns:
    • getActorId

      ActorId getActorId(String key)
      Find the Info record the matching name (case insensitive) and return its String Contents
      Parameters:
      key -
      Returns:
    • getInteger

      int getInteger(String key)
      Find the integer Info matching the name (case insensitive) and return its Integer Contents
      Parameters:
      key -
      Returns:
    • remove

      Message remove(String key)
      Remove entry from payload
      Parameters:
      key - The key to remove
      Returns:
      the message object