Files
bd-fhir-national/ops/adding-additional-igs.md
2026-03-16 00:02:58 +06:00

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:

  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.javanpmPackageValidationSupport() loads new package
  • FhirServerConfig.javaBD_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