Relationships between picos

In the previous post, we described pico channels. When you have the identifier of one of a pico's channels, you can use it to send events and queries to that pico.

When a pair of picos need to work together, each needs an ECI (event channel identifier) to the other. The bundle of that pair of ECIs reifies a relationship shared by the two picos. Each pico has its own copy of the bundle.

Historically, up to and including version 1 of the pico engine, such a relationship has been called a "subscription". Recently, we decided to use the term "relationship" instead, since subscription has connotations of one publisher and many subscribers, and here we're talking about pairwise relationships.

The io.picolabs.subscription ruleset

Saying that "each pico has its own copy of" something implies that it is maintaining it as state information or data. The only way a pico can do that is by using an entity variable in a ruleset (see the earlier post on "Picos and persistent state").

The ruleset used to hold information about a pico's relationships is io.picolabs.subscription which is installed in every pico. This means that pairwise relationships between picos is something fundamental, built in to every pico.

With this ruleset, a pico has events to propose, accept, decline, and use established relationships.

A subscription

The bundle* of information that defines a relationship (currently called a subscription) consists of:

  • An identifier (named Id)
  • This pico's role in the relationship (named Rx_role)
  • The other pico's role (named Tx_role)
  • This pico's Event Channel Identifier (ECI) (named Rx)
  • The other pico's ECI (named Tx)
  • The channel tags (part of each channel's definition, and not part of the bundle)

This information is held by both picos. The identifier is the same in both of them, as are the channel tags. But the roles and ECIs are reversed as makes sense for each pico.

Proposing a relationship

This is done** by a pico raising the wrangler:subscription event, with the following attributes:

  • wellKnown_Tx an ECI for the other pico (often its wellKnown_Rx)
  • Rx_role a string describing this pico's role in the proposed relationship
  • Tx_role a string describing the other party's role
  • name a string naming the relationship
  • channel_type a string giving the type of relationship

The last two items can be chosen by the KRL programmer. They become the tags of the channels created for the relationship in both picos.

A rule in the io.picolabs.subscription ruleset reacts to this event, assigns a new identifier, creates a new channel for the pico, and sends an event to the other pico to have it do its part.

At this point, the first pico has the bundle of information in its internal array of outbound subscription requests, and the other pico has the same bundle of information (with roles and channels reversed) in its internal array of inbound subscription requests.

Accepting (or declining) a proposed relationship

Now the ball is in the other pico's court. It can accept*** by sending the wrangler:pending_subscription_approval event (or decline by sending a wrangler:inbound_rejection event) to the first pico. 

Until the other sends one of these events, the relationship will remain in the proposed state.

Established relationships

Once a proposed subscription has been accepted by the other pico, it is said to be "established" and both picos can use it. The list (an array) of established subscriptions can be obtained in this manner:

  meta {
    use module io.picolabs.subscription alias subs
  }
  ...
    subs:established() // all established bundles for this pico
  ...

Using established relationships

In a system with a large number of picos, each will generally have many relationships. 

For example, let's assume that one pico serves as a "controller" and wants to send an update event to all of the other picos it controls. That could be accomplished by a ruleset like this one:

ruleset control_something {
  meta {
    use module io.picolabs.subscription alias subs
  }
  ...
  rule broadcast_update {
    select when control_something update_required
    foreach subs:established("Rx_role","controller") setting(s)
    event:send({
      "eci": s{"Tx"}, 
      "domain": "controlled", 
      "type": "update_required",
      "attrs": event:attrs
    })
  }
}

When the controlling pico receives the control_something:update_required event, it will obtain a list of the established subscriptions in which it plays the "controller" role, and forward the incoming attributes in a controlled:update_required event to each of the other picos in those relationships.

Notes

"one publisher and many subscribers" yet it looks like the example at the end is very much like that, with one controller and many controlled. But each relationship is represented, pairwise, by a subscription. When a controlled pico contacts the controller, it does so using a channel just for it (from its point of view, the Tx ECI), which will be different for each controlled pico.

* Inside the subscription ruleset, this bundle is called a "bus".

** The complete lifecycle is documented here, so this is just an overview. Where this post or that document differ from the subscription ruleset itself, the ruleset prevails.

*** A way of automatically accepting offered subscriptions is discussed in this document.

No comments:

Post a Comment