WellData Implementation Guide
0.1.2 - ci-build
WellData Implementation Guide - Local Development build (v0.1.2) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions
This document describes the technical architecture for an ephemeral FHIR service that provides a full FHIR R4 API backed by data stored in a Solid pod. The service acts as a translation and query layer between FHIR clients and the user's personal data store.
FHIR is designed as an exchange format optimized for interoperability between healthcare systems. It allows multiple valid representations of the same data and relies heavily on server-side search and query capabilities. Solid pods, on the other hand, are personal data stores that:
Storing FHIR resources directly in a pod and expecting FHIR-style queries to work is not feasible. A translation layer is required.
The solution is an ephemeral (short-lived) FHIR service that:
expires_in)The key insight is that the ephemeral service instance is bound to the pod access token. The token's expires_in value determines the service instance lifetime, and the jti (JWT ID) or token hash serves as the instance identifier.
┌─────────────────────────────────────────────────────────────────────────────┐
│ User's Environment │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ App A │ │ App B │ │ App C │ │
│ │ (Welldata) │ │ (Zipster) │ │ (Selfcare) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Ephemeral FHIR Service (Shared Instance) │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │ │
│ │ │ FHIR REST API │ │ In-Memory │ │ RDF Transformer │ │ │
│ │ │ (R4 compliant) │ │ Resource Store │ │ (FHIR RDF ↔ JSON) │ │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │ │
│ │ │ Capability │ │ Search Index │ │ Version Manager │ │ │
│ │ │ Statement │ │ (in-memory) │ │ (conflict detect) │ │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────────┘ │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────┴───────────────────┐ │
│ │ │ │
│ ▼ Load (session start) ▼ Persist (on write) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Solid Pod (RDF/Turtle) │ │
│ │ │ │
│ │ <pod>/weare/fhir/Patient/ │ │
│ │ <pod>/weare/fhir/Observation/ │ │
│ │ <pod>/weare/fhir/Questionnaire/ │ │
│ │ <pod>/weare/fhir/QuestionnaireResponse/ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
The ephemeral FHIR service supports the WellData profiles as defined in this implementation guide:
| Resource | Profile | Pod Location |
|---|---|---|
| Patient | WellDataPatient | <pod>/weare/fhir/Patient/<uuid>.ttl |
| Observation | WellDataObservation | <pod>/weare/fhir/Observation/<uuid>.ttl |
| Questionnaire | WellDataQuestionnaire | <pod>/weare/fhir/Questionnaire/<uuid>.ttl |
| QuestionnaireResponse | WellDataQuestionnaireResponse | <pod>/weare/fhir/QuestionnaireResponse/<uuid>.ttl |
Data is stored in the pod using the official FHIR RDF representation. This ensures:
Example Observation in Turtle format (as stored in pod):
@prefix fhir: <http://hl7.org/fhir/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
<urn:uuid:obs-weight-001> a fhir:Observation ;
fhir:meta [
fhir:versionId [ fhir:v "1" ] ;
fhir:lastUpdated [ fhir:v "2024-03-15T10:30:00Z"^^xsd:dateTime ]
] ;
fhir:status [ fhir:v "final" ] ;
fhir:code [
fhir:coding [
fhir:system [ fhir:v "http://snomed.info/sct" ] ;
fhir:code [ fhir:v "27113001" ] ;
fhir:display [ fhir:v "Body weight" ]
]
] ;
fhir:subject [
fhir:reference [ fhir:v "Patient/patient-001" ]
] ;
fhir:effectiveDateTime [ fhir:v "2024-03-15T10:30:00Z"^^xsd:dateTime ] ;
fhir:valueQuantity [
fhir:value [ fhir:v "70"^^xsd:decimal ] ;
fhir:unit [ fhir:v "kg" ]
] .
The ephemeral FHIR service lifecycle is directly bound to the pod access token. Each unique token creates its own service instance.
The service instance is identified by:
jti (JWT ID): If the access token contains a jti claim, this is used as the instance identifierjti is available, a SHA-256 hash of the token is usedThis ensures that:
When a request arrives with a pod access token:
jti or hash).ttl files in each resource containerexp claim (or current time + expires_in)Read operations are served entirely from the in-memory store:
GET /Patient/[id] - Direct lookupGET /Observation?subject=Patient/[id] - Search via in-memory indexGET /Observation?code=27113001 - Code-based searchThis provides fast response times and full FHIR search parameter support.
Write operations follow a write-through pattern:
.ttl file to appropriate containerThe service instance is terminated when:
exp claim time is reachedThe service can be safely terminated at any time because:
This is similar to a Java WeakReference pattern: the in-memory data can be garbage collected, but the authoritative data in the pod remains.
When a client refreshes their access token:
This approach is simpler than tracking token lineage and ensures a clean state on refresh.
Each FHIR resource includes meta.versionId and meta.lastUpdated fields. These are used for optimistic concurrency control:
versionId and lastUpdated for each resourceversionId, update lastUpdatedIf data in the pod is modified outside the FHIR service (e.g., by another pod application):
versionId/lastUpdated with in-memory versionHTTP/1.1 409 Conflict
Content-Type: application/fhir+json
{
"resourceType": "OperationOutcome",
"issue": [{
"severity": "error",
"code": "conflict",
"diagnostics": "Resource version mismatch. Pod version: 3, Expected: 2. Data has been reloaded."
}]
}
Multiple applications can share a FHIR service instance if they use the same access token.
Applications discover the FHIR service endpoint through:
Example WebID document extension:
@prefix solid: <http://www.w3.org/ns/solid/terms#> .
@prefix welldata: <https://gidsopenstandaarden.github.io/welldata#> .
<#me> welldata:fhirEndpoint <https://fhir.example.com/user123/r4> .
The sharing behavior is determined by token usage:
| Scenario | Behavior |
|---|---|
| Same token, multiple apps | Share the same instance |
| Different tokens, same user | Separate instances (both see same pod data) |
| Token refresh | New instance created |
Applications that need to share an instance should coordinate to use the same access token. This is typically the case when:
Memory management is simplified by binding to token lifetime:
exp claimmax_concurrent_tokens × avg_data_sizeThe FHIR service uses the pod access token directly - no separate authentication is needed.
The pod access token serves as both:
Authorization: Bearer <token>jti (or computes hash) to find/create instanceThe FHIR service respects the same access controls as direct pod access:
/weare/fhir/Observation/, the client can read/write Observations via FHIR403 ForbiddenThe FHIR service publishes a CapabilityStatement describing its capabilities:
{
"resourceType": "CapabilityStatement",
"status": "active",
"date": "2024-03-15",
"kind": "instance",
"fhirVersion": "4.0.1",
"format": ["json", "xml"],
"rest": [{
"mode": "server",
"resource": [
{
"type": "Patient",
"profile": "https://gidsopenstandaarden.github.io/welldata-implementation-guide/StructureDefinition/WellDataPatient",
"interaction": [
{ "code": "read" },
{ "code": "vread" },
{ "code": "update" },
{ "code": "delete" },
{ "code": "create" },
{ "code": "search-type" }
],
"searchParam": [
{ "name": "_id", "type": "token" },
{ "name": "identifier", "type": "token" },
{ "name": "name", "type": "string" },
{ "name": "birthdate", "type": "date" }
]
},
{
"type": "Observation",
"profile": "https://gidsopenstandaarden.github.io/welldata-implementation-guide/StructureDefinition/WellDataObservation",
"interaction": [
{ "code": "read" },
{ "code": "vread" },
{ "code": "update" },
{ "code": "delete" },
{ "code": "create" },
{ "code": "search-type" }
],
"searchParam": [
{ "name": "_id", "type": "token" },
{ "name": "subject", "type": "reference" },
{ "name": "code", "type": "token" },
{ "name": "date", "type": "date" },
{ "name": "status", "type": "token" }
]
},
{
"type": "Questionnaire",
"profile": "https://gidsopenstandaarden.github.io/welldata-implementation-guide/StructureDefinition/WellDataQuestionnaire",
"interaction": [
{ "code": "read" },
{ "code": "search-type" }
],
"searchParam": [
{ "name": "_id", "type": "token" },
{ "name": "identifier", "type": "token" },
{ "name": "name", "type": "string" },
{ "name": "status", "type": "token" }
]
},
{
"type": "QuestionnaireResponse",
"profile": "https://gidsopenstandaarden.github.io/welldata-implementation-guide/StructureDefinition/WellDataQuestionnaireResponse",
"interaction": [
{ "code": "read" },
{ "code": "vread" },
{ "code": "update" },
{ "code": "delete" },
{ "code": "create" },
{ "code": "search-type" }
],
"searchParam": [
{ "name": "_id", "type": "token" },
{ "name": "questionnaire", "type": "reference" },
{ "name": "subject", "type": "reference" },
{ "name": "author", "type": "reference" },
{ "name": "authored", "type": "date" },
{ "name": "status", "type": "token" }
]
}
]
}]
}
The ephemeral FHIR service can be implemented using various technologies:
| Option | Description | Considerations |
|---|---|---|
| HAPI FHIR | Java-based FHIR server | Mature, full-featured, JPA or in-memory store |
| Medplum | TypeScript FHIR platform | Modern, cloud-native design |
| Custom Implementation | Purpose-built for WellData | Minimal footprint, optimized for use case |
Optimization strategies:
Future versions may support FHIR Subscriptions to notify applications of changes:
Support for FHIR Bulk Data Export:
FHIR R4 includes GraphQL support: