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: aPUTof the EService instance with the token of PlanktonVRE, where the sharedStateFacetis replaced by a new one and anEventFacetis added. - Going back to
active: aPUTwith the token of the VO, with the EService instance as the VO sees it, andsync={planktonvre-uuid}: PlanktonVRE is aligned to the VO, the sharedStateFacetjoins it, and its ownStateFacetandEventFacetleave 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 theactiveones. - The cost: every change of the general state (the service goes
down) must also reach each VRE that has aStateFacetof its own; going back toactiveneeds 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 twoStateFacetinstances. - 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
StateFacetper Context; after an Add to Context the inheritedStateFacetmust be replaced by a new one. - Status: considered and rejected: the number of
StateFacetinstances 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
inactivein PlanktonVRE: aPUTof the EService instance with the token of PlanktonVRE, which adds theEventFacet. TheStateFacetis not touched. - Fine again: another
PUTwith the token of PlanktonVRE, without theEventFacet: 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: theStateFacetsays 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:
Eventalready haswhat,when,whereandwhy, 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).