262 lines
11 KiB
Markdown
262 lines
11 KiB
Markdown
# Adding Additional Implementation Guides
|
|
|
|
**Audience:** DGHS FHIR development and operations team
|
|
**Applies to:** Any IG added after BD Core FHIR IG v0.2.1
|
|
**Current IGs:** BD Core (`https://fhir.dghs.gov.bd/core`)
|
|
**Planned IGs:** MCCoD (`https://fhir.dghs.gov.bd/mccod`), IMCI (`https://fhir.dghs.gov.bd/imci`)
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Each DGHS Implementation Guide has its own canonical URL namespace:
|
|
|
|
| IG | Canonical base | Package naming convention |
|
|
|----|---------------|--------------------------|
|
|
| BD Core | `https://fhir.dghs.gov.bd/core` | `bd.gov.dghs.core-{version}.tgz` |
|
|
| MCCoD | `https://fhir.dghs.gov.bd/mccod` | `bd.gov.dghs.mccod-{version}.tgz` |
|
|
| IMCI | `https://fhir.dghs.gov.bd/imci` | `bd.gov.dghs.imci-{version}.tgz` |
|
|
|
|
Separate canonical namespaces mean profiles from different IGs never collide regardless of resource type overlap. A `Composition` profiled in MCCoD at `https://fhir.dghs.gov.bd/mccod/StructureDefinition/mccod-composition` and a `Composition` profiled in a future Core IG extension are completely independent. HAPI validates a resource against whichever profile URL it declares in `meta.profile`.
|
|
|
|
All packages are loaded into a single `NpmPackageValidationSupport` instance. HAPI merges them into one validation context at startup. There is no performance penalty for multiple IGs — profiles are loaded once into memory and reused across all validation calls.
|
|
|
|
---
|
|
|
|
## What changes when adding a new IG
|
|
|
|
### 1. `packages/` directory
|
|
|
|
Place the new IG `.tgz` alongside the existing core IG package:
|
|
|
|
```
|
|
hapi-overlay/src/main/resources/packages/
|
|
├── bd.gov.dghs.core-0.2.1.tgz ← existing
|
|
├── bd.gov.dghs.mccod-1.0.0.tgz ← new
|
|
└── bd.gov.dghs.imci-1.0.0.tgz ← new
|
|
```
|
|
|
|
Only one version of each IG per image. If you are upgrading an existing IG, remove the old `.tgz` and place the new one.
|
|
|
|
### 2. `FhirServerConfig.java` — load the new package
|
|
|
|
Find the `npmPackageValidationSupport()` bean and add a `loadPackageFromClasspath()` call for each new IG:
|
|
|
|
```java
|
|
@Bean
|
|
public NpmPackageValidationSupport npmPackageValidationSupport(FhirContext fhirContext) {
|
|
NpmPackageValidationSupport support = new NpmPackageValidationSupport(fhirContext);
|
|
|
|
// BD Core IG — always present
|
|
support.loadPackageFromClasspath(
|
|
"classpath:packages/bd.gov.dghs.core-0.2.1.tgz");
|
|
|
|
// MCCoD IG — add when deploying
|
|
support.loadPackageFromClasspath(
|
|
"classpath:packages/bd.gov.dghs.mccod-1.0.0.tgz");
|
|
|
|
// IMCI IG — add when deploying
|
|
support.loadPackageFromClasspath(
|
|
"classpath:packages/bd.gov.dghs.imci-1.0.0.tgz");
|
|
|
|
return support;
|
|
}
|
|
```
|
|
|
|
### 3. `FhirServerConfig.java` — register new resource types
|
|
|
|
The `BD_CORE_PROFILE_RESOURCE_TYPES` set determines which resource types receive full profile validation versus the `unvalidated-profile` tag. Add every resource type that any of your IGs profiles:
|
|
|
|
```java
|
|
private static final Set<String> BD_CORE_PROFILE_RESOURCE_TYPES = Set.of(
|
|
|
|
// BD Core IG
|
|
"Patient", "Condition", "Encounter", "Observation",
|
|
"Practitioner", "Organization", "Location",
|
|
"Medication", "MedicationRequest", "Immunization",
|
|
|
|
// MCCoD IG — add the resource types your MCCoD IG profiles
|
|
"Composition", "MedicationStatement",
|
|
|
|
// IMCI IG — add the resource types your IMCI IG profiles
|
|
"QuestionnaireResponse", "ClinicalImpression"
|
|
);
|
|
```
|
|
|
|
If a resource type appears in multiple IGs (e.g., `Composition` in both MCCoD and a future Core extension), add it once. HAPI validates against whichever profile URL the submitted resource declares — it does not matter that multiple profiles for that type are loaded.
|
|
|
|
### 4. `IgPackageInitializer.java` — load metadata for each new package
|
|
|
|
The initialiser currently loads one package under an advisory lock. Extend it to load each package. The advisory lock pattern remains the same — one lock per package, identified by package ID:
|
|
|
|
```java
|
|
@Override
|
|
public void afterPropertiesSet() throws Exception {
|
|
loadIgPackage(
|
|
"classpath:packages/bd.gov.dghs.core-0.2.1.tgz",
|
|
"bd.gov.dghs.core", "0.2.1");
|
|
|
|
loadIgPackage(
|
|
"classpath:packages/bd.gov.dghs.mccod-1.0.0.tgz",
|
|
"bd.gov.dghs.mccod", "1.0.0");
|
|
|
|
loadIgPackage(
|
|
"classpath:packages/bd.gov.dghs.imci-1.0.0.tgz",
|
|
"bd.gov.dghs.imci", "1.0.0");
|
|
}
|
|
|
|
private void loadIgPackage(
|
|
String classpathPath,
|
|
String packageId,
|
|
String version) throws Exception {
|
|
|
|
long lockKey = deriveLockKey(packageId);
|
|
// ... same advisory lock acquisition logic as current implementation
|
|
// ... same performIgLoad() call
|
|
// Each package gets its own independent advisory lock key
|
|
// so packages load concurrently across replicas without blocking each other
|
|
}
|
|
```
|
|
|
|
### 5. `application.yaml` — add new IG configuration entries
|
|
|
|
Under the `bd.fhir.ig` section, add entries for the new packages. This makes IG paths configurable without recompiling:
|
|
|
|
```yaml
|
|
bd:
|
|
fhir:
|
|
ig:
|
|
packages:
|
|
- classpath: classpath:packages/bd.gov.dghs.core-0.2.1.tgz
|
|
id: bd.gov.dghs.core
|
|
version: 0.2.1
|
|
- classpath: classpath:packages/bd.gov.dghs.mccod-1.0.0.tgz
|
|
id: bd.gov.dghs.mccod
|
|
version: 1.0.0
|
|
- classpath: classpath:packages/bd.gov.dghs.imci-1.0.0.tgz
|
|
id: bd.gov.dghs.imci
|
|
version: 1.0.0
|
|
```
|
|
|
|
Update `FhirServerConfig.java` to read this list and loop over it rather than having hardcoded paths. This means adding a new IG in future requires only a config change and new `.tgz` — no Java code change.
|
|
|
|
### 6. `.env` — add new IG version variables
|
|
|
|
Add version tracking variables for operational visibility and for the `/actuator/info` endpoint:
|
|
|
|
```bash
|
|
# BD Core IG
|
|
HAPI_IG_CORE_VERSION=0.2.1
|
|
|
|
# MCCoD IG
|
|
HAPI_IG_MCCOD_VERSION=1.0.0
|
|
|
|
# IMCI IG
|
|
HAPI_IG_IMCI_VERSION=1.0.0
|
|
```
|
|
|
|
### 7. Gitea workflow — add new IG package secrets
|
|
|
|
For each new IG, add a Gitea secret and decode it in the build step:
|
|
|
|
**Gitea → Repository → Settings → Secrets — add:**
|
|
|
|
| Secret | Value |
|
|
|--------|-------|
|
|
| `MCCOD_PACKAGE_B64` | `base64 -w 0 bd.gov.dghs.mccod-1.0.0.tgz` |
|
|
| `IMCI_PACKAGE_B64` | `base64 -w 0 bd.gov.dghs.imci-1.0.0.tgz` |
|
|
|
|
**Gitea → Repository → Settings → Variables — add:**
|
|
|
|
| Variable | Value |
|
|
|----------|-------|
|
|
| `MCCOD_PACKAGE_FILENAME` | `bd.gov.dghs.mccod-1.0.0.tgz` |
|
|
| `IMCI_PACKAGE_FILENAME` | `bd.gov.dghs.imci-1.0.0.tgz` |
|
|
|
|
**In `.gitea/workflows/build.yml` — extend the IG placement step:**
|
|
|
|
```yaml
|
|
- name: Place IG packages for build
|
|
run: |
|
|
echo "${{ secrets.IG_PACKAGE_B64 }}" | base64 -d > \
|
|
hapi-overlay/src/main/resources/packages/${{ vars.IG_PACKAGE_FILENAME }}
|
|
|
|
echo "${{ secrets.MCCOD_PACKAGE_B64 }}" | base64 -d > \
|
|
hapi-overlay/src/main/resources/packages/${{ vars.MCCOD_PACKAGE_FILENAME }}
|
|
|
|
echo "${{ secrets.IMCI_PACKAGE_B64 }}" | base64 -d > \
|
|
hapi-overlay/src/main/resources/packages/${{ vars.IMCI_PACKAGE_FILENAME }}
|
|
|
|
echo "Packages placed:"
|
|
ls -lh hapi-overlay/src/main/resources/packages/
|
|
```
|
|
|
|
**And extend the cleanup step:**
|
|
|
|
```yaml
|
|
- name: Clean up IG packages from workspace
|
|
if: always()
|
|
run: rm -f hapi-overlay/src/main/resources/packages/*.tgz
|
|
```
|
|
|
|
---
|
|
|
|
## What does not change
|
|
|
|
| Component | Reason |
|
|
|-----------|--------|
|
|
| Validation chain order | `NpmPackageValidationSupport` handles all loaded IGs transparently |
|
|
| OCL integration | `BdTerminologyValidationSupport` intercepts only `http://id.who.int/icd/release/11/mms` — other systems in any IG route through normally |
|
|
| Cluster expression validator | ICD-11 specific — unaffected by other IGs |
|
|
| Keycloak auth | No change — all vendors use `mci-api` role regardless of which IG they submit against |
|
|
| Audit tables | Schema is resource-type agnostic — new resource types are captured automatically |
|
|
| PostgreSQL schema | No migration needed — HAPI JPA stores all FHIR R4 resource types in the same tables |
|
|
| pgBouncer, nginx proxy config | Infrastructure is IG-agnostic |
|
|
|
|
---
|
|
|
|
## Terminology considerations for new IGs
|
|
|
|
If MCCoD or IMCI IGs introduce coded elements using systems **other than ICD-11** that are already in your OCL instance (e.g., LOINC, drug ValueSets), no additional configuration is needed. `BdTerminologyValidationSupport` only handles ICD-11. All other systems fall through to HAPI's standard remote terminology mechanism which already calls OCL.
|
|
|
|
If a new IG introduces a **new terminology system** not currently in OCL:
|
|
|
|
1. Import the new system into OCL first.
|
|
2. Verify OCL `$validate-code` works for the new system: `curl "https://tr.ocl.dghs.gov.bd/api/fhir/CodeSystem/$validate-code?system={new-system-url}&code={test-code}"`
|
|
3. No HAPI code changes needed — HAPI's remote terminology support handles any system OCL knows about.
|
|
|
|
If a new IG introduces a terminology system that will **never be in OCL** (e.g., a purely local ValueSet defined within the IG itself), HAPI will validate it using `InMemoryTerminologyServerValidationSupport` from the concepts loaded with the IG package. No external call is made.
|
|
|
|
---
|
|
|
|
## Upgrade procedure for an existing specialised IG
|
|
|
|
When MCCoD advances from v1.0.0 to v1.1.0:
|
|
|
|
1. Place `bd.gov.dghs.mccod-1.1.0.tgz` in `packages/`, remove `bd.gov.dghs.mccod-1.0.0.tgz`.
|
|
2. Update the package path in `FhirServerConfig.java` (or in `application.yaml` if you implemented the config-driven approach from Step 5 above).
|
|
3. Update `MCCOD_PACKAGE_FILENAME` Gitea variable to `bd.gov.dghs.mccod-1.1.0.tgz`.
|
|
4. Update `MCCOD_PACKAGE_B64` Gitea secret with the new package base64.
|
|
5. Tag and push — CI builds and pushes the new image.
|
|
6. Deploy the new image on the production server.
|
|
|
|
If the IG upgrade changes terminology ValueSets in OCL (new codes, reclassified codes), follow the cache flush procedure in `ops/version-upgrade-integration.md` after deployment.
|
|
|
|
---
|
|
|
|
## Deployment checklist for a new IG
|
|
|
|
- [ ] New IG `.tgz` placed in `packages/`, filename follows naming convention
|
|
- [ ] `FhirServerConfig.java` — `npmPackageValidationSupport()` loads new package
|
|
- [ ] `FhirServerConfig.java` — `BD_CORE_PROFILE_RESOURCE_TYPES` updated with new resource types
|
|
- [ ] `IgPackageInitializer.java` — new package included in initialisation loop
|
|
- [ ] `application.yaml` — new IG entry added under `bd.fhir.ig.packages`
|
|
- [ ] `.env` — new IG version variable added
|
|
- [ ] Gitea secrets — new `*_PACKAGE_B64` secret created
|
|
- [ ] Gitea variables — new `*_PACKAGE_FILENAME` variable created
|
|
- [ ] Gitea workflow — new package decode and cleanup steps added
|
|
- [ ] New image built, pushed, deployed
|
|
- [ ] Acceptance test: submit a resource claiming the new IG profile → 201 accepted
|
|
- [ ] Acceptance test: submit a resource violating the new IG profile → 422 rejected
|
|
- [ ] Acceptance test: existing Core IG submissions still work → 201 accepted
|
|
- [ ] Vendors notified of new IG availability and profile URLs |