11 KiB
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:
@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:
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:
@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:
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:
# 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:
- 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:
- 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:
- Import the new system into OCL first.
- Verify OCL
$validate-codeworks for the new system:curl "https://tr.ocl.dghs.gov.bd/api/fhir/CodeSystem/$validate-code?system={new-system-url}&code={test-code}" - 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:
- Place
bd.gov.dghs.mccod-1.1.0.tgzinpackages/, removebd.gov.dghs.mccod-1.0.0.tgz. - Update the package path in
FhirServerConfig.java(or inapplication.yamlif you implemented the config-driven approach from Step 5 above). - Update
MCCOD_PACKAGE_FILENAMEGitea variable tobd.gov.dghs.mccod-1.1.0.tgz. - Update
MCCOD_PACKAGE_B64Gitea secret with the new package base64. - Tag and push — CI builds and pushes the new image.
- 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
.tgzplaced inpackages/, filename follows naming convention FhirServerConfig.java—npmPackageValidationSupport()loads new packageFhirServerConfig.java—BD_CORE_PROFILE_RESOURCE_TYPESupdated with new resource typesIgPackageInitializer.java— new package included in initialisation loopapplication.yaml— new IG entry added underbd.fhir.ig.packages.env— new IG version variable added- Gitea secrets — new
*_PACKAGE_B64secret created - Gitea variables — new
*_PACKAGE_FILENAMEvariable 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