DiamondLightSource / DiamondLightSource/fastcs
Consider removing the GraphQL transport until a concrete use case is defined
- Dominant language
- Python
- Stars
- 6
- Forks
- 8
- Avg merge
- 2d 12h
- Merged PRs (30d)
- 3
Description
# Consider removing the GraphQL transport until a concrete use case is defined
The GraphQL transport was added as part of the multi-controller foundation. Before it ships as a supported interface, we should agree on who the consumers are — because the two plausible use cases pull in opposite directions and neither is fully served by what we have today.
## Use case A — Human explorer
A developer or operator opens a GraphQL playground (e.g. GraphiQL or Apollo Sandbox), introspects the auto-generated schema to discover what the `MAIN` temperature controller exposes, and reads the current temperature.
**With the current typed schema:**
```graphql
# Step 1: discover the schema via introspection (free, built into all GraphQL clients)
query IntrospectMain {
__type(name: "MAINQuery") {
fields { name type { name } }
}
}
# → fields: [temperature, setpoint, rampRate, ...]
# Step 2: fetch the value
query GetTemperature {
MAIN {
temperature
}
}
# → {"data": {"MAIN": {"temperature": 25.3}}}
```
**This use case is well served by the current design.** The typed schema is the natural medium for exploratory human use: field names are discoverable, types are checked, and the playground can auto-complete. The extra `MAIN` wrapper is a minor ergonomic cost.
**With a generic `getAttribute` API:**
```graphql
query GetTemperature {
getAttribute(path: "MAIN/temperature")
}
# → {"data": {"getAttribute": "25.3"}} # stringly-typed; no schema guidance
```
This is actively worse for the human case: the playground cannot auto-complete `"MAIN/temperature"`, the return is untyped, and there is no way to introspect what paths are valid without a separate `introspectController` call.
## Use case B — Machine subscriber (e.g. a TextRead widget)
A display panel has a `TextRead` widget bound to the `MAIN` controller's temperature attribute. It needs to receive a pushed update every time the value changes, without polling.
**With the current typed schema:**
There are no subscriptions. The only option is polling:
```graphql
# repeated every N seconds — not a subscription
query PollTemperature {
MAIN { temperature }
}
```
A `strawberry.subscription` could be added — it is supported by the library — but it requires a WebSocket transport and an async generator that bridges FastCS's internal attribute-change callbacks to the subscription stream. That plumbing does not exist yet.
**With a generic `subscribe` API:**
```graphql
subscription WatchTemperature {
subscribe(path: "MAIN/temperature")
}
# → stream of {"data": {"subscribe": "25.3"}} on each change
```
The generic path-based subscription is actually *more practical* here than the typed approach: a widget framework driven by string paths (like CSS selectors) can subscribe to any attribute without needing a code-generated client SDK. The loss of static typing matters less when the consumer is machine code that already knows the attribute's type from a separate schema fetch or configuration.
## The tension
| | Typed schema (current) | Generic `getAttribute` |
|---|---|---|
| Human explorer | ✅ Natural fit; introspection, auto-complete | ❌ No schema guidance |
| Machine bulk-read | ✅ One query, many typed fields | ✅ Could also work |
| Machine subscription | ❌ Not implemented; hard to express per-field | ✅ Simpler to wire to attr callbacks |
| Code-gen client SDK | ✅ Industry standard (Apollo, graphql-codegen) | ❌ SDK is useless without types |
| Odin-control drop-in | ❌ Different mental model entirely | ✅ Closer to ParameterTree |
The core problem is that **GraphQL's type system is only a benefit when clients are built against it**. We have no such clients. Meanwhile, the one machine use case we can anticipate (subscription updates for display widgets) is not implemented at all — and when it is, the generic path-based form is easier to connect to FastCS's attribute-change infrastructure than a typed field-per-attribute subscription.
## Proposal
**Remove the GraphQL transport from this PR** (or gate it behind an `extras` install that is explicitly marked experimental) until at least one of the following is true:
1. A real human-facing client exists that benefits from typed schema introspection.
2. The subscription mechanism (WebSocket transport + attribute-change → async generator bridge) is implemented and tested, confirming which schema shape is ergonomically correct for that use case.
Shipping the transport now locks in a schema shape (`{ MAIN { temperature } }`) that may need to change again once subscriptions land, and creates migration burden for consumers we don't yet have.
If the transport is kept, the minimum necessary addition before it can be considered stable is:
- `strawberry.subscription` fields that stream attribute value changes, either typed per-field or via a generic `subscribe(path: str)` — with a concrete display-widget integration validating whichever choice is made.
Contributor guide
Assessment
This issue has not been assessed yet.