27. Add incoming contexts to an indexed context

A common way to structure functionality is to add an indexed root context to the model. This functions as an entry point for the functionality. It is important to realise that even though each user can reach that root context through the same name, all these contexts are actually different from each other. That is the meaning of 'indexed'.

Now let’s assume that the embedded contexts are shared by several peers. As an example, consider this very simple situation:

domain model://joopringelberg.nl#PsychoTherapy
  use ps for domain model://joopringelberg.nl#PsychoTherapy

  case PsychoTherapy
    indexed ps:PsychoTherapyApp
    aspect sys:RootContext

    user Manager filledBy sys:TheWorld$PerspectivesUsers
      perspective on ClientCases
        all roleverbs
        props (Name) verbs (Consult)

    context ClientCases (relational) filledBy ClientCase

  case ClientCase

    user Client (relational) filledBy sys:TheWorld$PerspectivesUsers

    user Psychiatrist (functional) = extern >> binder ClientCases >> context >> Manager
      perspective on Client
        all roleverbs
        props (FirstName, Report) verbs (SetPropertyValue)

We’ve omitted several details, but the essence is there: the PsychotherapyApp holds a number of ClientCases and each such case has a Client and a Psychiatrist role. Now it is important to realise that both clients and psychiatrists use the same app and hence have the same (type of) entrypoint, to wit the PsychotherapyApp. Obviously, each will have its own instance! We expect the app of a working psychiatrist to have a large number of clientcases, and a client to have just one case - his own. Or at most a few, where he figures in the Client role of each of them.

We’ll furthermore assume that it is the psychiatrist who creates the ClientCases.

Perspectives (the PDR) handles synchronisation, so at first sight it seems all is well. The psychiatrist (in his role of Manager) creates a case, opens it (we’ve omitted how he gets a role in the case) and adds a Client role and fills it with a contact from his list (again, we just assume that contact is there). But even while the client will actually receive the case, he will not see it in his PsychotherapyApp. But wait! We haven’t actually given the Client a perspective on the ClientCases role.

That is easily fixed:

    user Client (relational) filledBy sys:TheWorld$PerspectivesUsers
      perspective on extern >> binder ClientCases

But again the client won’t see his case in his app - even though, this time, he will receive a ClientCases role instance filled with his own case. How come?

The key to understanding this is that the ClientCases role instance received by the client is actually a role in the PsychoTherapyApp of the psychiatrist! And that is not his own instance of the PsychoTherapyApp! The ClientCases role instance is useless to the client. It can never be added to his own app, too (a role has exactly one context).

Instead, we need to create an instance of ClientCases locally. Here is how:

  case ClientCase
    external
      property Name = context >> Client >> FirstName

      state AddIncoming = (not exists filter ps:PsychoTherapyApp >> ClientCases with filledBy origin) and exists (filter context >> Client with filledBy sys:SocialMe >> binding)
        perspective of Client
          perspective on extern >> binder ClientCases
            selfonly
            only (Create, Fill)
        on entry
          do for Client
            bind origin to ClientCases in ps:PsychoTherapyApp

Explanation. When an incoming ClientCase is (re)constructed from information sent by the psychiatrist, it enters state AddIncoming. In this state, the Client has an extra perspective, on ClientCases in the app. We use that perspective to create, on behalf of the Client, a new instance of ClientCases and fill it with the incoming case. Exactly what we need!

The condition of state AddIncoming is quite involved. Step by step:

  • Starting from his own PsychotherapyApp, we look for ClientCases that are filled by the incoming ClientCase (using the keyword origin). If that does not exist, we’re going to add it.

  • However, the state condition contains a second part. It checks whether the Client in the incoming case is actually filled by the receiving peer.

Why do we need that check? Because the incoming ClientCase is reconstructed part by part and we cannot be sure that the Client role is actually filled when we evaluate the ClientCase$External state. In fact, that is likely not to be the case. And then the PDR tries to execute an automatic action (on entry) for a non-existing user role instance and just ignores it. The second part of the condition ensures that we have someone to perform the act!


1. If no authoring role is provided by the API caller, we take it to be the System User.