# 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](05-api-operational-cookbook.html#the-situation-of-one-context).

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:

```json
{
  "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.

```json
{
  "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.

```json
{
  "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](#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.

```json
{
  "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](../12-is-architectural-decisions.html#hierarchical-graph-union-pre-computed-hierarchical-contexts)).
