Pico to Pico Subscriptions Lesson
Learning Objectives
After completing this lesson, you will be able to do the following:
Explain what a pico subscription is
Understand pico-to-pico subscriptions
Use the developer tools to manage pico subscriptions
Programmatically link picos using a subscription and have them interact.
Motivation
In the previous lesson, you learned about creating pico parent-child relationships. However, in distributed systems, not everything is a parent-child relationship. In fact, there are many systems which are based off of peer-to-peer relationships.
Each node in a peer-to-peer system needs some way to communicate with a different node independent of a parent child hierarchy. For example, a flower store may wish to create a relationship with a delivery driver, so that the store can broadcast a new delivery request, and the driver can bid on delivering that request. This is where more general relationships, which we call subscriptions, come in.
Subscriptions are a two-way link from one pico to another independent of the parent-child hierarchy. This lesson will show you how to create these subscriptions among picos so that they may communicate directly with each other rather than through some other medium.
Prerequisites
You should have already completed the following lessons:
Pico Engine Quickstart lesson. (Be able to register and install rulesets)
Events and Queries Lesson. (Be able to create channels and understand how events are raised on channels)
Pico State Lesson. (Be able to store and retrieve state using entity variables, both simple and complex)
Pico-Based Systems Lesson. (Be able to manage the pico lifecycle using UI and programmatically)
Read Managing Subscriptions. (Learn about subscriptions and how they work)
Contents
Video Tutorial
Subscriptions
A subscription has a name and two channels, one from each pico participating in the subscription to the other. Each side in the subscription can be given a role to further identify the purpose of the subscription relationship.
In this diagram, from the perspective of pico A, the top channel is the one on which it receives incoming events and queries, so A knows this channel as Rx
, and the bottom channel is the one on which pico A transmits events and queries to pico B, so A knows this channel as Tx
. Conversely, pico B knows the top channel as Tx
and the bottom channel as Rx
. Each pico has its own perspective on the subscription.
Subscriptions may also include a role for each pico. From the perspective of pico A, its role in the subscription is known as Rx_role
while pico A knows the role of pico B as Tx_role
. On the other hand, from pico B's perspective, the role of pico A is Tx_role
and its own role is Rx_role
. Putting yourself in a pico's place, you can think of Rx_role
as my role and Tx_role
as the other's role.
Managing Subscriptions Manually with the Testing tab
The experimental setup
In this section, we'll continue the registration example. Here the owner pico has two child picos, one belonging to Jane, a student, and the other being the registration system.
What we need is a relationship between Jane's pico and each section in which she is registered.
The subscriptions which we will create to enable this pico to pico communication are shown here as dashed magenta lines. Here there is a relationship between Jane's pico and the pico for American Heritage 100 section one.
Prepare for this section by (re-)creating the registration system that you built in the previous lesson.
Install this ruleset into your section collection pico: https://raw.githubusercontent.com/Picolab/pico_lessons/master/subscriptions/app_section_collection.krl,
Create a pico for a student
Be sure the
io.picolabs.subscription
ruleset is installed in the student pico and in the section picos you have created
Initiating subscriptions by introduction
To establish a subscription relationship, wrangler will set up a new channel in each of the two picos. To bootstrap this process, we need to use an existing channel in each of the picos.
Finding a well-known channel
Start by determining the ECI of the "well-known" subscription channel of the section pico. Visit the section pico, select the ECI which is tagged "#wellknown_rx #tx_rx" and for the io.picolabs.subscription
ruleset, click on the "wellKnown_Rx" button. In the Results section, you will see the channel. Double-click on the "id" value and copy it. We'll use it in the actual introduction step.
Setting up an invitation
Next, select your student pico, and go to its Testing tab, using its well-known ECI. Find the form for the wrangler:subscription
event and provide some values:
Paste the ECI of the section pico into the box labelled "wellKnown_Tx"
Provide this pico's role: in the box labelled "Rx_role" enter "student"
Provide the other pico's role: in the box labelled "Tx_role" enter "class"
Provide a name for the subscription. For example, "Jane–A HTG 100-1"
For the type, enter "subscription"
Since both picos are running in the same pico-engine, you can leave the box labelled "Tx_host" blank
Also leave the password blank (it is used with auto configuration, discussed elsewhere)
When you are ready, you screen should look like this, and you can click the "wrangler:subscription" button:
Creation of an inbound channel
Now, let's examine the situation. First, go to the student pico's Channels tab. Notice that there is a new channel created for this subscription. This is this pico's Rx
channel (and as we'll see later also the other pico's Tx
channel):
Outbound and Inbound subscriptions
Next, visit the Testing tab, and select the ECI of the new subscription channel. Click on the "outbound" button:
The subscription is considered "outbound" because it has been proposed to the other pico (identified by its well-known ECI), but not yet accepted by it. To manually accept the subscription, visit the other pico, the section pico.
Now if the subscription is "outbound" in this pico, it must be "inbound" in the section pico, right? To verify, visit the Testing tab of the section pico, and using its new Rx channel, click on the "inbound" button. Compare this view of the subscription with the outbound view in the student pico.
Note that the "Id"
is the only field which has the same value on both ends of the subscription. This is because it is the ID of the subscription itself. There is only one subscription, but it has two ends. The "Rx_role"
and "Tx_role"
are swapped, as are the two ECIs, "Tx"
and "Rx"
. You will note that the initiating pico does not yet have access to the other pico's ECI. This is because the subscription hasn't yet been accepted.
Accepting an inbound subscription
Double click on the "Id"
value, scroll up until you see the form for the wrangler:pending_subscription_approval
event, past in the ID value and click the button.
Then scroll back to the top and click on the "established" button to see that the subscription is now established.
Verifying an established subscription
Finally, visit the student pico's Testing tab, and verify that it too sees the subscription as established.
Notice that the student pico has two established subscriptions, both with the same role. Double check that the "Id"
value is the same in this pico's (second) subscription and the section pico's subscription. The other values are swapped, reflecting the opposite perspective each end of the subscription has. You can also verify that the second "Rx"
value is the same as the ECI selected in the Testing tab.
Managing Subscriptions Programmatically
In the previous section, you learned how to create a subscription manually, using the Testing tab. Here we will learn how to write KRL code to create an inbound subscription and accept it.
It is possible to initiate a subscription with a rule running in a pico which already has in its possession the ECI's of the two picos which need to be related by the subscription.
The well-known channel
Every pico has the io.picolabs.subscription
ruleset installed. This ruleset, on installation, creates a well-known channel with a limited policy. This is by design so that the ECI of that channel can be widely shared, as it can only be used to initiate subscriptions.
In this example, we will write code in a ruleset for the student picos which will create a subscription to the Section Collection pico. Then we will use that to create a subscription between the student pico and the section picos in which they wish to enroll.
Our first problem will be to find the well-known ECI of the section pico we want. When we did this manually, we just went to the section pico and found its well-known ECI in the developer UI. But to use this well-known (so named) ECI programmatically, we have to be able to find it! It needs to be published somewhere, where we can get at it.
One way to do this is to have a new pico inform its parent pico of its well-known ECI, and then other picos can get it from there.
Recording well-known channels IDs for section picos
Here is a modification to a rule that is already in the app_section
ruleset to do this:
rule pico_ruleset_added {
select when wrangler ruleset_installed
where event:attr("rids") >< meta:rid
pre {
section_id = event:attr("section_id")
parent_eci = wrangler:parent_eci()
wellKnown_eci = subs:wellKnown_Rx(){"id"}
}
event:send({"eci":parent_eci,
"domain": "section", "type": "identify",
"attrs": {
"section_id": section_id,
"wellKnown_eci": wellKnown_eci
}
})
always {
ent:section_id := section_id
}
}
The new lines are: 6, where we get the family channel ECI for our parent (the Section Collection pico); 7, where we get the well-known ECI from the subscription ruleset (be sure to include a use module io.picolabs.subscription alias subs
line in the meta
block); 10-16 which send a section:identify
event to the parent pico.
And, then this new rule in the app_section_collection
ruleset to react to that event:
rule accept_wellKnown {
select when section identify
section_id re#(.+)#
wellKnown_eci re#(.+)#
setting(section_id,wellKnown_eci)
fired {
ent:sections{[section_id,"wellKnown_eci"]} := wellKnown_eci
}
}
So now, the sections
function of that ruleset will show us this kind of structure:
{
"C S 462-1": {
"eci": "ckit4ayd20051qb2r8icddvwh",
"wellKnown_eci": "ckit4aydf0054qb2radgwbbj2"
},
"C S 462-2": {
"eci": "ckit4b5it005dqb2r3534e5l3",
"wellKnown_eci": "ckit4b5j6005gqb2rawjb3ucs"
},
"A HTG 100-1": {
"eci": "ckit4bbt6005pqb2rfkid1bdc",
"wellKnown_eci": "ckit4bbti005sqb2ra8spfeqk"
}
}
And the well-known ECIs for the sections are now better known. And when we need them, we can ask the Section Collection pico for them by name (aka the section id).
Recording the well-known channel ID of the Section Collection pico
Similarly, we can have the Section Collection pico report its well-known ECI to the Registration pico.
The code is left as an exercise for the reader. It's a bit simpler, because there's only one such pico known to the Registration pico.
Creating student picos
Now, we follow the journey of well-known ECIs another step.
We have the Registration pico create a student pico when a student "arrives" to register. Here's the rule (for the app_registration
ruleset):
Here, the rule selects on the event (lines 2-3), provides a default color (line 5), and sends an event to its parent pico, asking for the creation of a new student pico (lines 7-14).
The well-known ECI of the Section Collection pico is shared with the new student pico by virtue of line 12. You may need to change this if you stored the ECI in a different entity variable.
Subscription between student and Section Collection
With a well-known ECI in hand, we can write code to request/propose a subscription from the new student pico to the Section Collection pico. We do this by reacting to the wrangler:ruleset_installed
event in the app_student
ruleset:
We create a channel for the newly created student pico in line 5 (guarded by line 4 so we don't do it twice), and store away the incoming well-known channel for future use in line 9. Then, we raise
an event so that we will create a new subscription with the Section Collection pico. We'll see later how we will use that subscription once it is established.
It is a separate rule so that we can raise the event if and when we need a new subscription:
Here the student pico sends an event to the Section Collection pico, using its ECI (line 3). So it is as if were were in the Testing tab of that pico entering the rest of the fields for the wrangler:subscription
event. That is, the invitation is being done from the perspective of the Section Collection pico, even though it is being sent by the student pico. So, the wellKnown_Tx
is the student pico's well-known ECI (line 6), and the role of the Section Collection pico is "registration" while the other role is "student" (line 7). Once we have sent the event, the student pico will have a new channel
and also a new inbound subscription:
Programmatically accepting an inbound subscription
You'll remember that the next manual step was to copy the "Id"
value and paste it into the form for the wrangler:pending_subscription_approval
event. We can do that with a rule that selects on the wrangler:inbound_pending_subscription_added
event:
The event is raised to us when the inbound subscription arrives. We check it out to make sure it is what we expect (lines 3-7) and if so, we raise the wrangler:pending_subscription_approval
event (lines 9-10) and the subscription become established at both ends, and we save away the ECI for use with the Sections Collection pico (line 11). In case it is not an inbound subscription we are expecting, we raise the wrangler:inbound_rejection
event to refuse it (lines 13-14).
Writing rules that use subscriptions
Now that we have a relationship with the Section Collection pico, we (a student pico) can use it to add a class.
Student pico requests to add a class
Here is a rule for the app_student
ruleset:
We first check to ensure that we don't already have an enrollment for that section (line 7). Then we send (in lines 8-15) a section:add_request
event to the Section Collection pico (line 8, using the saved ent:subscriptionTx
ECI) and passing our own well-known ECI (line 11) along with the section ID and our name.
Notice that we use the same event, section:add_request
for student picos and for the Section Collection pico. We are considering a single application, registration, but the code is distributed across several rulesets for the various kinds of entities.
Section Collection pico passes it on to the section pico
Here is a rule for the app_section_collection
ruleset, which is installed in the Section Collection pico. It will react the event that student picos send to it:
This rule binds short names to two incoming attributes in its prelude (line 3-6). Then it sends a wrangler:subscription
event to the appropriate section pico (lines 7-15), using the saved well-known ECI for that section (line 7), and passing along the student pico's well-known ECI (line 10), appropriate roles for the subscription (line 11), a name
and channel_type
(line 12), and passes through the section_id
(line 13).
Section pico proposes a subscription
The section pico will react to this event by proposing a subscription to the student pico. We don't need to add any code to the app_section
ruleset, because this event is handled by the io.picolabs.subscription
ruleset.
Student pico accepts the subscription adding the class
This rule for the app_student
ruleset will see the inbound subscription and automatically accept it:
Lines 3-4 declare that the rule is to be selected only when there are attributes Rx_role
and Tx_role
which have the values "student"
and "section"
respectively. The local name section_id
is bound (line 6) to the attribute value of the same name, and if there is one (line 8), then we record (in line 10) both the "Id"
of the subscription and the section ID in an entity variable named ent:classes
, a map whose keys are subscription identifiers and whose values are section IDs. Then, lines 11-12 accept the inbound subscription, completing the relationship between this student pico and that section pico.
Special notice to line 13, which ends handling of the wrangler:inbound_pending_subscription_added
event. This is very important. The auto_accept_add
rule must be placed in the ruleset before the auto_accept
ruleset (see its definition earlier (the section entitled "Programmatically accepting an inbound subscription")). Both of these rulesets will select on that event, and if this one fires, the other one must not be evaluated. That is the purpose of the last
keyword, meaning that this is the last rule which is to be evaluated for the event.
Maintaining a class list
The entity variable ent:classes
is a map whose values are the classes we have added. So this function will provide our current class list (in the app_student
ruleset):
Be sure to share the function name in the meta
block of the ruleset.
Deleting subscriptions
To delete a subscription, either of the picos which participate in the subscription can raise
the event wrangler/subscription_cancellation
including either the attribute named "Tx"
or the subscription ID. For example, when a student wants to drop a class, it might use this rule (in the app_student
ruleset):
When a student pico receives a section:drop_request
event, this rule will be selected (as declared in line 2). Line 4 binds a local name to the passed in attribute, section_id
. Lines 5-8 compute the subscription identifier that corresponds to that section: operating on the ent:classes
entity variable (line 5), we first filter, retaining those entries in the map whose value equals the passed in section ID (line 6), giving us a smaller map containing only those entries from which we get an array of the keys of that smaller map (line 7), from which we retain only the first entry (line 8). Assuming we find such a value, it will be the identifier of the subscription between our pico and the corresponding section pico. We ask for the subscription to be deleted (lines 12-13), severing the relationship between the student and the section. Finally, we delete the entry from our ent:my_classes
map.
The raised event triggers a rule in the io.picolabs.subscription
ruleset in this pico. This rule takes care of deleting both sides of the subscription.
Managing Subscriptions in the UI
We will create a new pico named "Delta" to illustrate this section. Notice that it begins with the normal complement of channels, including a well-known channel for subscriptions, "ckja93foz0016uq2r17dt782z"
:
It also shows a "Subscriptions" tab, which initially looks like this, with its well-known channel prominently displayed:
Now, by double-clicking on the ECI of the well-known channel, the developer can select it and then copy it. Now, it takes two picos to make a subscription, so let's visit another pico, say Beta, and its Subscriptions tab, which shows that it already has one established subscription:
We create a new subscription by filling in the fields of the New Subscription form, with the copy/pasted ECI of Delta's well-known channel, "greek" as both Tx_role
and Rx_role
, with the name
"Delta-Beta" and a channel_type
of "subscription". When you click the "Add" button, you will have an "Outbound" subscription along with the "Established" subscription.
Meanwhile, back in the Delta pico, we see the "Inbound" subscription:
Clicking on the checkbox opens it up and we see the details of the proffered subscription, along with an "Accept" button:
Notice that Delta has a new channel, with tags "delta-beta, subscription"
and the ECI "ckjc0hor000ko122r8ghp56ty"
(which corresponds to the "Rx
" field of the subscription):
Clicking on the "Accept" button establishes the subscription:
Closing the pico, we see the two subscriptions, as magenta lines, in the topological view of the developer UI:
Copyright Picolabs | Licensed under Creative Commons.