You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The querystore SPI has a set of conventions that consumers need to learn — patient-scoping contract, voided-row policy, trigger-method matching, lazy-init for provided-scope supertypes, etc. — but the conventions live as inline comments scattered across AbstractIndexingAdvice, AbstractRecordSerializer, QueryDocument, and the existing reference serializers (AllergyRecordSerializer, VisitRecordSerializer, EncounterRecordSerializer, etc.).
A new contributor wanting to add a resource type currently has to:
Read omod/config.xml files in existing consumer modules to learn the advice registration pattern.
Read the Spring moduleApplicationContext.xml files in existing consumer modules to learn the lazy-init=\"true\" pattern and why it's required.
Reverse-engineer the patient-scoping contract from getPatientUuid() implementations.
For the billing module's slice we ended up writing a 24-decision ADR for the consumer (openmrs/openmrs-module-billing#176) covering the conventions we discovered. That document is consumer-specific but ~70% of its content is restating SPI-level conventions that would belong in a querystore-level document.
Suggested approach
A docs/adr/ directory in this repo with an initial ADR (or design doc) covering the SPI's load-bearing decisions. Candidates for inclusion:
Patient-scoping contract.getPatientUuid() may return null for administrative documents (provider-scoped, catalog-scoped). The querystore name is patient-centric but the SPI accepts patient-less documents — this is non-obvious.
Voided-row policy. Voided documents are filtered, not removed. The purge path is separate from the void path. Consumers default-filter with voided=false.
lazy-init=\"true\" is required for serializers and providers because their supertypes are in a provided-scope jar that may be absent on dependent modules' test classpaths. Eager init would crash module loading in those contexts.
Resource type ID namespacing. Consumers should prefix their type IDs (billing_*, fhir_*) to avoid silent collisions on the (resourceType, resourceUuid) key.
text is for full-text search; metadata is the structured contract. Consumers parsing the text blob is brittle.
The billing PR's ADR is a worked example of the format and depth that's useful. A querystore-level version would have prevented several Phase 1 review cycles in our slice and would unblock future contributors.
Failure mode prevented
Without this, every new consumer module rediscovers the conventions by reverse-engineering. The cost is paid in code review cycles, sometimes in production drift (a typo'd trigger method that ships green and surfaces weeks later, a missing lazy-init that breaks a downstream module's tests), and in fragmentation — each consumer ends up with a slightly different interpretation of the SPI's intent.
Problem
The querystore SPI has a set of conventions that consumers need to learn — patient-scoping contract, voided-row policy, trigger-method matching, lazy-init for
provided-scope supertypes, etc. — but the conventions live as inline comments scattered acrossAbstractIndexingAdvice,AbstractRecordSerializer,QueryDocument, and the existing reference serializers (AllergyRecordSerializer,VisitRecordSerializer,EncounterRecordSerializer, etc.).A new contributor wanting to add a resource type currently has to:
AbstractIndexingAdvicesource to understand the per-entity exception swallow (see Per-entity serialize() failures are swallowed at WARN with no metric — silent index drift #29) and trigger-name matching (see Validate AbstractIndexingAdvice TRIGGER_METHODS at bean init against the target service interface #17).omod/config.xmlfiles in existing consumer modules to learn the advice registration pattern.moduleApplicationContext.xmlfiles in existing consumer modules to learn thelazy-init=\"true\"pattern and why it's required.getPatientUuid()implementations.For the billing module's slice we ended up writing a 24-decision ADR for the consumer (openmrs/openmrs-module-billing#176) covering the conventions we discovered. That document is consumer-specific but ~70% of its content is restating SPI-level conventions that would belong in a querystore-level document.
Suggested approach
A
docs/adr/directory in this repo with an initial ADR (or design doc) covering the SPI's load-bearing decisions. Candidates for inclusion:getPatientUuid()may returnnullfor administrative documents (provider-scoped, catalog-scoped). The querystore name is patient-centric but the SPI accepts patient-less documents — this is non-obvious.voided=false.lazy-init=\"true\"is required for serializers and providers because their supertypes are in aprovided-scope jar that may be absent on dependent modules' test classpaths. Eager init would crash module loading in those contexts.List<String>for plural fields,nullwrites are filtered out, etc. (see Document the metadata-shape contract for ClinicalRecordSerializer / QueryDocument.putMetadata #27).billing_*,fhir_*) to avoid silent collisions on the(resourceType, resourceUuid)key.textis for full-text search;metadatais the structured contract. Consumers parsing the text blob is brittle.The billing PR's ADR is a worked example of the format and depth that's useful. A querystore-level version would have prevented several Phase 1 review cycles in our slice and would unblock future contributors.
Failure mode prevented
Without this, every new consumer module rediscovers the conventions by reverse-engineering. The cost is paid in code review cycles, sometimes in production drift (a typo'd trigger method that ships green and surfaces weeks later, a missing lazy-init that breaks a downstream module's tests), and in fragmentation — each consumer ends up with a slightly different interpretation of the SPI's intent.
Related: #16, #17, #18, #27, #28, #29, #30.