Skip to content

A Case: the State of a Service in One Context

A worked case of a problem the Information System meets often: one Resource instance is shared by many Contexts, and its situation differs in one of them. It shows how the instances look with three ways of modelling the difference, all supported by the API, and why the Information System chose the third for the state of the services. The general patterns are in the API Operational Cookbook.

The Contexts of the case are invented: a Root /example, a VO /example/OceanVO, and in it the VREs /example/OceanVO/PlanktonVRE and /example/OceanVO/CoralVRE.

The Situation

gcat, the catalogue service, runs on one node and works in the VO /example/OceanVO and in its VREs. SmartGears publishes it as one EService instance, present in every one of those Contexts, with its access points, its events and one StateFacet (min: 1, max: 1).

In the VRE /example/OceanVO/PlanktonVRE the catalogue backend is not there: gcat prepares itself in each Context and, in that VRE, the preparation fails with the reason CKAN not found or not configured in /example/OceanVO/PlanktonVRE. Everywhere else it works. The service is up, its health is up; only that VRE cannot be served. When the backend arrives, the next preparation succeeds and the VRE is fine again.

The Information System must say, for that VRE only, that the service does not work there and why, and stop saying it when the VRE is fine again.

In the examples the EService instance is shown with only the Facet instances that matter. Its ids are mnemonic names in place of UUIDs, to help the reader follow each instance from one example to the next (e1 the EService, s-shared the shared StateFacet, s-plankton the StateFacet of PlanktonVRE, ev-plankton its EventFacet): in the registry every id is a UUID. Each instance also shows its contexts, the Contexts it belongs to, as a read with contextsPolicy=all and membershipScope=global returns them: the UUID of each Context as key and its path as value (the UUIDs are invented, the same in every example: 54c04f68… is the VO, e5db6636… PlanktonVRE, 6271f280… CoralVRE). They show in which Context each StateFacet is, and so which one each Context sees. Every way is shown as a hierarchical read from the VO, the union of what the VO and its two VREs hold, so that every instance appears with the Contexts it is in; the tables say what each Context sees alone.

When Everything Works

The starting point of every way below. The service is active in every Context, and every Context sees the same instances: one StateFacet, shared, with the general state, and the EventFacet the service records for each transition of its lifecycle (here: when it became active; such an event names no Context).

As the VO, either VRE, or a hierarchical read from the VO sees it:

{
  "type": "EService",
  "id": "e1",
  "contexts": {
    "54c04f68-f31b-460a-a43c-8e4fae3deb72": "/example/OceanVO",
    "e5db6636-d10a-4e07-bf4a-2c457d3c9c1b": "/example/OceanVO/PlanktonVRE",
    "6271f280-303f-4692-ae15-fe3a5e90bae1": "/example/OceanVO/CoralVRE"
  },
  "consistsOf": [
    {
      "type": "ConsistsOf",
      "target": {
        "type": "StateFacet",
        "id": "s-shared",
        "contexts": {
          "54c04f68-f31b-460a-a43c-8e4fae3deb72": "/example/OceanVO",
          "e5db6636-d10a-4e07-bf4a-2c457d3c9c1b": "/example/OceanVO/PlanktonVRE",
          "6271f280-303f-4692-ae15-fe3a5e90bae1": "/example/OceanVO/CoralVRE"
        },
        "value": "active"
      }
    },
    {
      "type": "ConsistsOf",
      "target": {
        "type": "EventFacet",
        "id": "ev-active",
        "contexts": {
          "54c04f68-f31b-460a-a43c-8e4fae3deb72": "/example/OceanVO",
          "e5db6636-d10a-4e07-bf4a-2c457d3c9c1b": "/example/OceanVO/PlanktonVRE",
          "6271f280-303f-4692-ae15-fe3a5e90bae1": "/example/OceanVO/CoralVRE"
        },
        "event": {
          "what": "active",
          "when": "2026-10-20 09:00:00.000 +0200"
        }
      }
    }
  ]
}
Read StateFacet instances EventFacet instances naming a Context
VO, PlanktonVRE, CoralVRE s-shared (active) none
VO, hierarchical=true s-shared (active) none

First Way: a StateFacet of That Context, Brought Back with sync

PlanktonVRE replaces the shared StateFacet with one of its own, inactive, and records why in an EventFacet of its own.

  • Going inactive: a PUT of the EService instance with the token of PlanktonVRE, where the shared StateFacet is replaced by a new one and an EventFacet is added.
  • Going back to active: a PUT with the token of the VO, with the EService instance as the VO sees it, and sync={planktonvre-uuid}: PlanktonVRE is aligned to the VO, the shared StateFacet joins it, and its own StateFacet and EventFacet leave it and are deleted.

A hierarchical read from the VO, the union of what the VO and its two VREs hold, while PlanktonVRE is inactive: the shared StateFacet is now in the VO and CoralVRE only, and PlanktonVRE has its own.

{
  "type": "EService",
  "id": "e1",
  "contexts": {
    "54c04f68-f31b-460a-a43c-8e4fae3deb72": "/example/OceanVO",
    "e5db6636-d10a-4e07-bf4a-2c457d3c9c1b": "/example/OceanVO/PlanktonVRE",
    "6271f280-303f-4692-ae15-fe3a5e90bae1": "/example/OceanVO/CoralVRE"
  },
  "consistsOf": [
    {
      "type": "ConsistsOf",
      "target": {
        "type": "StateFacet",
        "id": "s-shared",
        "contexts": {
          "54c04f68-f31b-460a-a43c-8e4fae3deb72": "/example/OceanVO",
          "6271f280-303f-4692-ae15-fe3a5e90bae1": "/example/OceanVO/CoralVRE"
        },
        "value": "active"
      }
    },
    {
      "type": "ConsistsOf",
      "target": {
        "type": "StateFacet",
        "id": "s-plankton",
        "contexts": {
          "e5db6636-d10a-4e07-bf4a-2c457d3c9c1b": "/example/OceanVO/PlanktonVRE"
        },
        "value": "inactive"
      }
    },
    {
      "type": "ConsistsOf",
      "target": {
        "type": "EventFacet",
        "id": "ev-active",
        "contexts": {
          "54c04f68-f31b-460a-a43c-8e4fae3deb72": "/example/OceanVO",
          "e5db6636-d10a-4e07-bf4a-2c457d3c9c1b": "/example/OceanVO/PlanktonVRE",
          "6271f280-303f-4692-ae15-fe3a5e90bae1": "/example/OceanVO/CoralVRE"
        },
        "event": {
          "what": "active",
          "when": "2026-10-20 09:00:00.000 +0200"
        }
      }
    },
    {
      "type": "ConsistsOf",
      "target": {
        "type": "EventFacet",
        "id": "ev-plankton",
        "contexts": {
          "e5db6636-d10a-4e07-bf4a-2c457d3c9c1b": "/example/OceanVO/PlanktonVRE"
        },
        "event": {
          "what": "inactive",
          "when": "2026-10-23 10:20:00.000 +0200",
          "where": "/example/OceanVO/PlanktonVRE",
          "why": "CKAN not found or not configured in /example/OceanVO/PlanktonVRE"
        }
      }
    }
  ]
}
Read StateFacet instances EventFacet instances naming a Context
VO, CoralVRE s-shared (active) none
PlanktonVRE s-plankton (inactive) ev-plankton
VO, hierarchical=true s-shared (active) and s-plankton (inactive) ev-plankton
  • What a discovery sees: a search by state in PlanktonVRE finds the service inactive, and does not find it among the active ones.
  • The cost: every change of the general state (the service goes down) must also reach each VRE that has a StateFacet of its own; going back to active needs the token of the VO; the synchronization removes every Facet instance the VRE has alone, so the history of the situation is lost; a hierarchical read shows two StateFacet instances.
  • Status: implemented and certified by the tests of the service (the synchronization of one Context, the deletion of the Facet instances that leave their last Context); the Information System used it first.

Second Way: One StateFacet per Context, Never Shared

Every Context has its own StateFacet: the VO and each VRE hold a different one, even when they all say active. A change of state in a Context is a PUT with the token of that Context, which updates its StateFacet; the situation is recorded with an EventFacet of that Context. No synchronization, and no shared instance to rejoin.

A hierarchical read from the VO, the union of what the VO and its two VREs hold, while PlanktonVRE is inactive: each Context has a StateFacet of its own.

{
  "type": "EService",
  "id": "e1",
  "contexts": {
    "54c04f68-f31b-460a-a43c-8e4fae3deb72": "/example/OceanVO",
    "e5db6636-d10a-4e07-bf4a-2c457d3c9c1b": "/example/OceanVO/PlanktonVRE",
    "6271f280-303f-4692-ae15-fe3a5e90bae1": "/example/OceanVO/CoralVRE"
  },
  "consistsOf": [
    {
      "type": "ConsistsOf",
      "target": {
        "type": "StateFacet",
        "id": "s-vo",
        "contexts": {
          "54c04f68-f31b-460a-a43c-8e4fae3deb72": "/example/OceanVO"
        },
        "value": "active"
      }
    },
    {
      "type": "ConsistsOf",
      "target": {
        "type": "StateFacet",
        "id": "s-plankton",
        "contexts": {
          "e5db6636-d10a-4e07-bf4a-2c457d3c9c1b": "/example/OceanVO/PlanktonVRE"
        },
        "value": "inactive"
      }
    },
    {
      "type": "ConsistsOf",
      "target": {
        "type": "StateFacet",
        "id": "s-coral",
        "contexts": {
          "6271f280-303f-4692-ae15-fe3a5e90bae1": "/example/OceanVO/CoralVRE"
        },
        "value": "active"
      }
    },
    {
      "type": "ConsistsOf",
      "target": {
        "type": "EventFacet",
        "id": "ev-active",
        "contexts": {
          "54c04f68-f31b-460a-a43c-8e4fae3deb72": "/example/OceanVO",
          "e5db6636-d10a-4e07-bf4a-2c457d3c9c1b": "/example/OceanVO/PlanktonVRE",
          "6271f280-303f-4692-ae15-fe3a5e90bae1": "/example/OceanVO/CoralVRE"
        },
        "event": {
          "what": "active",
          "when": "2026-10-20 09:00:00.000 +0200"
        }
      }
    },
    {
      "type": "ConsistsOf",
      "target": {
        "type": "EventFacet",
        "id": "ev-plankton",
        "contexts": {
          "e5db6636-d10a-4e07-bf4a-2c457d3c9c1b": "/example/OceanVO/PlanktonVRE"
        },
        "event": {
          "what": "inactive",
          "when": "2026-10-23 10:20:00.000 +0200",
          "where": "/example/OceanVO/PlanktonVRE",
          "why": "CKAN not found or not configured in /example/OceanVO/PlanktonVRE"
        }
      }
    }
  ]
}
Read StateFacet instances
VO s-vo
VO, hierarchical=true s-vo, s-plankton, s-coral: one per Context; with 130 VREs, 131
  • What a discovery sees: a search by state in PlanktonVRE finds the service inactive, as in the first way.
  • The cost: a change of the general state is one write per Context; a hierarchical read of one service returns one StateFacet per Context; after an Add to Context the inherited StateFacet must be replaced by a new one.
  • Status: considered and rejected: the number of StateFacet instances a hierarchical read of one service returns grows with the number of Contexts.

Third Way, the Chosen One: the General State and an Event of That Context

The EService instance keeps one StateFacet, shared by every Context: its general state, the lifecycle of the service (started, active, down, failed). The situation of PlanktonVRE is an EventFacet present in PlanktonVRE only.

  • Going inactive in PlanktonVRE: a PUT of the EService instance with the token of PlanktonVRE, which adds the EventFacet. The StateFacet is not touched.
  • Fine again: another PUT with the token of PlanktonVRE, without the EventFacet: it leaves the VRE and, being in no other Context, is deleted. The instances are back to When Everything Works.

A hierarchical read from the VO, the union of what the VO and its two VREs hold, while the backend is missing in PlanktonVRE: the shared StateFacet stays in all three Contexts, and the EventFacet is in PlanktonVRE only.

{
  "type": "EService",
  "id": "e1",
  "contexts": {
    "54c04f68-f31b-460a-a43c-8e4fae3deb72": "/example/OceanVO",
    "e5db6636-d10a-4e07-bf4a-2c457d3c9c1b": "/example/OceanVO/PlanktonVRE",
    "6271f280-303f-4692-ae15-fe3a5e90bae1": "/example/OceanVO/CoralVRE"
  },
  "consistsOf": [
    {
      "type": "ConsistsOf",
      "target": {
        "type": "StateFacet",
        "id": "s-shared",
        "contexts": {
          "54c04f68-f31b-460a-a43c-8e4fae3deb72": "/example/OceanVO",
          "e5db6636-d10a-4e07-bf4a-2c457d3c9c1b": "/example/OceanVO/PlanktonVRE",
          "6271f280-303f-4692-ae15-fe3a5e90bae1": "/example/OceanVO/CoralVRE"
        },
        "value": "active"
      }
    },
    {
      "type": "ConsistsOf",
      "target": {
        "type": "EventFacet",
        "id": "ev-active",
        "contexts": {
          "54c04f68-f31b-460a-a43c-8e4fae3deb72": "/example/OceanVO",
          "e5db6636-d10a-4e07-bf4a-2c457d3c9c1b": "/example/OceanVO/PlanktonVRE",
          "6271f280-303f-4692-ae15-fe3a5e90bae1": "/example/OceanVO/CoralVRE"
        },
        "event": {
          "what": "active",
          "when": "2026-10-20 09:00:00.000 +0200"
        }
      }
    },
    {
      "type": "ConsistsOf",
      "target": {
        "type": "EventFacet",
        "id": "ev-plankton",
        "contexts": {
          "e5db6636-d10a-4e07-bf4a-2c457d3c9c1b": "/example/OceanVO/PlanktonVRE"
        },
        "event": {
          "what": "inactive",
          "when": "2026-10-23 10:20:00.000 +0200",
          "where": "/example/OceanVO/PlanktonVRE",
          "why": "CKAN not found or not configured in /example/OceanVO/PlanktonVRE"
        }
      }
    }
  ]
}
Read StateFacet instances EventFacet instances naming a Context
VO, CoralVRE s-shared (active) none
PlanktonVRE s-shared (active) ev-plankton
VO, hierarchical=true s-shared (active) ev-plankton, and one per VRE with a situation of its own
  • What a discovery sees: a search by state finds the general state, active, also in PlanktonVRE. This is intended: the StateFacet says how the service is, the event says how it is in that Context. Whoever must know the situation of a Context reads its events. A query that leaves out the services with such an event in a Context needs the absence of a related element, which the query language does not express yet (a candidate evolution).
  • The cost: none of the ones above: one write per change, by the Context concerned, with its own token; the general state is one instance; a hierarchical read shows one StateFacet.
  • Status: the way the Information System uses for the state of the services published by SmartGears.

What the Case Teaches

  • Look first at what is general and what belongs to one Context. A state that holds everywhere is one shared instance; a difference in one Context is better recorded as something that Context has in addition, than as a replacement of the shared instance.
  • An event is the natural place for a situation with a reason: Event already has what, when, where and why, and the services already record their lifecycle as events.
  • The first way remains the right one when the value that differs per Context is itself what a discovery must filter on, and the cost of the synchronization is acceptable.
  • A hierarchical read is a union over several Contexts, not one Context: it may show more instances of a Facet type than the schema allows in one Context, and a client accepts it as it is (Hierarchical Graph Union).