Editions (DEPLOY-002)
The platform ships in more than one shape from one tree. A pharmacy does not need the lab, imaging or workforce schemas; a standalone laboratory does not need pharmacy. An edition is a dependency list, and nothing more.
BASE (every edition) core + erp + reporting + fhir + hl7
+ every module platform-app the hospital edition, the default deployable
+ pharmacy pharmacy-edition
+ lab lab-edition
The base is declared once, in backend/dist/edition-base, together with the PlatformApplication
launcher and edition-base.yml. Listing it module-by-module in each edition is how one edition quietly
loses reporting.
What an edition module contains
Three files, and they must agree with each other:
| File | Decides |
|---|---|
pom.xml | which module code is in the jar |
src/main/resources/db/changelog/<edition>.db.changelog-master.yaml | which schemas the deployment creates |
src/main/resources/application.yml | which cross-module Feign clients are registered |
EditionCompositionTest fails the build when they disagree, naming the edition and the module. Each
disagreement otherwise fails in a way that does not name its cause:
- in the pom but not the changelog: the jar boots and 500s on the first query, against tables nobody created;
- in the changelog but not the pom: a schema no release will ever migrate again, in a database an operator will later be asked to explain;
- a module flag left unstated: DEPLOY-001 registers a domain's clients when the property is absent,
so the edition demands a
<module>.service.urlfor a module it does not ship, and the context fails at load.
Declining a module
⚠ false must be stated. matchIfMissing = true is deliberate — this landed on a working platform
and a default of "off" would have silently removed clients from every deployment that had not yet heard
of the flag. Saying false is how an edition declines a module:
platform:
module:
pharmacy:
enabled: true
lab:
enabled: false
The eight gated domains are dhis2, hl7, imaging, insurance, lab, pharmacy, reporting, workforce. The
other client packages are core engines, and core is in every edition.
What a module does when its neighbour is not deployed
⚠ The answer is per case, and "carry on quietly" is usually wrong. Absent a module, a validator has nothing to validate — which is not the same as having found nothing wrong:
| Caller | Absent module | Behaviour |
|---|---|---|
ErpStaffReferenceValidator | workforce | refuses the reference: no staff profile in this deployment can be verified |
ErpClinicalProductGuard | pharmacy | refuses the line: there is no catalogue for the id to name |
WorkforceLabourAccrualAdapter, WorkforcePayrollPostingAdapter, PharmacyCogsAdapter | workforce / pharmacy | isConfigured() is false — the posting source is skipped, as it is when its flag or URL is unset |
FhirCoverageClaimServiceImpl | insurance | empty Bundle: a deployment with no insurance module holds no Coverage, so the search succeeds and finds nothing |
OruMessageHandler | imaging and lab | rejects the message with a reason, through the path an unknown accession already takes — never accepted and dropped |
PatientMergeModuleOrchestratorServiceImpl | any | NOT_DEPLOYED: nothing is being asserted, there is simply no record to re-point |
Configuration layering
Since CONF-002 there is exactly one application.yml on a deployable's classpath. An edition's own
application.yml is small, and everything shared is imported:
| Precedence | Source |
|---|---|
| 1 (wins) | environment variables and .env |
| 2 | the edition's application.yml — its changelog and its module flags |
| 3 | edition-base.yml, imported by it |
| 4 | each module's uhp-module-defaults.yml |
⚠ edition-base.yml is deliberately not called application.yml: a second one on the classpath
would make which of them the jar reads depend on jar order.
Building and running an edition
./mvnw -o -pl dist/pharmacy-edition -am -DskipTests install
java -jar backend/dist/pharmacy-edition/target/pharmacy-edition-0.0.1-SNAPSHOT.jar
⚠ Use APP_PORT, never --server.port: the latter moves only the listener, and the loopback Feign
clients keep calling the old port.
What an edition is not
⚠ It is not a smaller download. The hospital jar is 180 MB and the pharmacy jar 177 MB — nearly all of both is shared third-party code. The saving is in the schemas created, the HTTP surface exposed and the modules an operator has to run and patch, not in bytes.
Topology is a separate axis
An edition can run bundled (one process) or distributed (one per module). See
Topology, and note that under distributed a missing <module>.service.url is fatal at
startup rather than quietly defaulted to loopback.