Gino Canessa on how to describe an operation, and where that approach quietly stops working — Mar 25, 2025
Describing the export operation — profile the Parameters resource
Profile the Parameters resource. The information mirrors OperationDefinition almost exactly, but you can't validate against an OperationDefinition and developers get no example to copy, so they ask for a profile anyway. And only the profile can carry an invariant like "you can't supply both a URL and a canonical".
The R6 build has added inputProfile and outputProfile for exactly this; older versions need an extension.
If you could go back in time, OperationDefinition's parameter block would just be a Parameters you could profile. But it went normative, so all we could add was a slot for one.
Returning CSV or Parquet — nothing in FHIR describes that cleanly
Gino's route is to declare the return type as Binary and let content negotiation do the work.
Nikolai said the spec is a little bit lying about GraphQL — everyone returns plain GraphQL JSON rather than a Binary wrapper, because otherwise GraphQL clients break — and Gino agreed nobody is expected to ever actually hand back a Binary resource.
John came round after reading the Binary page, which has a whole content-negotiation section, and made the point that settled it: you never have to implement Binary storage, ids or lifecycle, you're only using the type to describe a response.
They agreed to file a ticket for a better way to say it.
Logical model for the response — floated, pushed back
Gino floated defining a logical model for the response instead — a view run response with the shape you actually want, rather than a Parameters full of names and parts — which fits R6's push to treat logical models as first class.
Parameters is trivial to write helpers against, needs no generated types, and stays open when an implementation wants to add a field. A logical model sounds like overkill.
Gino's compromise: from an OperationDefinition, generating either a profile or a logical model should be a trivial transform, and he made himself a note because he doesn't know why the tool doesn't already exist.
Async export — a Parameters body that grows with the job
Nikolai proposed that an async export return a Parameters body alongside the 202 and the Location header — the export id, an echo of what was requested, maybe an estimate — and that the same shape simply grow as the job runs: status, progress, partial outputs, and finally the outputs themselves.
John pointed out this is the FHIR async pattern, which in Pathling he implemented once generically for every operation via the respond-async header.
On progress he was sceptical: today it's an unstructured string and it's hard to make it more, because progress is implementation-specific — Spark counts jobs it knows about against jobs finished, another engine has nothing to report — and a string is only good for displaying.
Nikolai's answer: as Parameters it can be a percentage, an estimated finish, the views already done, the rows processed. Gino added that all of it stays optional, so a client copes with a server that says nothing at all.
Advertising ViewDefinition in a capability statement
Gino's advice: accept being on the bleeding edge of what R6 is still defining. Pick an approach, try it, and keep track of what works and what doesn't so it can steer the spec.
When Nikolai said they'd be backward compatible forever so they wouldn't need a version on it, Gino's reply was: sure, but everyone's not using Clojure.